-- ========================================== -- CLUB DAGUERRE CAMERA DATABASE -- Part 1: Core Hierarchy -- ========================================== -- Enable required extensions CREATE EXTENSION IF NOT EXISTS btree_gist; -- ========================================== -- MANUFACTURERS -- ========================================== CREATE TABLE manufacturers ( id SERIAL PRIMARY KEY, goergens_code VARCHAR(4) UNIQUE NOT NULL, name VARCHAR(200) NOT NULL, country VARCHAR(3), years_active INT4RANGE, -- Audit created_at TIMESTAMP NOT NULL DEFAULT NOW(), created_by INT, updated_at TIMESTAMP, updated_by INT, version INT DEFAULT 1, deleted_at TIMESTAMP, CONSTRAINT chk_mfg_code CHECK (goergens_code ~ '^[A-Z]{2,4}$') ); CREATE INDEX idx_mfg_name ON manufacturers(name); CREATE INDEX idx_mfg_country ON manufacturers(country); CREATE INDEX idx_mfg_active ON manufacturers(id) WHERE deleted_at IS NULL; COMMENT ON TABLE manufacturers IS 'Camera manufacturers - top level of Goergens hierarchy'; COMMENT ON COLUMN manufacturers.goergens_code IS 'Goergens manufacturer code (e.g., ERNM, ZEII)'; -- ========================================== -- CAMERA MODELS -- ========================================== CREATE TABLE camera_models ( id SERIAL PRIMARY KEY, manufacturer_id INT NOT NULL REFERENCES manufacturers(id), goergens_model_number VARCHAR(10) NOT NULL, -- Goergens classification codes body_type VARCHAR(2) NOT NULL, viewfinder_type VARCHAR(4), format_code VARCHAR(20) NOT NULL, -- Production years year_first INT, year_last INT, -- Audit created_at TIMESTAMP NOT NULL DEFAULT NOW(), created_by INT, updated_at TIMESTAMP, updated_by INT, version INT DEFAULT 1, deleted_at TIMESTAMP, UNIQUE(manufacturer_id, goergens_model_number), CONSTRAINT chk_years CHECK (year_first IS NULL OR year_last IS NULL OR year_first <= year_last), CONSTRAINT chk_body_type CHECK (body_type ~ '^[A-Z][a-z]?$') ); CREATE INDEX idx_models_manufacturer ON camera_models(manufacturer_id); CREATE INDEX idx_models_type ON camera_models(body_type, viewfinder_type); CREATE INDEX idx_models_format ON camera_models(format_code); CREATE INDEX idx_models_years ON camera_models(year_first, year_last); CREATE INDEX idx_models_active ON camera_models(id) WHERE deleted_at IS NULL; COMMENT ON TABLE camera_models IS 'Camera models - grouping level in Goergens system'; COMMENT ON COLUMN camera_models.goergens_model_number IS 'Model number from Goergens ID (e.g., 1910 for Bob IV)'; -- ========================================== -- CAMERA MODEL I18N -- ========================================== CREATE TABLE camera_model_i18n ( camera_model_id INT PRIMARY KEY REFERENCES camera_models(id) ON DELETE CASCADE, model_name VARCHAR(200), description_de TEXT, description_en TEXT, marketing_name_de VARCHAR(200), marketing_name_en VARCHAR(200), -- Full-text search vectors search_vector_de TSVECTOR GENERATED ALWAYS AS ( to_tsvector('german', COALESCE(model_name, '') || ' ' || COALESCE(description_de, '')) ) STORED, search_vector_en TSVECTOR GENERATED ALWAYS AS ( to_tsvector('english', COALESCE(model_name, '') || ' ' || COALESCE(description_en, '')) ) STORED ); CREATE INDEX idx_i18n_fts_de ON camera_model_i18n USING GIN(search_vector_de); CREATE INDEX idx_i18n_fts_en ON camera_model_i18n USING GIN(search_vector_en); COMMENT ON TABLE camera_model_i18n IS 'Multilingual content for camera models'; -- ========================================== -- HOUSING VARIANTS -- ========================================== CREATE TABLE housing_variants ( id SERIAL PRIMARY KEY, camera_model_id INT NOT NULL REFERENCES camera_models(id) ON DELETE CASCADE, goergens_variant_letter VARCHAR(2) NOT NULL, color VARCHAR(50), body_material VARCHAR(50), distinguishing_features TEXT, -- Audit created_at TIMESTAMP NOT NULL DEFAULT NOW(), created_by INT, updated_at TIMESTAMP, updated_by INT, version INT DEFAULT 1, deleted_at TIMESTAMP, UNIQUE(camera_model_id, goergens_variant_letter), CONSTRAINT chk_variant_letter CHECK (goergens_variant_letter ~ '^[a-z]{1,2}$') ); CREATE INDEX idx_variants_model ON housing_variants(camera_model_id); CREATE INDEX idx_variants_active ON housing_variants(id) WHERE deleted_at IS NULL; COMMENT ON TABLE housing_variants IS 'Housing variants - color, material variations'; COMMENT ON COLUMN housing_variants.goergens_variant_letter IS 'Variant letter from Goergens ID (a, b, c, etc.)'; -- ========================================== -- LENS/SHUTTER COMBINATIONS -- ========================================== CREATE TABLE lens_shutter_combos ( id SERIAL PRIMARY KEY, housing_variant_id INT NOT NULL REFERENCES housing_variants(id) ON DELETE CASCADE, goergens_combo_number VARCHAR(3) NOT NULL, lens_id INT, shutter_id INT, -- DENORMALIZED FIELDS FOR PERFORMANCE manufacturer_id INT, camera_model_id INT, goergens_full_id VARCHAR(50), display_name TEXT, -- STRUCTURED TECHNICAL SPECS weight_g INT, width_mm DECIMAL(6,2), height_mm DECIMAL(6,2), depth_mm DECIMAL(6,2), -- FLEXIBLE SPECS (JSONB) specs JSONB, -- Full-text search search_text TEXT, -- Audit created_at TIMESTAMP NOT NULL DEFAULT NOW(), created_by INT, updated_at TIMESTAMP, updated_by INT, version INT DEFAULT 1, deleted_at TIMESTAMP, UNIQUE(housing_variant_id, goergens_combo_number), CONSTRAINT chk_weight CHECK (weight_g IS NULL OR (weight_g > 0 AND weight_g < 50000)), CONSTRAINT chk_dimensions CHECK ( width_mm IS NULL OR height_mm IS NULL OR depth_mm IS NULL OR (width_mm > 0 AND height_mm > 0 AND depth_mm > 0) ) ); -- Critical indexes CREATE INDEX idx_combos_variant ON lens_shutter_combos(housing_variant_id); CREATE INDEX idx_combos_lens ON lens_shutter_combos(lens_id); CREATE INDEX idx_combos_shutter ON lens_shutter_combos(shutter_id); -- Denormalized indexes (performance boost) CREATE INDEX idx_combos_manufacturer ON lens_shutter_combos(manufacturer_id); CREATE INDEX idx_combos_model ON lens_shutter_combos(camera_model_id); CREATE INDEX idx_combos_goergens_id ON lens_shutter_combos(goergens_full_id); -- JSONB and full-text search CREATE INDEX idx_combos_specs ON lens_shutter_combos USING GIN(specs); CREATE INDEX idx_combos_fts ON lens_shutter_combos USING GIN(to_tsvector('german', COALESCE(search_text, ''))); CREATE INDEX idx_combos_active ON lens_shutter_combos(id) WHERE deleted_at IS NULL; COMMENT ON TABLE lens_shutter_combos IS 'Lens/shutter combinations - lowest level of hierarchy'; COMMENT ON COLUMN lens_shutter_combos.goergens_full_id IS 'Complete Goergens ID (e.g., ERNM K 1910 a01)'; COMMENT ON COLUMN lens_shutter_combos.specs IS 'Flexible technical specs as JSON (body_material, bellows_color, etc.)'; -- ========================================== -- CAMERA RELATIONSHIPS -- ========================================== CREATE TABLE camera_relations ( id SERIAL PRIMARY KEY, source_combo_id INT NOT NULL REFERENCES lens_shutter_combos(id) ON DELETE CASCADE, related_combo_id INT NOT NULL REFERENCES lens_shutter_combos(id) ON DELETE CASCADE, relationship_type VARCHAR(50) NOT NULL, notes TEXT, created_at TIMESTAMP NOT NULL DEFAULT NOW(), created_by INT, UNIQUE(source_combo_id, related_combo_id, relationship_type), CONSTRAINT chk_not_self CHECK (source_combo_id != related_combo_id), CONSTRAINT chk_relationship_type CHECK (relationship_type IN ('successor', 'predecessor', 'similar', 'variant_of', 'replaced_by')) ); CREATE INDEX idx_relations_source ON camera_relations(source_combo_id); CREATE INDEX idx_relations_target ON camera_relations(related_combo_id); CREATE INDEX idx_relations_type ON camera_relations(relationship_type); COMMENT ON TABLE camera_relations IS 'Relationships between cameras (evolution, variants, etc.)';