fix: Fix imports and database bugs. Removed legacy ETL code.

This commit is contained in:
Eric Gullickson
2025-12-27 12:07:24 -06:00
parent 0d9edbe761
commit bfb0c23ae1
30 changed files with 239174 additions and 4441 deletions

View File

@@ -1,139 +1,293 @@
-- Create dedicated schema for normalized vehicle lookup data
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_namespace WHERE nspname = 'vehicles'
) THEN
EXECUTE 'CREATE SCHEMA vehicles';
END IF;
END;
$$;
-- Migration: Create Automotive Vehicle Selection Database
-- Optimized for dropdown cascade queries
-- Date: 2025-11-10
-- Create manufacturers table
CREATE TABLE IF NOT EXISTS vehicles.make (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(150) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
-- Drop existing tables if they exist
DROP TABLE IF EXISTS vehicle_options CASCADE;
DROP TABLE IF EXISTS engines CASCADE;
DROP TABLE IF EXISTS transmissions CASCADE;
DROP INDEX IF EXISTS idx_vehicle_year;
DROP INDEX IF EXISTS idx_vehicle_make;
DROP INDEX IF EXISTS idx_vehicle_model;
DROP INDEX IF EXISTS idx_vehicle_trim;
DROP INDEX IF EXISTS idx_vehicle_composite;
-- Create engines table with detailed specifications
CREATE TABLE engines (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
displacement VARCHAR(50),
configuration VARCHAR(50),
horsepower VARCHAR(100),
torque VARCHAR(100),
fuel_type VARCHAR(100),
fuel_system VARCHAR(255),
aspiration VARCHAR(100),
specs_json JSONB,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Prevent duplicate engine display names (case-insensitive)
CREATE UNIQUE INDEX IF NOT EXISTS uq_engines_name_lower ON engines (LOWER(name));
CREATE INDEX idx_engines_displacement ON engines(displacement);
CREATE INDEX idx_engines_config ON engines(configuration);
-- Create transmissions table
CREATE TABLE transmissions (
id SERIAL PRIMARY KEY,
type VARCHAR(100) NOT NULL,
speeds VARCHAR(50),
drive_type VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Prevent duplicate transmission display names (case-insensitive)
CREATE UNIQUE INDEX IF NOT EXISTS uq_transmissions_type_lower ON transmissions (LOWER(type));
CREATE INDEX idx_transmissions_type ON transmissions(type);
-- Create denormalized vehicle_options table optimized for dropdown queries
CREATE TABLE vehicle_options (
id SERIAL PRIMARY KEY,
year INTEGER NOT NULL,
make VARCHAR(100) NOT NULL,
model VARCHAR(255) NOT NULL,
trim VARCHAR(255) NOT NULL,
engine_id INTEGER REFERENCES engines(id) ON DELETE SET NULL,
transmission_id INTEGER REFERENCES transmissions(id) ON DELETE SET NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Prevent duplicate vehicle option rows
CREATE UNIQUE INDEX IF NOT EXISTS uq_vehicle_options_full ON vehicle_options (
year, make, model, trim, engine_id, transmission_id
);
CREATE OR REPLACE FUNCTION vehicles.touch_updated_at()
RETURNS TRIGGER AS $$
-- Indexes for cascading dropdown performance
CREATE INDEX idx_vehicle_year ON vehicle_options(year);
CREATE INDEX idx_vehicle_make ON vehicle_options(make);
CREATE INDEX idx_vehicle_model ON vehicle_options(model);
CREATE INDEX idx_vehicle_trim ON vehicle_options(trim);
CREATE INDEX idx_vehicle_year_make ON vehicle_options(year, make);
CREATE INDEX idx_vehicle_year_make_model ON vehicle_options(year, make, model);
CREATE INDEX idx_vehicle_year_make_model_trim ON vehicle_options(year, make, model, trim);
CREATE INDEX idx_vehicle_year_make_model_trim_engine ON vehicle_options(year, make, model, trim, engine_id);
CREATE INDEX idx_vehicle_year_make_model_trim_trans ON vehicle_options(year, make, model, trim, transmission_id);
-- Full-text search index for admin catalog search
CREATE INDEX idx_vehicle_options_fts ON vehicle_options
USING gin(to_tsvector('english', year::text || ' ' || make || ' ' || model || ' ' || trim));
-- Index on engines.name for join performance during search
CREATE INDEX idx_engines_name ON engines(name);
-- Views for dropdown queries
-- View: Get all available years
CREATE OR REPLACE VIEW available_years AS
SELECT DISTINCT year
FROM vehicle_options
ORDER BY year DESC;
-- View: Get makes by year
CREATE OR REPLACE VIEW makes_by_year AS
SELECT DISTINCT year, make
FROM vehicle_options
ORDER BY year DESC, make ASC;
-- View: Get models by year and make
CREATE OR REPLACE VIEW models_by_year_make AS
SELECT DISTINCT year, make, model
FROM vehicle_options
ORDER BY year DESC, make ASC, model ASC;
-- View: Get trims by year, make, and model
CREATE OR REPLACE VIEW trims_by_year_make_model AS
SELECT DISTINCT year, make, model, trim
FROM vehicle_options
ORDER BY year DESC, make ASC, model ASC, trim ASC;
-- View: Get complete vehicle configurations with engine and transmission details
CREATE OR REPLACE VIEW complete_vehicle_configs AS
SELECT
vo.id,
vo.year,
vo.make,
vo.model,
vo.trim,
e.name AS engine_name,
e.displacement,
e.configuration,
e.horsepower,
e.torque,
e.fuel_type,
t.type AS transmission_type,
t.speeds AS transmission_speeds,
t.drive_type
FROM vehicle_options vo
LEFT JOIN engines e ON vo.engine_id = e.id
LEFT JOIN transmissions t ON vo.transmission_id = t.id
ORDER BY vo.year DESC, vo.make ASC, vo.model ASC, vo.trim ASC;
-- Function to get makes for a specific year
CREATE OR REPLACE FUNCTION get_makes_for_year(p_year INTEGER)
RETURNS TABLE(make VARCHAR) AS $$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
RETURN NEW;
RETURN QUERY
SELECT DISTINCT vehicle_options.make
FROM vehicle_options
WHERE vehicle_options.year = p_year
ORDER BY vehicle_options.make ASC;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS touch_make_updated_at ON vehicles.make;
CREATE TRIGGER touch_make_updated_at
BEFORE UPDATE ON vehicles.make
FOR EACH ROW
EXECUTE FUNCTION vehicles.touch_updated_at();
-- Function to get models for a specific year and make
CREATE OR REPLACE FUNCTION get_models_for_year_make(p_year INTEGER, p_make VARCHAR)
RETURNS TABLE(model VARCHAR) AS $$
BEGIN
RETURN QUERY
SELECT DISTINCT vehicle_options.model
FROM vehicle_options
WHERE vehicle_options.year = p_year
AND vehicle_options.make = p_make
ORDER BY vehicle_options.model ASC;
END;
$$ LANGUAGE plpgsql;
-- Create models table
CREATE TABLE IF NOT EXISTS vehicles.model (
id BIGSERIAL PRIMARY KEY,
make_id BIGINT NOT NULL REFERENCES vehicles.make(id) ON DELETE CASCADE,
name VARCHAR(150) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT vehicles_model_unique UNIQUE(make_id, name)
);
-- Function to get trims for a specific year, make, and model
CREATE OR REPLACE FUNCTION get_trims_for_year_make_model(p_year INTEGER, p_make VARCHAR, p_model VARCHAR)
RETURNS TABLE(trim_name VARCHAR) AS $$
BEGIN
RETURN QUERY
SELECT DISTINCT vehicle_options.trim
FROM vehicle_options
WHERE vehicle_options.year = p_year
AND vehicle_options.make = p_make
AND vehicle_options.model = p_model
ORDER BY vehicle_options.trim ASC;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS touch_model_updated_at ON vehicles.model;
CREATE TRIGGER touch_model_updated_at
BEFORE UPDATE ON vehicles.model
FOR EACH ROW
EXECUTE FUNCTION vehicles.touch_updated_at();
-- Function to get engine and transmission options for a specific vehicle
CREATE OR REPLACE FUNCTION get_options_for_vehicle(p_year INTEGER, p_make VARCHAR, p_model VARCHAR, p_trim VARCHAR)
RETURNS TABLE(
engine_name VARCHAR,
engine_displacement VARCHAR,
engine_horsepower VARCHAR,
transmission_type VARCHAR,
transmission_speeds VARCHAR,
drive_type VARCHAR
) AS $$
BEGIN
RETURN QUERY
SELECT
e.name,
e.displacement,
e.horsepower,
t.type,
t.speeds,
t.drive_type
FROM vehicle_options vo
LEFT JOIN engines e ON vo.engine_id = e.id
LEFT JOIN transmissions t ON vo.transmission_id = t.id
WHERE vo.year = p_year
AND vo.make = p_make
AND vo.model = p_model
AND vo.trim = p_trim;
END;
$$ LANGUAGE plpgsql;
-- Create model_year table
CREATE TABLE IF NOT EXISTS vehicles.model_year (
id BIGSERIAL PRIMARY KEY,
model_id BIGINT NOT NULL REFERENCES vehicles.model(id) ON DELETE CASCADE,
year INTEGER NOT NULL CHECK (year BETWEEN 1900 AND 2100),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT vehicles_model_year_unique UNIQUE(model_id, year)
);
-- Helper functions for trim-level options and pair-safe filtering
CREATE OR REPLACE FUNCTION get_transmissions_for_vehicle(p_year INTEGER, p_make VARCHAR, p_model VARCHAR, p_trim VARCHAR)
RETURNS TABLE(
transmission_id INTEGER,
transmission_type VARCHAR
) AS $$
BEGIN
RETURN QUERY
SELECT DISTINCT
t.id,
t.type
FROM vehicle_options vo
JOIN transmissions t ON vo.transmission_id = t.id
WHERE vo.year = p_year
AND vo.make = p_make
AND vo.model = p_model
AND vo.trim = p_trim
ORDER BY t.type ASC;
END;
$$ LANGUAGE plpgsql;
CREATE INDEX IF NOT EXISTS idx_model_year_year ON vehicles.model_year(year DESC);
CREATE OR REPLACE FUNCTION get_engines_for_vehicle(p_year INTEGER, p_make VARCHAR, p_model VARCHAR, p_trim VARCHAR)
RETURNS TABLE(
engine_id INTEGER,
engine_name VARCHAR
) AS $$
BEGIN
RETURN QUERY
SELECT DISTINCT
e.id,
e.name
FROM vehicle_options vo
JOIN engines e ON vo.engine_id = e.id
WHERE vo.year = p_year
AND vo.make = p_make
AND vo.model = p_model
AND vo.trim = p_trim
ORDER BY e.name ASC;
END;
$$ LANGUAGE plpgsql;
DROP TRIGGER IF EXISTS touch_model_year_updated_at ON vehicles.model_year;
CREATE TRIGGER touch_model_year_updated_at
BEFORE UPDATE ON vehicles.model_year
FOR EACH ROW
EXECUTE FUNCTION vehicles.touch_updated_at();
CREATE OR REPLACE FUNCTION get_transmissions_for_vehicle_engine(p_year INTEGER, p_make VARCHAR, p_model VARCHAR, p_trim VARCHAR, p_engine_name VARCHAR)
RETURNS TABLE(
transmission_id INTEGER,
transmission_type VARCHAR
) AS $$
BEGIN
RETURN QUERY
SELECT DISTINCT
t.id,
t.type
FROM vehicle_options vo
JOIN engines e ON vo.engine_id = e.id
JOIN transmissions t ON vo.transmission_id = t.id
WHERE vo.year = p_year
AND vo.make = p_make
AND vo.model = p_model
AND vo.trim = p_trim
AND e.name = p_engine_name
ORDER BY t.type ASC;
END;
$$ LANGUAGE plpgsql;
-- Create trims table
CREATE TABLE IF NOT EXISTS vehicles.trim (
id BIGSERIAL PRIMARY KEY,
model_year_id BIGINT NOT NULL REFERENCES vehicles.model_year(id) ON DELETE CASCADE,
name VARCHAR(150) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT vehicles_trim_unique UNIQUE(model_year_id, name)
);
CREATE OR REPLACE FUNCTION get_engines_for_vehicle_trans(p_year INTEGER, p_make VARCHAR, p_model VARCHAR, p_trim VARCHAR, p_trans_type VARCHAR)
RETURNS TABLE(
engine_id INTEGER,
engine_name VARCHAR
) AS $$
BEGIN
RETURN QUERY
SELECT DISTINCT
e.id,
e.name
FROM vehicle_options vo
JOIN engines e ON vo.engine_id = e.id
JOIN transmissions t ON vo.transmission_id = t.id
WHERE vo.year = p_year
AND vo.make = p_make
AND vo.model = p_model
AND vo.trim = p_trim
AND t.type = p_trans_type
ORDER BY e.name ASC;
END;
$$ LANGUAGE plpgsql;
CREATE INDEX IF NOT EXISTS idx_trim_model_year ON vehicles.trim(model_year_id);
DROP TRIGGER IF EXISTS touch_trim_updated_at ON vehicles.trim;
CREATE TRIGGER touch_trim_updated_at
BEFORE UPDATE ON vehicles.trim
FOR EACH ROW
EXECUTE FUNCTION vehicles.touch_updated_at();
-- Create engines table
CREATE TABLE IF NOT EXISTS vehicles.engine (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(200) NOT NULL UNIQUE,
code VARCHAR(50),
displacement_l NUMERIC(5,2),
cylinders SMALLINT,
fuel_type VARCHAR(50),
aspiration VARCHAR(50),
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
DROP TRIGGER IF EXISTS touch_engine_updated_at ON vehicles.engine;
CREATE TRIGGER touch_engine_updated_at
BEFORE UPDATE ON vehicles.engine
FOR EACH ROW
EXECUTE FUNCTION vehicles.touch_updated_at();
-- Create trim-engine bridge table
CREATE TABLE IF NOT EXISTS vehicles.trim_engine (
trim_id BIGINT NOT NULL REFERENCES vehicles.trim(id) ON DELETE CASCADE,
engine_id BIGINT NOT NULL REFERENCES vehicles.engine(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (trim_id, engine_id)
);
-- Create transmissions table (static manual/automatic for now)
CREATE TABLE IF NOT EXISTS vehicles.transmission (
id BIGSERIAL PRIMARY KEY,
name VARCHAR(50) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
);
DROP TRIGGER IF EXISTS touch_transmission_updated_at ON vehicles.transmission;
CREATE TRIGGER touch_transmission_updated_at
BEFORE UPDATE ON vehicles.transmission
FOR EACH ROW
EXECUTE FUNCTION vehicles.touch_updated_at();
-- Optional bridge for future proofing (not yet populated)
CREATE TABLE IF NOT EXISTS vehicles.trim_transmission (
trim_id BIGINT NOT NULL REFERENCES vehicles.trim(id) ON DELETE CASCADE,
transmission_id BIGINT NOT NULL REFERENCES vehicles.transmission(id) ON DELETE CASCADE,
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (trim_id, transmission_id)
);
-- Helpful indexes for cascading dropdown lookups
CREATE INDEX IF NOT EXISTS idx_model_make ON vehicles.model(make_id);
CREATE INDEX IF NOT EXISTS idx_trim_name ON vehicles.trim(LOWER(name));
CREATE INDEX IF NOT EXISTS idx_engine_name ON vehicles.engine(LOWER(name));
CREATE INDEX IF NOT EXISTS idx_trim_engine_engine ON vehicles.trim_engine(engine_id);
COMMENT ON TABLE vehicle_options IS 'Denormalized table optimized for cascading dropdown queries';
COMMENT ON TABLE engines IS 'Engine specifications with detailed technical data';
COMMENT ON TABLE transmissions IS 'Transmission specifications';
COMMENT ON VIEW available_years IS 'Returns all distinct years available in the database';
COMMENT ON VIEW makes_by_year IS 'Returns makes grouped by year for dropdown population';
COMMENT ON VIEW models_by_year_make IS 'Returns models grouped by year and make';
COMMENT ON VIEW trims_by_year_make_model IS 'Returns trims grouped by year, make, and model';
COMMENT ON VIEW complete_vehicle_configs IS 'Complete vehicle configurations with all details';