diff --git a/postgresql/migrations/v1_11-05-23_migrations_script.py b/postgresql/migrations/v1_11-05-23_migrations_script.py new file mode 100644 index 0000000..4592685 --- /dev/null +++ b/postgresql/migrations/v1_11-05-23_migrations_script.py @@ -0,0 +1,96 @@ + +import psycopg2 +import os +import logging +from contextlib import contextmanager + +# Configure logging +logging.basicConfig( + filename='migration.log', + filemode='a', + format='%(asctime)s - %(levelname)s - %(message)s', + level=logging.INFO +) + +# Database connection parameters +db_params = { + "dbname": os.getenv("DB_NAME"), + "user": os.getenv("DB_USER"), + "password": os.getenv("DB_PASSWORD"), + "host": os.getenv("DB_HOST"), + "port": os.getenv("DB_PORT") +} + +# Context manager for database connection +@contextmanager +def get_db_connection(): + conn = psycopg2.connect(**db_params) + try: + yield conn + finally: + conn.close() + +# Context manager for database cursor +@contextmanager +def get_db_cursor(commit=False): + with get_db_connection() as conn: + cursor = conn.cursor() + try: + yield cursor + if commit: + conn.commit() + except Exception as e: + conn.rollback() + logging.error(f"Transaction failed: {e}") + raise + finally: + cursor.close() + +# Function to check if a migration was applied +def is_migration_applied(cursor, migration_name): + cursor.execute("SELECT COUNT(*) FROM applied_migrations WHERE migration_name = %s", (migration_name,)) + return cursor.fetchone()[0] > 0 + +# Function to record a migration as applied +def record_migration(cursor, migration_name): + cursor.execute("INSERT INTO applied_migrations (migration_name) VALUES (%s)", (migration_name,)) + +# Function to apply a migration script +def apply_migration(cursor, filepath): + filename = os.path.basename(filepath) + if is_migration_applied(cursor, filename): + logging.info(f"Migration {filename} already applied.") + return + + logging.info(f"Applying migration {filename}") + with open(filepath, 'r') as file: + cursor.execute(file.read()) + record_migration(cursor, filename) + logging.info(f"Migration {filename} applied successfully.") + +# Main migration function +def migrate(): + # List of migration scripts + migration_scripts = ['path_to_your_first_migration_script.sql', 'path_to_your_next_migration_script.sql'] # etc. + + with get_db_cursor(commit=True) as cursor: + # Ensure the migrations tracking table exists + cursor.execute(""" + CREATE TABLE IF NOT EXISTS applied_migrations ( + migration_name TEXT PRIMARY KEY, + applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP + ); + """) + + # Apply each migration script + for script_path in migration_scripts: + apply_migration(cursor, script_path) + +def main(): + try: + migrate() + except Exception as e: + logging.error(f"Migration failed: {e}") + +if __name__ == '__main__': + main() diff --git a/postgresql/schemas/01_init_db.sh b/postgresql/schemas/01_init_db.sh index df05188..a3efcbc 100755 --- a/postgresql/schemas/01_init_db.sh +++ b/postgresql/schemas/01_init_db.sh @@ -1,44 +1,46 @@ #!/bin/bash -# This script initializes the PostgreSQL environment for your project. # Replace 'versus_rex' with your desired database name, # 'oedypus' with your desired username, and 'Zarathustra22!' with your desired password. # Exit immediately if a command exits with a non-zero status. set -e -# Replace 'versus_rex' with the name of the database you want to create. +# Database name and user credentials DATABASE_NAME="versus_rex" - -# Replace 'myusername' with the username you want to create. USERNAME="oedypus" - -# Replace 'mypassword' with the password for the new user. PASSWORD="Zarathustra22!" # Create a new PostgreSQL role. echo "Creating role..." -sudo -u postgres psql -c "CREATE ROLE $USERNAME WITH LOGIN PASSWORD '$PASSWORD';" +sudo -u postgres createuser --login --password "$PASSWORD" "$USERNAME" # Alter role to set superuser or necessary privileges. -echo "Altering role..." +echo "Altering role to superuser and allowing database creation..." sudo -u postgres psql -c "ALTER ROLE $USERNAME SUPERUSER CREATEDB;" # Create a new database with the new role as the owner. echo "Creating database..." -sudo -u postgres psql -c "CREATE DATABASE $DATABASE_NAME WITH OWNER $USERNAME;" +sudo -u postgres createdb --owner="$USERNAME" "$DATABASE_NAME" # Grant all privileges of the new database to the new role. echo "Granting privileges to user on database..." sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE $DATABASE_NAME TO $USERNAME;" # Optionally, set the default schema search path for the new role. +# Replace 'my_schema' with the schema you will use. echo "Setting schema search path..." -sudo -u postgres psql -c "ALTER ROLE $USERNAME SET search_path TO my_schema, public;" - -# Ensure the new role does not have more privileges than necessary. -# If the role should not be a superuser, comment out or remove the ALTER ROLE line above -# and uncomment and customize the line below according to your needs. -# sudo -u postgres psql -c "ALTER ROLE $USERNAME NOSUPERUSER INHERIT NOCREATEDB NOCREATEROLE NOREPLICATION;" +sudo -u postgres psql -d "$DATABASE_NAME" -c "ALTER ROLE $USERNAME SET search_path TO my_schema, public;" + +# Create the applied_migrations table to track migration history +echo "Creating applied_migrations table to track migrations..." +sudo -u postgres psql -d "$DATABASE_NAME" -c " +BEGIN; +CREATE TABLE IF NOT EXISTS applied_migrations ( + migration_name TEXT PRIMARY KEY, + applied_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP +); +COMMIT; +" echo "PostgreSQL environment initialization complete." diff --git a/postgresql/schemas/core_logic_template_11-05-23_03_create_tables.sql b/postgresql/schemas/core_logic_template_11-05-23_03_create_tables.sql new file mode 100644 index 0000000..067cb4d --- /dev/null +++ b/postgresql/schemas/core_logic_template_11-05-23_03_create_tables.sql @@ -0,0 +1,73 @@ +BEGIN; + +-- Creating the 'emails' table within the 'email_schema'. +CREATE TABLE IF NOT EXISTS email_schema.emails ( + email_id BIGSERIAL PRIMARY KEY, + subject TEXT, + sender VARCHAR(100) CHECK (sender ~* '^[^@]+@[^@]+\.[^@]+$'), -- Regex for basic email validation + recipient VARCHAR(100) CHECK (recipient ~* '^[^@]+@[^@]+\.[^@]+$'), + sent_time TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(), -- Default to the current timestamp + body_text TEXT, + ip_address INET, + x_originating_ip INET, + received TEXT, + user_agent VARCHAR(255), + authentication_results TEXT, + dkim_signature TEXT, + sender_user_id INT, + recipient_user_id INT +); + +-- Adding foreign key constraints to the 'emails' table. +ALTER TABLE email_schema.emails + ADD CONSTRAINT fk_emails_sender_user_id + FOREIGN KEY (sender_user_id) REFERENCES email_schema.users(user_id) + ON DELETE SET NULL, + ADD CONSTRAINT fk_emails_recipient_user_id + FOREIGN KEY (recipient_user_id) REFERENCES email_schema.users(user_id) + ON DELETE SET NULL; + +-- Creating the 'attachments' table within the 'email_schema'. +-- Note: We are ensuring that there is NO CASCADE on delete. +CREATE TABLE IF NOT EXISTS email_schema.attachments ( + attachment_id BIGSERIAL PRIMARY KEY, + email_id BIGINT NOT NULL, + file_name TEXT NOT NULL, + file_type TEXT NOT NULL, -- assuming every attachment must have a file type + file_size BIGINT NOT NULL CHECK (file_size >= 0), -- assuming file size must be non-negative + content BYTEA -- consider storing only a reference to the file location if they're large +); + +-- Foreign key constraint for attachments referencing emails without cascading deletes. +ALTER TABLE email_schema.attachments + ADD CONSTRAINT fk_attachments_email_id + FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) + ON DELETE RESTRICT; + +-- Creating the 'email_attachment_mapping' table for a many-to-many relationship between emails and attachments. +-- Note: We are ensuring that there is NO CASCADE on delete. +CREATE TABLE IF NOT EXISTS email_schema.email_attachment_mapping ( + email_id BIGINT NOT NULL, + attachment_id BIGINT NOT NULL, + PRIMARY KEY (email_id, attachment_id), + CONSTRAINT fk_email_attachment_mapping_email_id FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) ON DELETE RESTRICT, + CONSTRAINT fk_email_attachment_mapping_attachment_id FOREIGN KEY (attachment_id) REFERENCES email_schema.attachments(attachment_id) ON DELETE RESTRICT +); + +-- Creating the 'email_status' table to track the state of emails. +-- Note: We are ensuring that there is NO CASCADE on delete. +CREATE TABLE IF NOT EXISTS email_schema.email_status ( + status_id SERIAL PRIMARY KEY, + email_id BIGINT NOT NULL, + status TEXT NOT NULL CHECK (status IN ('sent', 'received', 'read', 'error', 'pending')), -- Add more statuses as needed + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +-- Foreign key constraint for email_status referencing emails without cascading deletes. +ALTER TABLE email_schema.email_status + ADD CONSTRAINT fk_email_status_email_id + FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) + ON DELETE RESTRICT; + +-- Commit the changes if all operations are successful. +COMMIT; diff --git a/postgresql/schemas/v1_11-05-23_03_create_tables.sql b/postgresql/schemas/v1_11-05-23_03_create_tables.sql new file mode 100644 index 0000000..1ad7ac3 --- /dev/null +++ b/postgresql/schemas/v1_11-05-23_03_create_tables.sql @@ -0,0 +1,139 @@ +BEGIN; + +-- Creating the 'emails' table within the 'email_schema' to store email information. +CREATE TABLE IF NOT EXISTS email_schema.emails ( + email_id BIGSERIAL PRIMARY KEY, -- A unique identifier for each email, BIGSERIAL for large number of records. + subject TEXT, -- The subject of the email. + sender VARCHAR(100), -- The email address of the sender, assuming an email address won't exceed 100 characters. + recipient VARCHAR(100), -- The email address of the recipient, same assumption as sender. + sent_time TIMESTAMP WITHOUT TIME ZONE, -- The time the email was sent. Assuming timezone is handled application-side. + body_text TEXT, -- The body of the email. + ip_address INET, -- The IP address from which the email was sent, using INET type for proper IP storage. + x_originating_ip INET, -- The originating IP address if provided, also INET type. + received TEXT, -- To store the full 'Received' header chain. + user_agent VARCHAR(255), -- The user agent information if available. + authentication_results TEXT, -- To store 'Authentication-Results' header information. + dkim_signature TEXT -- To store 'DKIM-Signature' header information. +); + +-- Indexes on the 'emails' table to improve query performance. +CREATE INDEX IF NOT EXISTS idx_emails_sender ON email_schema.emails(sender); +CREATE INDEX IF NOT EXISTS idx_emails_recipient ON email_schema.emails(recipient); +-- Additional indexes omitted for brevity, but should be reviewed for actual query patterns. + + +- Creating the 'attachments' table within the 'email_schema'. +CREATE TABLE IF NOT EXISTS email_schema.attachments ( + attachment_id BIGSERIAL PRIMARY KEY, -- A unique identifier for each attachment, BIGSERIAL for large datasets. + email_id BIGINT NOT NULL REFERENCES email_schema.emails(email_id) ON DELETE CASCADE, -- Using BIGINT to match the email_id type. + file_name TEXT NOT NULL, -- The file name of the attachment. + file_type TEXT, -- The MIME type of the attachment. + file_size BIGINT, -- The size of the attachment file in bytes, BIGINT to handle large files. + content BYTEA -- To store the actual content of the attachment if needed. +); + +-- Indexes on the 'attachments' table to improve query performance. +CREATE INDEX IF NOT EXISTS idx_attachments_email_id ON email_schema.attachments(email_id); +CREATE INDEX IF NOT EXISTS idx_attachments_file_name ON email_schema.attachments(file_name); +-- Additional indexes omitted for brevity. + +-- Creating the 'email_attachment_mapping' table if a many-to-many relationship is needed. +-- Assuming from previous parts of the conversation that this might be needed. +CREATE TABLE IF NOT EXISTS email_schema.email_attachment_mapping ( + email_id BIGINT, + attachment_id BIGINT, + PRIMARY KEY (email_id, attachment_id), + FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) ON DELETE CASCADE, + FOREIGN KEY (attachment_id) REFERENCES email_schema.attachments(attachment_id) ON DELETE CASCADE +); + +-- Indexes for the 'email_attachment_mapping' table to improve query performance. +CREATE INDEX IF NOT EXISTS idx_email_attachment_mapping_email_id ON email_schema.email_attachment_mapping(email_id); +CREATE INDEX IF NOT EXISTS idx_email_attachment_mapping_attachment_id ON email_schema.email_attachment_mapping(attachment_id); + +-- Creating the 'email_status' table to track the state of emails. +CREATE TABLE IF NOT EXISTS email_schema.email_status ( + status_id SERIAL PRIMARY KEY, -- A unique identifier for each status. + email_id BIGINT NOT NULL REFERENCES email_schema.emails(email_id) ON DELETE CASCADE, + status TEXT NOT NULL, -- The status of the email (e.g., 'sent', 'received', 'read'). + updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -- Timestamp of the status update. +); + +-- Index on the 'email_status' table to improve query performance. +CREATE INDEX IF NOT EXISTS idx_email_status_email_id ON email_schema.email_status(email_id); +CREATE INDEX IF NOT EXISTS idx_email_status_status ON email_schema.email_status(status); + +-- Creating the 'users' table if user management is part of the scope. +CREATE TABLE IF NOT EXISTS email_schema.users ( + user_id SERIAL PRIMARY KEY, -- A unique identifier for each user. + email VARCHAR(255) UNIQUE NOT NULL, -- The user's email address, unique to ensure no duplicates. + created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP -- Timestamp of the user creation. +); + +-- Index on the 'users' table to improve query performance. +CREATE INDEX IF NOT EXISTS idx_users_email ON email_schema.users(email); + +-- Altering the 'emails' table to link with the 'users' table, assuming this association is required. +ALTER TABLE email_schema.emails +ADD COLUMN sender_user_id INT REFERENCES email_schema.users(user_id), +ADD COLUMN recipient_user_id INT REFERENCES email_schema.users(user_id); + +-- Update the index after adding the new columns. +DROP INDEX IF EXISTS email_schema.idx_emails_sender; +DROP INDEX IF EXISTS email_schema.idx_emails_recipient; +CREATE INDEX idx_emails_sender_user_id ON email_schema.emails(sender_user_id); +CREATE INDEX idx_emails_recipient_user_id ON email_schema.emails(recipient_user_id); + +-- Assuming a logging or audit mechanism is desired. +-- Creating a table to log actions or changes in the database. +CREATE TABLE IF NOT EXISTS email_schema.audit_log ( + log_id SERIAL PRIMARY KEY, -- A unique identifier for each log entry. + action TEXT NOT NULL, -- The action performed (e.g., 'CREATE', 'UPDATE', 'DELETE'). + performed_by INT REFERENCES email_schema.users(user_id), -- The user who performed the action. + performed_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, -- Timestamp of the action. + detail TEXT -- Details of the action performed. +); + +-- Index on the 'audit_log' table to improve query performance. +CREATE INDEX IF NOT EXISTS idx_audit_log_performed_by ON email_schema.audit_log(performed_by); +CREATE INDEX IF NOT EXISTS idx_audit_log_performed_at ON email_schema.audit_log(performed_at); + +-- Adding a function to track changes for the audit log, assuming this is required. +CREATE OR REPLACE FUNCTION email_schema.audit_log_trigger() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO email_schema.audit_log(action, performed_by, detail) + VALUES (TG_OP, NEW.sender_user_id, 'Email ' || TG_OP || ' with ID ' || NEW.email_id); + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + +-- Creating a trigger for the 'emails' table to automatically log inserts/updates/deletes +CREATE TRIGGER emails_audit_trigger +AFTER INSERT OR UPDATE OR DELETE ON email_schema.emails +FOR EACH ROW EXECUTE FUNCTION email_schema.audit_log_trigger(); + +-- For instance, adding a routine to clean up old audit logs +CREATE OR REPLACE FUNCTION email_schema.cleanup_audit_logs() RETURNS VOID AS $$ +BEGIN + DELETE FROM email_schema.audit_log WHERE performed_at < NOW() - INTERVAL '90 days'; +END; +$$ LANGUAGE plpgsql; + +-- Schedule the cleanup function to run periodically, if required +-- This requires setting up a job scheduler like pgAgent or an external cron job + +-- Finalize the schema with any additional grants or role assignments +-- This is where you grant specific privileges to different database roles +-- Example: +GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA email_schema TO my_role; + +-- If there are any views, sequences or other schema objects, permissions for those should be set here + +-- Commit the transaction if everything above is successful +COMMIT; + +-- The script ends here. The database is now set up with tables, indices, functions, and triggers. +-- It's prepared for application integration and further development. + + diff --git a/postgresql/schemas/v1_refactoring_from_core_logic_template_11-05-23_03_create_tables.sql b/postgresql/schemas/v1_refactoring_from_core_logic_template_11-05-23_03_create_tables.sql new file mode 100644 index 0000000..83a0e04 --- /dev/null +++ b/postgresql/schemas/v1_refactoring_from_core_logic_template_11-05-23_03_create_tables.sql @@ -0,0 +1,264 @@ +BEGIN; + +-- Step 1: Create the error_log table +CREATE TABLE IF NOT EXISTS email_schema.error_log ( + error_id SERIAL PRIMARY KEY, + error_timestamp TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, + error_message TEXT +); + +-- Attempt to create the 'emails' table within the 'email_schema'. +DO $$ +BEGIN + CREATE TABLE IF NOT EXISTS email_schema.emails ( + email_id BIGSERIAL PRIMARY KEY, + subject TEXT, + sender VARCHAR(255) CHECK (sender ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'), -- More robust email validation regex + recipient VARCHAR(255) CHECK (recipient ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'), + sent_time TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(), -- Default to the current timestamp + body_text TEXT, + ip_address INET, + x_originating_ip INET, + received TEXT, + user_agent VARCHAR(255), + authentication_results TEXT, + dkim_signature TEXT, + sender_user_id INT, + recipient_user_id INT + -- phone_number VARCHAR(255) CHECK (phone_number ~* '^\(\d{3}\) \d{3}-\d{4}$') -- Example format: (123) 456-7890 + ); + + -- Index for sender_user_id foreign key + CREATE INDEX IF NOT EXISTS idx_emails_sender_user_id ON email_schema.emails(sender_user_id); + + -- Index for recipient_user_id foreign key + CREATE INDEX IF NOT EXISTS idx_emails_recipient_user_id ON email_schema.emails(recipient_user_id); + + -- Index for sent_time to quickly find emails by date + CREATE INDEX IF NOT EXISTS idx_emails_sent_time ON email_schema.emails(sent_time); + + -- Index for sender and recipient email address columns for quick searches + CREATE INDEX IF NOT EXISTS idx_emails_sender ON email_schema.emails(sender); + CREATE INDEX IF NOT EXISTS idx_emails_recipient ON email_schema.emails(recipient); + +EXCEPTION + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Attempted to create index on undefined table.'); + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Attempted to create index on undefined column.'); + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Attempted to create a duplicate index.'); + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while creating indexes: ' || SQLERRM); + RAISE; +END; +$$; + +DO $$ +BEGIN + -- Adding foreign key constraints to the 'emails' table. + ALTER TABLE email_schema.emails + ADD CONSTRAINT fk_emails_sender_user_id + FOREIGN KEY (sender_user_id) REFERENCES email_schema.users(user_id) + ON DELETE SET NULL, + ADD CONSTRAINT fk_emails_recipient_user_id + FOREIGN KEY (recipient_user_id) REFERENCES email_schema.users(user_id) + ON DELETE SET NULL; + + -- Add indexes on foreign key columns after adding constraints + CREATE INDEX IF NOT EXISTS idx_emails_sender_user_id ON email_schema.emails(sender_user_id); + CREATE INDEX IF NOT EXISTS idx_emails_recipient_user_id ON email_schema.emails(recipient_user_id); + +EXCEPTION + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table referenced in foreign key constraint or index creation.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column referenced in foreign key constraint or index creation.'); + RAISE; + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate foreign key constraint or index name.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while adding foreign key constraints or creating indexes: ' || SQLERRM); + RAISE; +END; +$$; + + + +DO $$ +BEGIN + -- Creating the 'attachments' table within the 'email_schema'. + CREATE TABLE IF NOT EXISTS email_schema.attachments ( + attachment_id BIGSERIAL PRIMARY KEY, + email_id BIGINT NOT NULL, + file_name TEXT NOT NULL, + file_type TEXT NOT NULL, -- assuming every attachment must have a file type + file_size BIGINT NOT NULL CHECK (file_size >= 0), -- assuming file size must be non-negative + content BYTEA -- consider storing only a reference to the file location if they're large + ); + + -- Adding foreign key constraint without CASCADE on delete + ALTER TABLE email_schema.attachments + ADD CONSTRAINT fk_attachments_email_id + FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) ON DELETE RESTRICT; + + -- Create an index on the email_id column to improve join performance + CREATE INDEX IF NOT EXISTS idx_attachments_email_id ON email_schema.attachments(email_id); + + -- Optional: Create additional indexes on columns used in search queries + CREATE INDEX IF NOT EXISTS idx_attachments_file_name ON email_schema.attachments(file_name); + CREATE INDEX IF NOT EXISTS idx_attachments_file_type ON email_schema.attachments(file_type); + +EXCEPTION + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table referenced in foreign key constraint or index creation for attachments.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column referenced in foreign key constraint or index creation for attachments.'); + RAISE; + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate foreign key constraint or index name for attachments.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while creating attachments table or creating indexes: ' || SQLERRM); + RAISE; +END; +$$; + +DO $$ +BEGIN + -- Adding foreign key constraint for attachments referencing emails without cascading deletes. + ALTER TABLE email_schema.attachments + ADD CONSTRAINT fk_attachments_email_id + FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) + ON DELETE RESTRICT; + + -- Create an index on the email_id column to improve join performance + CREATE INDEX IF NOT EXISTS idx_attachments_email_id ON email_schema.attachments(email_id); + +EXCEPTION + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table referenced in foreign key constraint or index creation.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column referenced in foreign key constraint or index creation.'); + RAISE; + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate foreign key constraint or index name.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while adding foreign key constraints or creating indexes: ' || SQLERRM); + RAISE; +END; +$$; + + + +DO $$ +BEGIN + -- Creating the 'email_attachment_mapping' table for a many-to-many relationship. + CREATE TABLE IF NOT EXISTS email_schema.email_attachment_mapping ( + email_id BIGINT NOT NULL, + attachment_id BIGINT NOT NULL, + PRIMARY KEY (email_id, attachment_id), + CONSTRAINT fk_email_attachment_mapping_email_id FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) ON DELETE RESTRICT, + CONSTRAINT fk_email_attachment_mapping_attachment_id FOREIGN KEY (attachment_id) REFERENCES email_schema.attachments(attachment_id) ON DELETE RESTRICT + ); + + -- Since email_id and attachment_id are part of a composite primary key, they are indexed. + -- Additional indexes can be created if there are frequent queries that filter or join on these columns independently. + + -- Create an index on the email_id column to improve performance for queries filtering by email_id + CREATE INDEX IF NOT EXISTS idx_email_attachment_mapping_email_id ON email_schema.email_attachment_mapping(email_id); + + -- Create an index on the attachment_id column to improve performance for queries filtering by attachment_id + CREATE INDEX IF NOT EXISTS idx_email_attachment_mapping_attachment_id ON email_schema.email_attachment_mapping(attachment_id); + +EXCEPTION + WHEN duplicate_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate table name: email_attachment_mapping.'); + RAISE; + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table referenced in foreign key constraint or index creation.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column referenced in foreign key constraint or index creation.'); + RAISE; + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate foreign key constraint or index name.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while creating email_attachment_mapping table or indexes: ' || SQLERRM); + RAISE; +END; +$$; + + + +DO $$ +BEGIN + -- Creating the 'email_status' table to track the state of emails. + CREATE TABLE IF NOT EXISTS email_schema.email_status ( + status_id SERIAL PRIMARY KEY, + email_id BIGINT NOT NULL, + status TEXT NOT NULL CHECK (status IN ('sent', 'received', 'read', 'error', 'pending')), -- Add more statuses as needed + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP + ); + + -- Index for email_id foreign key + CREATE INDEX IF NOT EXISTS idx_email_status_email_id ON email_schema.email_status(email_id); + + -- Index for status column for quick searches + CREATE INDEX IF NOT EXISTS idx_email_status_status ON email_schema.email_status(status); + + -- Index for updated_at to quickly find statuses by update date + CREATE INDEX IF NOT EXISTS idx_email_status_updated_at ON email_schema.email_status(updated_at); + +EXCEPTION + WHEN duplicate_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate table name: email_status.'); + RAISE; + WHEN check_violation THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Check constraint violation on table email_status.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while creating email_status table or indexes: ' || SQLERRM); + RAISE; +END; +$$; + + + +DO $$ +BEGIN + -- Foreign key constraint for email_status referencing emails without cascading deletes. + ALTER TABLE email_schema.email_status + ADD CONSTRAINT fk_email_status_email_id + FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) + ON DELETE RESTRICT; + + -- Explicitly create an index on the foreign key column, even though it's automatically indexed by PostgreSQL + CREATE INDEX IF NOT EXISTS idx_fk_email_status_email_id ON email_schema.email_status(email_id); + +EXCEPTION + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate constraint or index name: fk_email_status_email_id or idx_fk_email_status_email_id.'); + RAISE; + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table: email_status or emails.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column: email_id in table email_status or emails.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while adding foreign key constraint or creating index for email_status table: ' || SQLERRM); + RAISE; +END; +$$; + +-- Commit the changes if all operations are successful. +COMMIT; + + diff --git a/postgresql/schemas/v2(logging)_refactoring_from_core_logic_template_11-05-23_03_create_tables.sql b/postgresql/schemas/v2(logging)_refactoring_from_core_logic_template_11-05-23_03_create_tables.sql new file mode 100644 index 0000000..83a0e04 --- /dev/null +++ b/postgresql/schemas/v2(logging)_refactoring_from_core_logic_template_11-05-23_03_create_tables.sql @@ -0,0 +1,264 @@ +BEGIN; + +-- Step 1: Create the error_log table +CREATE TABLE IF NOT EXISTS email_schema.error_log ( + error_id SERIAL PRIMARY KEY, + error_timestamp TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, + error_message TEXT +); + +-- Attempt to create the 'emails' table within the 'email_schema'. +DO $$ +BEGIN + CREATE TABLE IF NOT EXISTS email_schema.emails ( + email_id BIGSERIAL PRIMARY KEY, + subject TEXT, + sender VARCHAR(255) CHECK (sender ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'), -- More robust email validation regex + recipient VARCHAR(255) CHECK (recipient ~* '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$'), + sent_time TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(), -- Default to the current timestamp + body_text TEXT, + ip_address INET, + x_originating_ip INET, + received TEXT, + user_agent VARCHAR(255), + authentication_results TEXT, + dkim_signature TEXT, + sender_user_id INT, + recipient_user_id INT + -- phone_number VARCHAR(255) CHECK (phone_number ~* '^\(\d{3}\) \d{3}-\d{4}$') -- Example format: (123) 456-7890 + ); + + -- Index for sender_user_id foreign key + CREATE INDEX IF NOT EXISTS idx_emails_sender_user_id ON email_schema.emails(sender_user_id); + + -- Index for recipient_user_id foreign key + CREATE INDEX IF NOT EXISTS idx_emails_recipient_user_id ON email_schema.emails(recipient_user_id); + + -- Index for sent_time to quickly find emails by date + CREATE INDEX IF NOT EXISTS idx_emails_sent_time ON email_schema.emails(sent_time); + + -- Index for sender and recipient email address columns for quick searches + CREATE INDEX IF NOT EXISTS idx_emails_sender ON email_schema.emails(sender); + CREATE INDEX IF NOT EXISTS idx_emails_recipient ON email_schema.emails(recipient); + +EXCEPTION + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Attempted to create index on undefined table.'); + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Attempted to create index on undefined column.'); + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Attempted to create a duplicate index.'); + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while creating indexes: ' || SQLERRM); + RAISE; +END; +$$; + +DO $$ +BEGIN + -- Adding foreign key constraints to the 'emails' table. + ALTER TABLE email_schema.emails + ADD CONSTRAINT fk_emails_sender_user_id + FOREIGN KEY (sender_user_id) REFERENCES email_schema.users(user_id) + ON DELETE SET NULL, + ADD CONSTRAINT fk_emails_recipient_user_id + FOREIGN KEY (recipient_user_id) REFERENCES email_schema.users(user_id) + ON DELETE SET NULL; + + -- Add indexes on foreign key columns after adding constraints + CREATE INDEX IF NOT EXISTS idx_emails_sender_user_id ON email_schema.emails(sender_user_id); + CREATE INDEX IF NOT EXISTS idx_emails_recipient_user_id ON email_schema.emails(recipient_user_id); + +EXCEPTION + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table referenced in foreign key constraint or index creation.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column referenced in foreign key constraint or index creation.'); + RAISE; + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate foreign key constraint or index name.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while adding foreign key constraints or creating indexes: ' || SQLERRM); + RAISE; +END; +$$; + + + +DO $$ +BEGIN + -- Creating the 'attachments' table within the 'email_schema'. + CREATE TABLE IF NOT EXISTS email_schema.attachments ( + attachment_id BIGSERIAL PRIMARY KEY, + email_id BIGINT NOT NULL, + file_name TEXT NOT NULL, + file_type TEXT NOT NULL, -- assuming every attachment must have a file type + file_size BIGINT NOT NULL CHECK (file_size >= 0), -- assuming file size must be non-negative + content BYTEA -- consider storing only a reference to the file location if they're large + ); + + -- Adding foreign key constraint without CASCADE on delete + ALTER TABLE email_schema.attachments + ADD CONSTRAINT fk_attachments_email_id + FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) ON DELETE RESTRICT; + + -- Create an index on the email_id column to improve join performance + CREATE INDEX IF NOT EXISTS idx_attachments_email_id ON email_schema.attachments(email_id); + + -- Optional: Create additional indexes on columns used in search queries + CREATE INDEX IF NOT EXISTS idx_attachments_file_name ON email_schema.attachments(file_name); + CREATE INDEX IF NOT EXISTS idx_attachments_file_type ON email_schema.attachments(file_type); + +EXCEPTION + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table referenced in foreign key constraint or index creation for attachments.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column referenced in foreign key constraint or index creation for attachments.'); + RAISE; + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate foreign key constraint or index name for attachments.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while creating attachments table or creating indexes: ' || SQLERRM); + RAISE; +END; +$$; + +DO $$ +BEGIN + -- Adding foreign key constraint for attachments referencing emails without cascading deletes. + ALTER TABLE email_schema.attachments + ADD CONSTRAINT fk_attachments_email_id + FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) + ON DELETE RESTRICT; + + -- Create an index on the email_id column to improve join performance + CREATE INDEX IF NOT EXISTS idx_attachments_email_id ON email_schema.attachments(email_id); + +EXCEPTION + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table referenced in foreign key constraint or index creation.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column referenced in foreign key constraint or index creation.'); + RAISE; + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate foreign key constraint or index name.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while adding foreign key constraints or creating indexes: ' || SQLERRM); + RAISE; +END; +$$; + + + +DO $$ +BEGIN + -- Creating the 'email_attachment_mapping' table for a many-to-many relationship. + CREATE TABLE IF NOT EXISTS email_schema.email_attachment_mapping ( + email_id BIGINT NOT NULL, + attachment_id BIGINT NOT NULL, + PRIMARY KEY (email_id, attachment_id), + CONSTRAINT fk_email_attachment_mapping_email_id FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) ON DELETE RESTRICT, + CONSTRAINT fk_email_attachment_mapping_attachment_id FOREIGN KEY (attachment_id) REFERENCES email_schema.attachments(attachment_id) ON DELETE RESTRICT + ); + + -- Since email_id and attachment_id are part of a composite primary key, they are indexed. + -- Additional indexes can be created if there are frequent queries that filter or join on these columns independently. + + -- Create an index on the email_id column to improve performance for queries filtering by email_id + CREATE INDEX IF NOT EXISTS idx_email_attachment_mapping_email_id ON email_schema.email_attachment_mapping(email_id); + + -- Create an index on the attachment_id column to improve performance for queries filtering by attachment_id + CREATE INDEX IF NOT EXISTS idx_email_attachment_mapping_attachment_id ON email_schema.email_attachment_mapping(attachment_id); + +EXCEPTION + WHEN duplicate_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate table name: email_attachment_mapping.'); + RAISE; + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table referenced in foreign key constraint or index creation.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column referenced in foreign key constraint or index creation.'); + RAISE; + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate foreign key constraint or index name.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while creating email_attachment_mapping table or indexes: ' || SQLERRM); + RAISE; +END; +$$; + + + +DO $$ +BEGIN + -- Creating the 'email_status' table to track the state of emails. + CREATE TABLE IF NOT EXISTS email_schema.email_status ( + status_id SERIAL PRIMARY KEY, + email_id BIGINT NOT NULL, + status TEXT NOT NULL CHECK (status IN ('sent', 'received', 'read', 'error', 'pending')), -- Add more statuses as needed + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP + ); + + -- Index for email_id foreign key + CREATE INDEX IF NOT EXISTS idx_email_status_email_id ON email_schema.email_status(email_id); + + -- Index for status column for quick searches + CREATE INDEX IF NOT EXISTS idx_email_status_status ON email_schema.email_status(status); + + -- Index for updated_at to quickly find statuses by update date + CREATE INDEX IF NOT EXISTS idx_email_status_updated_at ON email_schema.email_status(updated_at); + +EXCEPTION + WHEN duplicate_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate table name: email_status.'); + RAISE; + WHEN check_violation THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Check constraint violation on table email_status.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while creating email_status table or indexes: ' || SQLERRM); + RAISE; +END; +$$; + + + +DO $$ +BEGIN + -- Foreign key constraint for email_status referencing emails without cascading deletes. + ALTER TABLE email_schema.email_status + ADD CONSTRAINT fk_email_status_email_id + FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) + ON DELETE RESTRICT; + + -- Explicitly create an index on the foreign key column, even though it's automatically indexed by PostgreSQL + CREATE INDEX IF NOT EXISTS idx_fk_email_status_email_id ON email_schema.email_status(email_id); + +EXCEPTION + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate constraint or index name: fk_email_status_email_id or idx_fk_email_status_email_id.'); + RAISE; + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table: email_status or emails.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column: email_id in table email_status or emails.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while adding foreign key constraint or creating index for email_status table: ' || SQLERRM); + RAISE; +END; +$$; + +-- Commit the changes if all operations are successful. +COMMIT; + + diff --git a/postgresql/schemas/v2_11-05-23_03_create_tables.sql b/postgresql/schemas/v2_11-05-23_03_create_tables.sql new file mode 100644 index 0000000..6ec2244 --- /dev/null +++ b/postgresql/schemas/v2_11-05-23_03_create_tables.sql @@ -0,0 +1,236 @@ +BEGIN; + +CREATE TABLE IF NOT EXISTS email_schema.emails ( + email_id BIGSERIAL PRIMARY KEY, + subject TEXT, + sender VARCHAR(100) CHECK (sender ~* '^[^@]+@[^@]+\.[^@]+$'), -- Regex for basic email validation + recipient VARCHAR(100) CHECK (recipient ~* '^[^@]+@[^@]+\.[^@]+$'), + sent_time TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(), -- Default to the current timestamp + body_text TEXT, + ip_address INET, + x_originating_ip INET, + received TEXT, + user_agent VARCHAR(255), + authentication_results TEXT, + dkim_signature TEXT +); + +-- Creating the 'attachments' table within the 'email_schema'. +CREATE TABLE IF NOT EXISTS email_schema.attachments ( + attachment_id BIGSERIAL PRIMARY KEY, + email_id BIGINT NOT NULL REFERENCES email_schema.emails(email_id) ON DELETE CASCADE, + file_name TEXT NOT NULL, + file_type TEXT NOT NULL, -- assuming every attachment must have a file type + file_size BIGINT NOT NULL CHECK (file_size >= 0), -- assuming file size must be non-negative + content BYTEA, -- consider storing only a reference to the file location if they're large + CONSTRAINT fk_attachments_email_id FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) +); + +-- Index for attachments to improve lookup performance on the email_id foreign key +CREATE INDEX IF NOT EXISTS idx_attachments_email_id ON email_schema.attachments(email_id); + +-- Creating the 'email_attachment_mapping' table for a many-to-many relationship between emails and attachments +CREATE TABLE IF NOT EXISTS email_schema.email_attachment_mapping ( + email_id BIGINT NOT NULL, + attachment_id BIGINT NOT NULL, + PRIMARY KEY (email_id, attachment_id), + FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) ON DELETE RESTRICT, + FOREIGN KEY (attachment_id) REFERENCES email_schema.attachments(attachment_id) ON DELETE RESTRICT +); + +COMMENT ON TABLE email_schema.email_attachment_mapping IS 'Maps many-to-many relationships between emails and attachments.'; + +-- Individual indexes for each foreign key, if frequent querying by single column +CREATE INDEX IF NOT EXISTS idx_email_attachment_mapping_email_id ON email_schema.email_attachment_mapping(email_id); +CREATE INDEX IF NOT EXISTS idx_email_attachment_mapping_attachment_id ON email_schema.email_attachment_mapping(attachment_id); + +-- Including error handling for constraint violations during inserts or updates to this table +DO $$ +BEGIN + -- Assuming we have operations here that involve the email_attachment_mapping table +EXCEPTION + WHEN foreign_key_violation THEN + RAISE WARNING 'Foreign key constraint violated: %', SQLERRM; + -- Additional handling logic here, such as rollback to a savepoint or logging the event + WHEN unique_violation THEN + RAISE WARNING 'Unique constraint violated: %', SQLERRM; + -- Handle unique constraint violations + WHEN OTHERS THEN + RAISE WARNING 'An unexpected error occurred: %', SQLERRM; + -- Handle other unexpected errors +END; +$$; + + +-- Creating the 'email_status' table to track the state of emails +CREATE TABLE IF NOT EXISTS email_schema.email_status ( + status_id SERIAL PRIMARY KEY, + email_id BIGINT NOT NULL REFERENCES email_schema.emails(email_id) ON DELETE CASCADE, + status TEXT NOT NULL CHECK (status IN ('sent', 'received', 'read', 'error', 'pending')), -- Add more statuses as needed + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +COMMENT ON TABLE email_schema.email_status IS 'Tracks the status and state changes of emails.'; + +-- Index for email_status to improve lookup performance on the email_id foreign key +CREATE INDEX IF NOT EXISTS idx_email_status_email_id ON email_schema.email_status(email_id); + +-- Index for frequently used status queries +CREATE INDEX IF NOT EXISTS idx_email_status_status ON email_schema.email_status(status); + +-- Adding error handling for operations on the email_status table +DO $$ +BEGIN + -- Placeholder for operations on the email_status table +EXCEPTION + WHEN foreign_key_violation THEN + RAISE WARNING 'Foreign key constraint violated: %', SQLERRM; + -- Handle foreign key constraint violation + WHEN check_violation THEN + RAISE WARNING 'Check constraint violated: %', SQLERRM; + -- Handle check constraint violation + WHEN unique_violation THEN + RAISE WARNING 'Unique constraint violated: %', SQLERRM; + -- Handle unique constraint violations + WHEN OTHERS THEN + RAISE WARNING 'An unexpected error occurred: %', SQLERRM; + -- Handle other unexpected errors +END; +$$; + + +-- Adding a foreign key constraint to the 'email_status' table +DO $$ +BEGIN + ALTER TABLE email_schema.email_status + ADD CONSTRAINT fk_email_status_email_id + FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) + ON DELETE RESTRICT; +EXCEPTION + WHEN duplicate_object THEN + RAISE NOTICE 'The constraint fk_email_status_email_id already exists.'; + WHEN undefined_table THEN + RAISE NOTICE 'The table email_schema.emails does not exist.'; + WHEN undefined_column THEN + RAISE NOTICE 'The column email_id does not exist in table email_schema.emails.'; + WHEN OTHERS THEN + RAISE NOTICE 'An unexpected error occurred: %', SQLERRM; +END; +$$; + +-- Creating the 'users' table for user management. +CREATE TABLE IF NOT EXISTS email_schema.users ( + user_id SERIAL PRIMARY KEY, + email VARCHAR(255) UNIQUE NOT NULL, + created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP +); + +-- Adding new columns with foreign keys, which could fail if the users table or columns don't exist +ALTER TABLE email_schema.emails + ADD COLUMN IF NOT EXISTS sender_user_id INT, + ADD COLUMN IF NOT EXISTS recipient_user_id INT; + +-- Adding the foreign key constraints separately, allowing for individual error handling +ALTER TABLE email_schema.emails + ADD CONSTRAINT IF NOT EXISTS fk_emails_sender_user_id + FOREIGN KEY (sender_user_id) REFERENCES email_schema.users(user_id) + ON DELETE SET NULL; + +ALTER TABLE email_schema.emails + ADD CONSTRAINT IF NOT EXISTS fk_emails_recipient_user_id + FOREIGN KEY (recipient_user_id) REFERENCES email_schema.users(user_id) + ON DELETE SET NULL; + +-- Creating indexes, which are unlikely to fail due to a unique violation +CREATE INDEX IF NOT EXISTS idx_emails_sender_user_id ON email_schema.emails(sender_user_id); +CREATE INDEX IF NOT EXISTS idx_emails_recipient_user_id ON email_schema.emails(recipient_user_id); + +EXCEPTION + WHEN undefined_table THEN + RAISE NOTICE 'The operation failed because a required table does not exist.'; + WHEN undefined_column THEN + RAISE NOTICE 'The operation failed because a required column does not exist.'; + WHEN datatype_mismatch THEN + RAISE NOTICE 'The operation failed because of a datatype mismatch.'; + WHEN OTHERS THEN + RAISE NOTICE 'An unexpected error occurred: %', SQLERRM; + -- Rollback could be considered for unexpected errors + ROLLBACK; + +DO $$ +BEGIN + -- Attempt to add foreign key constraints + ALTER TABLE email_schema.emails + ADD CONSTRAINT fk_emails_sender_user_id + FOREIGN KEY (sender_user_id) REFERENCES email_schema.users(user_id) + ON DELETE RESTRICT, + ADD CONSTRAINT fk_emails_recipient_user_id + FOREIGN KEY (recipient_user_id) REFERENCES email_schema.users(user_id) + ON DELETE RESTRICT; + +EXCEPTION + WHEN undefined_table OR undefined_column THEN + INSERT INTO email_schema.error_log (error_message, error_timestamp) VALUES (SQLERRM, CURRENT_TIMESTAMP); + RAISE NOTICE 'One of the tables or columns involved in the foreign key constraints does not exist.'; + ROLLBACK; + WHEN foreign_key_violation THEN + INSERT INTO email_schema.error_log (error_message, error_timestamp) VALUES (SQLERRM, CURRENT_TIMESTAMP); + RAISE NOTICE 'Existing data violates the foreign key constraint being added.'; + ROLLBACK; + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message, error_timestamp) VALUES (SQLERRM, CURRENT_TIMESTAMP); + RAISE NOTICE 'A constraint with the same name already exists.'; + ROLLBACK; + WHEN OTHERS THEN + INSERT INTO email_schema.error_log (error_message, error_timestamp) VALUES (SQLERRM, CURRENT_TIMESTAMP); + RAISE NOTICE 'An unexpected error occurred: %', SQLERRM; + -- Handle the exception, maybe rollback to a savepoint or log the error + ROLLBACK; -- Uncomment if you decide to rollback the transaction on error +END; +$$; + + +-- Assuming you have a users table with user_id as a primary key +-- Update the 'performed_by' column to be a foreign key +ALTER TABLE email_schema.audit_log +ADD CONSTRAINT fk_audit_log_performed_by +FOREIGN KEY (performed_by) REFERENCES email_schema.users(user_id); + +-- Now the function with improved error handling and foreign key relation +CREATE OR REPLACE FUNCTION email_schema.audit_log_trigger() +RETURNS TRIGGER AS $$ +BEGIN + -- Attempt to log the audit data + IF TG_OP = 'DELETE' THEN + INSERT INTO email_schema.audit_log(action, performed_by, detail) + VALUES (TG_OP, OLD.sender_user_id, 'Email ' || TG_OP || ' with ID ' || OLD.email_id); + ELSE + INSERT INTO email_schema.audit_log(action, performed_by, detail) + VALUES (TG_OP, NEW.sender_user_id, 'Email ' || TG_OP || ' with ID ' || NEW.email_id); + END IF; + RETURN NEW; +EXCEPTION + WHEN unique_violation THEN + -- Handle unique constraint violation + RAISE NOTICE 'A unique constraint was violated: %', SQLERRM; + RETURN NEW; + WHEN foreign_key_violation THEN + -- Handle foreign key violation + RAISE NOTICE 'A foreign key constraint was violated: %', SQLERRM; + RETURN NEW; + WHEN OTHERS THEN + -- Handle other unexpected errors + RAISE NOTICE 'An unexpected error occurred: %', SQLERRM; + RETURN NEW; +END; +$$ LANGUAGE plpgsql; + + +-- Then, define the trigger that uses the function. +DROP TRIGGER IF EXISTS emails_audit_trigger ON email_schema.emails; +CREATE TRIGGER emails_audit_trigger +AFTER INSERT OR UPDATE OR DELETE ON email_schema.emails +FOR EACH ROW EXECUTE FUNCTION email_schema.audit_log_trigger(); + + +COMMIT; \ No newline at end of file diff --git a/postgresql/schemas/v3_11-05-23_03_create_tables.sql b/postgresql/schemas/v3_11-05-23_03_create_tables.sql new file mode 100644 index 0000000..b13c2a6 --- /dev/null +++ b/postgresql/schemas/v3_11-05-23_03_create_tables.sql @@ -0,0 +1,206 @@ +BEGIN; + +-- Step 1: Create the error_log table +CREATE TABLE IF NOT EXISTS email_schema.error_log ( + error_id SERIAL PRIMARY KEY, + error_timestamp TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP, + error_message TEXT +); + +-- Attempt to create the 'emails' table within the 'email_schema'. +DO $$ +BEGIN + CREATE TABLE IF NOT EXISTS email_schema.emails ( + email_id BIGSERIAL PRIMARY KEY, + subject TEXT, + sender VARCHAR(100) CHECK (sender ~* '^[^@]+@[^@]+\.[^@]+$'), -- Regex for basic email validation + recipient VARCHAR(100) CHECK (recipient ~* '^[^@]+@[^@]+\.[^@]+$'), + sent_time TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(), -- Default to the current timestamp + body_text TEXT, + ip_address INET, + x_originating_ip INET, + received TEXT, + user_agent VARCHAR(255), + authentication_results TEXT, + dkim_signature TEXT, + sender_user_id INT, + recipient_user_id INT + ); +EXCEPTION + WHEN duplicate_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate table: emails'); + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while creating emails table: ' || SQLERRM); + RAISE; +END; +$$; + +DO $$ +BEGIN + -- Adding foreign key constraints to the 'emails' table. + ALTER TABLE email_schema.emails + ADD CONSTRAINT fk_emails_sender_user_id + FOREIGN KEY (sender_user_id) REFERENCES email_schema.users(user_id) + ON DELETE SET NULL, + ADD CONSTRAINT fk_emails_recipient_user_id + FOREIGN KEY (recipient_user_id) REFERENCES email_schema.users(user_id) + ON DELETE SET NULL; + +EXCEPTION + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table referenced in foreign key constraint for fk_emails_sender_user_id or fk_emails_recipient_user_id.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column referenced in foreign key constraint for fk_emails_sender_user_id or fk_emails_recipient_user_id.'); + RAISE; + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate foreign key constraint name for fk_emails_sender_user_id or fk_emails_recipient_user_id.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while adding foreign key constraints to emails table: ' || SQLERRM); + RAISE; +END; +$$; + + +DO $$ +BEGIN + -- Creating the 'attachments' table within the 'email_schema'. + CREATE TABLE IF NOT EXISTS email_schema.attachments ( + attachment_id BIGSERIAL PRIMARY KEY, + email_id BIGINT NOT NULL, + file_name TEXT NOT NULL, + file_type TEXT NOT NULL, -- assuming every attachment must have a file type + file_size BIGINT NOT NULL CHECK (file_size >= 0), -- assuming file size must be non-negative + content BYTEA -- consider storing only a reference to the file location if they're large + ); + + -- Adding foreign key constraints without CASCADE on delete + ALTER TABLE email_schema.attachments + ADD CONSTRAINT fk_attachments_email_id + FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) ON DELETE RESTRICT; + +EXCEPTION + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table referenced in foreign key constraint for attachments.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column referenced in foreign key constraint for attachments.'); + RAISE; + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate foreign key constraint name for attachments.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while creating attachments table: ' || SQLERRM); + RAISE; +END; +$$; + + +DO $$ +BEGIN + -- Adding foreign key constraint for attachments referencing emails without cascading deletes. + ALTER TABLE email_schema.attachments + ADD CONSTRAINT fk_attachments_email_id + FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) + ON DELETE RESTRICT; + +EXCEPTION + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table referenced in foreign key constraint.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column referenced in foreign key constraint.'); + RAISE; + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate foreign key constraint name.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while adding foreign key constraints: ' || SQLERRM); + RAISE; +END; +$$; + + +DO $$ +BEGIN + -- Creating the 'email_attachment_mapping' table for a many-to-many relationship. + CREATE TABLE IF NOT EXISTS email_schema.email_attachment_mapping ( + email_id BIGINT NOT NULL, + attachment_id BIGINT NOT NULL, + PRIMARY KEY (email_id, attachment_id), + CONSTRAINT fk_email_attachment_mapping_email_id FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) ON DELETE RESTRICT, + CONSTRAINT fk_email_attachment_mapping_attachment_id FOREIGN KEY (attachment_id) REFERENCES email_schema.attachments(attachment_id) ON DELETE RESTRICT + ); + +EXCEPTION + WHEN duplicate_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate table name: email_attachment_mapping.'); + RAISE; + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table referenced in foreign key constraint.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column referenced in foreign key constraint.'); + RAISE; + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate foreign key constraint name.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while creating email_attachment_mapping table: ' || SQLERRM); + RAISE; +END; +$$; + + +DO $$ +BEGIN + -- Creating the 'email_status' table to track the state of emails. + CREATE TABLE IF NOT EXISTS email_schema.email_status ( + status_id SERIAL PRIMARY KEY, + email_id BIGINT NOT NULL, + status TEXT NOT NULL CHECK (status IN ('sent', 'received', 'read', 'error', 'pending')), -- Add more statuses as needed + updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT CURRENT_TIMESTAMP + ); + +EXCEPTION + WHEN duplicate_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate table name: email_status.'); + RAISE; + WHEN check_violation THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Check constraint violation on table email_status.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while creating email_status table: ' || SQLERRM); + RAISE; +END; +$$; + + +DO $$ +BEGIN + -- Foreign key constraint for email_status referencing emails without cascading deletes. + ALTER TABLE email_schema.email_status + ADD CONSTRAINT fk_email_status_email_id + FOREIGN KEY (email_id) REFERENCES email_schema.emails(email_id) + ON DELETE RESTRICT; + +EXCEPTION + WHEN duplicate_object THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Duplicate constraint name: fk_email_status_email_id.'); + RAISE; + WHEN undefined_table THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined table: email_status or emails.'); + RAISE; + WHEN undefined_column THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Undefined column: email_id in table email_status or emails.'); + RAISE; + WHEN others THEN + INSERT INTO email_schema.error_log (error_message) VALUES ('Unexpected error occurred while adding foreign key constraint to email_status table: ' || SQLERRM); + RAISE; +END; +$$; + +-- Commit the changes if all operations are successful. +COMMIT; +