From d8f69a34f440320600a3e02e38cc4bd81fb42c3a Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:26:53 +0000 Subject: [PATCH 1/2] Add persistent Oracle database setup with multiple tables - Add Docker Compose configuration for persistent Oracle database with volume - Create simple tables (categories, status_lookup) with basic constraints - Create medium complexity tables (customers, products, orders) with foreign keys and indexes - Create complex tables (order_items, audit_log, inventory_transactions) with triggers - Add triggers for order totals, inventory tracking, and audit logging - Add setup scripts for database initialization and installation - Add verification script to test data persistence across restarts - Add comprehensive documentation for the persistent database setup Co-Authored-By: sachet.agarwal@windsurf.com --- docker-compose.yml | 28 ++ docs/PERSISTENT_DATABASE_SETUP.md | 296 ++++++++++++++++++ scripts/install_all.sh | 79 +++++ scripts/setup_persistent_db.sh | 86 +++++ scripts/verify_persistence.sh | 138 ++++++++ .../V2.01__simple_tables.sql | 32 ++ .../V2.02__medium_tables.sql | 101 ++++++ .../V2.03__complex_tables.sql | 236 ++++++++++++++ .../install_persistent_tables.sql | 14 + 9 files changed, 1010 insertions(+) create mode 100644 docker-compose.yml create mode 100644 docs/PERSISTENT_DATABASE_SETUP.md create mode 100755 scripts/install_all.sh create mode 100755 scripts/setup_persistent_db.sh create mode 100755 scripts/verify_persistence.sh create mode 100644 source/persistent_tables/V2.01__simple_tables.sql create mode 100644 source/persistent_tables/V2.02__medium_tables.sql create mode 100644 source/persistent_tables/V2.03__complex_tables.sql create mode 100644 source/persistent_tables/install_persistent_tables.sql diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..9323e20 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,28 @@ +version: '3.8' + +services: + oracle-xe: + image: gvenzl/oracle-xe:21-slim + container_name: oracle-xe-persistent + environment: + - ORACLE_PASSWORD=oracle + ports: + - "1521:1521" + volumes: + - oracle-data:/opt/oracle/oradata + shm_size: 1g + healthcheck: + test: ["CMD", "healthcheck.sh"] + interval: 10s + timeout: 5s + retries: 10 + networks: + - utplsql_network + +networks: + utplsql_network: + driver: bridge + +volumes: + oracle-data: + driver: local diff --git a/docs/PERSISTENT_DATABASE_SETUP.md b/docs/PERSISTENT_DATABASE_SETUP.md new file mode 100644 index 0000000..814b84e --- /dev/null +++ b/docs/PERSISTENT_DATABASE_SETUP.md @@ -0,0 +1,296 @@ +# Persistent Oracle Database Setup Guide + +This guide explains how to set up and run the utPLSQL demo project with a persistent Oracle database that preserves data across container restarts. + +## Overview + +The default CI/CD setup uses ephemeral Docker containers that are destroyed after each pipeline run. This persistent setup uses Docker volumes to ensure data survives container restarts and recreations. + +## Prerequisites + +Before starting, ensure you have installed: + +- **Docker**: Version 20.10 or later +- **Docker Compose**: Version 2.0 or later (or docker-compose v1.29+) +- **Git**: For cloning the utPLSQL framework + +## Quick Start + +### 1. Start the Persistent Database + +```bash +# Make the setup script executable +chmod +x scripts/setup_persistent_db.sh + +# Start the database +./scripts/setup_persistent_db.sh +``` + +This will: +- Create a Docker network for the database +- Start an Oracle XE 21c container with persistent storage +- Wait for the database to be ready + +### 2. Install utPLSQL and Demo Project + +```bash +# Make the installation script executable +chmod +x scripts/install_all.sh + +# Install everything +./scripts/install_all.sh +``` + +This will: +- Clone and install the utPLSQL v3 testing framework +- Create the `ut3_demo` user +- Install the demo PL/SQL packages +- Install the persistent tables with varying complexity +- Install the unit tests + +### 3. Verify Data Persistence + +```bash +# Make the verification script executable +chmod +x scripts/verify_persistence.sh + +# Run the verification +./scripts/verify_persistence.sh +``` + +This script will: +- Display current data in all tables +- Insert test data +- Stop and restart the container +- Verify the test data persisted + +## Connection Details + +| Property | Value | +|----------|-------| +| Host | localhost | +| Port | 1521 | +| Service Name | XE | +| SYS Password | oracle | +| Demo User | ut3_demo | +| Demo Password | ut3_demo | + +### Connection Strings + +```bash +# SYS (admin) connection +sqlplus sys/oracle@//127.0.0.1:1521/XE as sysdba + +# Demo user connection +sqlplus ut3_demo/ut3_demo@//127.0.0.1:1521/XE +``` + +## Docker Commands + +### Start the Database +```bash +docker compose up -d +``` + +### Stop the Database (preserves data) +```bash +docker compose stop +``` + +### Restart the Database +```bash +docker compose start +``` + +### Stop and Remove Container (preserves data in volume) +```bash +docker compose down +``` + +### Stop and Remove Everything (including data) +```bash +docker compose down -v +``` + +### View Database Logs +```bash +docker logs oracle-xe-persistent +``` + +### Follow Database Logs +```bash +docker logs -f oracle-xe-persistent +``` + +## Table Structure + +The persistent tables are organized into three complexity levels: + +### Simple Tables (V2.01) + +**categories** - Basic lookup table for product categories +- `category_id` (NUMBER, PK) +- `category_name` (VARCHAR2) +- `description` (VARCHAR2) +- `created_date` (DATE) + +**status_lookup** - Status codes for various entities +- `status_code` (VARCHAR2, PK) +- `status_description` (VARCHAR2) +- `is_active` (CHAR) + +### Medium Complexity Tables (V2.02) + +**customers** - Customer information with constraints +- Primary key with sequence +- Foreign key to status_lookup +- Email uniqueness constraint +- Email format check constraint +- Multiple indexes + +**products** - Product catalog with category relationships +- Primary key with sequence +- Foreign keys to categories and status_lookup +- Price and stock check constraints +- Multiple indexes + +**orders** - Order header information +- Primary key with sequence +- Foreign keys to customers and status_lookup +- Date validation constraint +- Multiple indexes + +### Complex Tables (V2.03) + +**order_items** - Order line items with computed columns +- Primary key with sequence +- Foreign keys to orders and products +- Virtual column for line_total calculation +- Unique constraint on order/product combination +- Triggers for order total and inventory updates + +**audit_log** - Automatic change tracking +- Captures INSERT, UPDATE, DELETE operations +- Records old and new values +- Tracks user and session information + +**inventory_transactions** - Stock movement history +- Tracks all inventory changes +- Links to source transactions +- Maintains quantity history + +### Triggers + +1. **trg_update_order_total** - Automatically calculates order totals when items change +2. **trg_update_inventory** - Updates product stock and creates inventory transactions +3. **trg_audit_customers** - Logs all changes to the customers table +4. **trg_customers_updated** - Sets updated_date on customer changes +5. **trg_products_updated** - Sets updated_date on product changes + +## Running Tests + +After installation, run the utPLSQL unit tests: + +```bash +./scripts/4_run_tests.sh +``` + +Or using utPLSQL-cli directly: + +```bash +# Download utPLSQL-cli if not already present +curl -Lk -o utPLSQL-cli.zip "https://github.com/utPLSQL/utPLSQL-cli/releases/download/3.1.9/utPLSQL-cli.zip" +unzip utPLSQL-cli.zip +chmod -R u+x utPLSQL-cli + +# Run tests +utPLSQL-cli/bin/utplsql run ut3_demo/ut3_demo@//127.0.0.1:1521/XE -f=ut_documentation_reporter +``` + +## Troubleshooting + +### Database Won't Start + +1. Check if Docker is running: + ```bash + docker info + ``` + +2. Check container logs: + ```bash + docker logs oracle-xe-persistent + ``` + +3. Ensure port 1521 is not in use: + ```bash + lsof -i :1521 + ``` + +### Connection Refused + +1. Wait for the database to fully start (can take 2-3 minutes) +2. Check if the container is running: + ```bash + docker ps | grep oracle-xe-persistent + ``` + +### Data Not Persisting + +1. Ensure you're using `docker compose stop` instead of `docker compose down -v` +2. Check if the volume exists: + ```bash + docker volume ls | grep oracle-data + ``` + +### Permission Issues + +If you encounter permission issues with the scripts: +```bash +chmod +x scripts/*.sh +``` + +## Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Docker Host │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ oracle-xe-persistent │ │ +│ │ ┌─────────────────────────────────────────────────┐ │ │ +│ │ │ Oracle XE 21c Database │ │ │ +│ │ │ ┌─────────────┐ ┌─────────────────────────┐ │ │ │ +│ │ │ │ UT3 Schema │ │ UT3_DEMO Schema │ │ │ │ +│ │ │ │ (utPLSQL) │ │ (Demo + Persistent) │ │ │ │ +│ │ │ └─────────────┘ └─────────────────────────┘ │ │ │ +│ │ └─────────────────────────────────────────────────┘ │ │ +│ └───────────────────────────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌───────────────────────────────────────────────────────┐ │ +│ │ oracle-data (Docker Volume) │ │ +│ │ /opt/oracle/oradata │ │ +│ └───────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ Port 1521 + ┌─────────────┐ + │ Client │ + │ (SQLPlus, │ + │ SQL Dev, │ + │ utPLSQL) │ + └─────────────┘ +``` + +## Files Created + +| File | Description | +|------|-------------| +| `docker-compose.yml` | Docker Compose configuration for persistent database | +| `scripts/setup_persistent_db.sh` | Script to start the persistent database | +| `scripts/install_all.sh` | Script to install utPLSQL, demo project, and tables | +| `scripts/verify_persistence.sh` | Script to verify data persistence | +| `source/persistent_tables/V2.01__simple_tables.sql` | Simple table definitions | +| `source/persistent_tables/V2.02__medium_tables.sql` | Medium complexity tables | +| `source/persistent_tables/V2.03__complex_tables.sql` | Complex tables with triggers | +| `source/persistent_tables/install_persistent_tables.sql` | Installation script for tables | +| `docs/PERSISTENT_DATABASE_SETUP.md` | This documentation | diff --git a/scripts/install_all.sh b/scripts/install_all.sh new file mode 100755 index 0000000..7f9bcd5 --- /dev/null +++ b/scripts/install_all.sh @@ -0,0 +1,79 @@ +#!/bin/bash +# Script to install utPLSQL framework, demo project, and persistent tables +# Run this after setting up the persistent database with setup_persistent_db.sh + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +# Default utPLSQL version +UTPLSQL_VERSION=${UTPLSQL_VERSION:-v3.1.13} + +echo "==========================================" +echo "Installing utPLSQL and Demo Project" +echo "==========================================" + +cd "$PROJECT_DIR" + +# Check if database is running +if ! docker ps --format '{{.Names}}' | grep -q "oracle-xe-persistent"; then + echo "Error: Oracle database container is not running." + echo "Please run ./scripts/setup_persistent_db.sh first." + exit 1 +fi + +# Step 1: Install utPLSQL framework +echo "" +echo "Step 1: Installing utPLSQL ${UTPLSQL_VERSION}..." +if [ -d "utPLSQL" ]; then + echo "Removing existing utPLSQL directory..." + rm -rf utPLSQL +fi + +git clone --depth=1 --branch=${UTPLSQL_VERSION} https://github.com/utPLSQL/utPLSQL.git utPLSQL +chmod -R go+w $(pwd)/utPLSQL/source + +docker run --rm -v $(pwd)/utPLSQL:/utPLSQL -w /utPLSQL/source --network host \ + --entrypoint sqlplus gvenzl/oracle-xe:21-slim \ + sys/oracle@//127.0.0.1:1521/XE as sysdba @install_headless.sql UT3 UT3 users + +echo "utPLSQL installed successfully!" + +# Step 2: Create demo user and install demo project +echo "" +echo "Step 2: Creating demo user and installing demo project..." +docker run --rm -v $(pwd):/work -w /work --network host \ + --entrypoint sqlplus gvenzl/oracle-xe:21-slim \ + sys/oracle@//127.0.0.1:1521/XE as sysdba @source/create_user.sql + +docker run --rm -v $(pwd):/work -w /work --network host \ + --entrypoint sqlplus gvenzl/oracle-xe:21-slim \ + ut3_demo/ut3_demo@//127.0.0.1:1521/XE @source/install.sql + +echo "Demo project installed successfully!" + +# Step 3: Install persistent tables +echo "" +echo "Step 3: Installing persistent tables with varying complexity..." +docker run --rm -v $(pwd):/work -w /work --network host \ + --entrypoint sqlplus gvenzl/oracle-xe:21-slim \ + ut3_demo/ut3_demo@//127.0.0.1:1521/XE @source/persistent_tables/install_persistent_tables.sql + +echo "Persistent tables installed successfully!" + +# Step 4: Install unit tests +echo "" +echo "Step 4: Installing unit tests..." +sh "$SCRIPT_DIR/3_install_tests.sh" + +echo "" +echo "==========================================" +echo "Installation Complete!" +echo "==========================================" +echo "" +echo "You can now:" +echo " 1. Run tests: ./scripts/4_run_tests.sh" +echo " 2. Connect to database: sqlplus ut3_demo/ut3_demo@//127.0.0.1:1521/XE" +echo " 3. Verify data persistence by stopping and restarting the container" +echo "" diff --git a/scripts/setup_persistent_db.sh b/scripts/setup_persistent_db.sh new file mode 100755 index 0000000..789358d --- /dev/null +++ b/scripts/setup_persistent_db.sh @@ -0,0 +1,86 @@ +#!/bin/bash +# Script to set up persistent Oracle database with utPLSQL and demo tables +# This script creates a persistent Oracle database using Docker Compose + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +echo "==========================================" +echo "Setting up Persistent Oracle Database" +echo "==========================================" + +# Check if Docker is running +if ! docker info > /dev/null 2>&1; then + echo "Error: Docker is not running. Please start Docker and try again." + exit 1 +fi + +# Check if docker-compose is available +if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null 2>&1; then + echo "Error: docker-compose is not installed. Please install it and try again." + exit 1 +fi + +cd "$PROJECT_DIR" + +# Stop and remove existing container if it exists +echo "Checking for existing containers..." +if docker ps -a --format '{{.Names}}' | grep -q "oracle-xe-persistent"; then + echo "Stopping and removing existing oracle-xe-persistent container..." + docker stop oracle-xe-persistent 2>/dev/null || true + docker rm oracle-xe-persistent 2>/dev/null || true +fi + +# Start the database using docker-compose +echo "Starting Oracle database with persistent storage..." +if docker compose version &> /dev/null 2>&1; then + docker compose up -d +else + docker-compose up -d +fi + +# Wait for database to be ready +echo "Waiting for database to be ready (this may take a few minutes)..." +MAX_ATTEMPTS=60 +ATTEMPT=0 +while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do + if docker logs oracle-xe-persistent 2>&1 | grep -q "DATABASE IS READY TO USE!"; then + echo "Database is ready!" + break + fi + ATTEMPT=$((ATTEMPT + 1)) + echo "Waiting... (attempt $ATTEMPT of $MAX_ATTEMPTS)" + sleep 10 +done + +if [ $ATTEMPT -eq $MAX_ATTEMPTS ]; then + echo "Error: Database did not start within expected time." + echo "Check logs with: docker logs oracle-xe-persistent" + exit 1 +fi + +echo "" +echo "==========================================" +echo "Database Setup Complete!" +echo "==========================================" +echo "" +echo "Connection Details:" +echo " Host: localhost" +echo " Port: 1521" +echo " Service: XE" +echo " SYS Password: oracle" +echo "" +echo "To install utPLSQL and demo project, run:" +echo " ./scripts/install_all.sh" +echo "" +echo "To stop the database:" +echo " docker compose down" +echo "" +echo "To stop and preserve data:" +echo " docker compose stop" +echo "" +echo "To restart the database:" +echo " docker compose start" +echo "" diff --git a/scripts/verify_persistence.sh b/scripts/verify_persistence.sh new file mode 100755 index 0000000..99f7977 --- /dev/null +++ b/scripts/verify_persistence.sh @@ -0,0 +1,138 @@ +#!/bin/bash +# Script to verify data persistence across container restarts +# This script demonstrates that data survives container restarts + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" + +echo "==========================================" +echo "Verifying Data Persistence" +echo "==========================================" + +cd "$PROJECT_DIR" + +# Check if database is running +if ! docker ps --format '{{.Names}}' | grep -q "oracle-xe-persistent"; then + echo "Error: Oracle database container is not running." + echo "Please run ./scripts/setup_persistent_db.sh first." + exit 1 +fi + +# Function to run SQL and get results +run_sql() { + docker run --rm -v $(pwd):/work -w /work --network host \ + --entrypoint sqlplus gvenzl/oracle-xe:21-slim \ + ut3_demo/ut3_demo@//127.0.0.1:1521/XE < /dev/null 2>&1; then + docker compose stop +else + docker-compose stop +fi + +echo "Container stopped." +sleep 5 + +echo "" +echo "Step 4: Restarting the database container..." +if docker compose version &> /dev/null 2>&1; then + docker compose start +else + docker-compose start +fi + +echo "Waiting for database to be ready..." +sleep 30 + +# Wait for database to be ready +MAX_ATTEMPTS=30 +ATTEMPT=0 +while [ $ATTEMPT -lt $MAX_ATTEMPTS ]; do + if docker logs oracle-xe-persistent 2>&1 | tail -20 | grep -q "DATABASE IS READY TO USE!"; then + echo "Database is ready!" + break + fi + ATTEMPT=$((ATTEMPT + 1)) + echo "Waiting... (attempt $ATTEMPT of $MAX_ATTEMPTS)" + sleep 10 +done + +echo "" +echo "Step 5: Verifying test data persisted..." +echo "" + +RESULT=$(run_sql "SELECT category_name FROM categories WHERE category_id = 999;") + +if echo "$RESULT" | grep -q "Test Category $TEST_TIMESTAMP"; then + echo "SUCCESS: Test data persisted across container restart!" + echo "Found: Test Category $TEST_TIMESTAMP" +else + echo "WARNING: Could not verify test data. Please check manually." + echo "Result: $RESULT" +fi + +echo "" +echo "Step 6: Cleaning up test data..." +run_sql "DELETE FROM categories WHERE category_id = 999; COMMIT;" + +echo "" +echo "==========================================" +echo "Persistence Verification Complete!" +echo "==========================================" +echo "" +echo "Your data is persisted in the Docker volume 'oracle-data'." +echo "Data will survive container restarts and recreations." +echo "" diff --git a/source/persistent_tables/V2.01__simple_tables.sql b/source/persistent_tables/V2.01__simple_tables.sql new file mode 100644 index 0000000..dd4fa95 --- /dev/null +++ b/source/persistent_tables/V2.01__simple_tables.sql @@ -0,0 +1,32 @@ +-- Simple Tables: Basic columns with primary key +-- These tables demonstrate basic table structures with minimal constraints + +-- Simple table 1: Categories +CREATE TABLE categories ( + category_id NUMBER PRIMARY KEY, + category_name VARCHAR2(100) NOT NULL, + description VARCHAR2(500), + created_date DATE DEFAULT SYSDATE +); + +-- Simple table 2: Status lookup table +CREATE TABLE status_lookup ( + status_code VARCHAR2(20) PRIMARY KEY, + status_description VARCHAR2(200) NOT NULL, + is_active CHAR(1) DEFAULT 'Y' CHECK (is_active IN ('Y', 'N')) +); + +-- Insert sample data into simple tables +INSERT INTO categories (category_id, category_name, description) VALUES (1, 'Electronics', 'Electronic devices and accessories'); +INSERT INTO categories (category_id, category_name, description) VALUES (2, 'Clothing', 'Apparel and fashion items'); +INSERT INTO categories (category_id, category_name, description) VALUES (3, 'Books', 'Physical and digital books'); +INSERT INTO categories (category_id, category_name, description) VALUES (4, 'Home & Garden', 'Home improvement and garden supplies'); +INSERT INTO categories (category_id, category_name, description) VALUES (5, 'Sports', 'Sports equipment and accessories'); + +INSERT INTO status_lookup (status_code, status_description, is_active) VALUES ('ACTIVE', 'Item is currently active', 'Y'); +INSERT INTO status_lookup (status_code, status_description, is_active) VALUES ('INACTIVE', 'Item is currently inactive', 'Y'); +INSERT INTO status_lookup (status_code, status_description, is_active) VALUES ('PENDING', 'Item is pending approval', 'Y'); +INSERT INTO status_lookup (status_code, status_description, is_active) VALUES ('ARCHIVED', 'Item has been archived', 'Y'); +INSERT INTO status_lookup (status_code, status_description, is_active) VALUES ('DELETED', 'Item has been soft deleted', 'N'); + +COMMIT; diff --git a/source/persistent_tables/V2.02__medium_tables.sql b/source/persistent_tables/V2.02__medium_tables.sql new file mode 100644 index 0000000..4259e97 --- /dev/null +++ b/source/persistent_tables/V2.02__medium_tables.sql @@ -0,0 +1,101 @@ +-- Medium Complexity Tables: Tables with foreign keys, indexes, and constraints +-- These tables demonstrate relationships and data integrity constraints + +-- Sequences for auto-incrementing IDs +CREATE SEQUENCE customer_seq START WITH 1000 INCREMENT BY 1; +CREATE SEQUENCE product_seq START WITH 5000 INCREMENT BY 1; +CREATE SEQUENCE order_seq START WITH 10000 INCREMENT BY 1; + +-- Customers table with various constraints +CREATE TABLE customers ( + customer_id NUMBER DEFAULT customer_seq.NEXTVAL PRIMARY KEY, + first_name VARCHAR2(50) NOT NULL, + last_name VARCHAR2(50) NOT NULL, + email VARCHAR2(100) NOT NULL UNIQUE, + phone VARCHAR2(20), + address_line1 VARCHAR2(200), + address_line2 VARCHAR2(200), + city VARCHAR2(100), + state VARCHAR2(50), + postal_code VARCHAR2(20), + country VARCHAR2(50) DEFAULT 'USA', + status_code VARCHAR2(20) DEFAULT 'ACTIVE', + created_date DATE DEFAULT SYSDATE NOT NULL, + updated_date DATE, + CONSTRAINT fk_customer_status FOREIGN KEY (status_code) REFERENCES status_lookup(status_code), + CONSTRAINT chk_customer_email CHECK (email LIKE '%@%.%') +); + +-- Products table with category relationship +CREATE TABLE products ( + product_id NUMBER DEFAULT product_seq.NEXTVAL PRIMARY KEY, + product_name VARCHAR2(200) NOT NULL, + product_code VARCHAR2(50) NOT NULL UNIQUE, + category_id NUMBER NOT NULL, + unit_price NUMBER(10,2) NOT NULL, + quantity_in_stock NUMBER DEFAULT 0, + reorder_level NUMBER DEFAULT 10, + status_code VARCHAR2(20) DEFAULT 'ACTIVE', + description CLOB, + created_date DATE DEFAULT SYSDATE NOT NULL, + updated_date DATE, + CONSTRAINT fk_product_category FOREIGN KEY (category_id) REFERENCES categories(category_id), + CONSTRAINT fk_product_status FOREIGN KEY (status_code) REFERENCES status_lookup(status_code), + CONSTRAINT chk_product_price CHECK (unit_price >= 0), + CONSTRAINT chk_product_stock CHECK (quantity_in_stock >= 0) +); + +-- Orders table linking customers and products +CREATE TABLE orders ( + order_id NUMBER DEFAULT order_seq.NEXTVAL PRIMARY KEY, + customer_id NUMBER NOT NULL, + order_date DATE DEFAULT SYSDATE NOT NULL, + ship_date DATE, + status_code VARCHAR2(20) DEFAULT 'PENDING', + shipping_address VARCHAR2(500), + total_amount NUMBER(12,2) DEFAULT 0, + notes VARCHAR2(1000), + created_date DATE DEFAULT SYSDATE NOT NULL, + updated_date DATE, + CONSTRAINT fk_order_customer FOREIGN KEY (customer_id) REFERENCES customers(customer_id), + CONSTRAINT fk_order_status FOREIGN KEY (status_code) REFERENCES status_lookup(status_code), + CONSTRAINT chk_order_dates CHECK (ship_date IS NULL OR ship_date >= order_date) +); + +-- Create indexes for better query performance +CREATE INDEX idx_customers_email ON customers(email); +CREATE INDEX idx_customers_status ON customers(status_code); +CREATE INDEX idx_customers_name ON customers(last_name, first_name); + +CREATE INDEX idx_products_category ON products(category_id); +CREATE INDEX idx_products_status ON products(status_code); +CREATE INDEX idx_products_code ON products(product_code); + +CREATE INDEX idx_orders_customer ON orders(customer_id); +CREATE INDEX idx_orders_status ON orders(status_code); +CREATE INDEX idx_orders_date ON orders(order_date); + +-- Insert sample data into medium complexity tables +INSERT INTO customers (first_name, last_name, email, phone, city, state, postal_code) +VALUES ('John', 'Smith', 'john.smith@email.com', '555-0101', 'New York', 'NY', '10001'); +INSERT INTO customers (first_name, last_name, email, phone, city, state, postal_code) +VALUES ('Jane', 'Doe', 'jane.doe@email.com', '555-0102', 'Los Angeles', 'CA', '90001'); +INSERT INTO customers (first_name, last_name, email, phone, city, state, postal_code) +VALUES ('Bob', 'Johnson', 'bob.johnson@email.com', '555-0103', 'Chicago', 'IL', '60601'); +INSERT INTO customers (first_name, last_name, email, phone, city, state, postal_code) +VALUES ('Alice', 'Williams', 'alice.williams@email.com', '555-0104', 'Houston', 'TX', '77001'); +INSERT INTO customers (first_name, last_name, email, phone, city, state, postal_code) +VALUES ('Charlie', 'Brown', 'charlie.brown@email.com', '555-0105', 'Phoenix', 'AZ', '85001'); + +INSERT INTO products (product_name, product_code, category_id, unit_price, quantity_in_stock, description) +VALUES ('Laptop Pro 15', 'ELEC-LP15', 1, 1299.99, 50, 'High-performance laptop with 15-inch display'); +INSERT INTO products (product_name, product_code, category_id, unit_price, quantity_in_stock, description) +VALUES ('Wireless Mouse', 'ELEC-WM01', 1, 29.99, 200, 'Ergonomic wireless mouse with long battery life'); +INSERT INTO products (product_name, product_code, category_id, unit_price, quantity_in_stock, description) +VALUES ('Cotton T-Shirt', 'CLTH-TS01', 2, 19.99, 500, '100% cotton comfortable t-shirt'); +INSERT INTO products (product_name, product_code, category_id, unit_price, quantity_in_stock, description) +VALUES ('Programming Guide', 'BOOK-PG01', 3, 49.99, 100, 'Comprehensive programming guide for beginners'); +INSERT INTO products (product_name, product_code, category_id, unit_price, quantity_in_stock, description) +VALUES ('Garden Tools Set', 'HOME-GT01', 4, 79.99, 75, 'Complete garden tools set with carrying case'); + +COMMIT; diff --git a/source/persistent_tables/V2.03__complex_tables.sql b/source/persistent_tables/V2.03__complex_tables.sql new file mode 100644 index 0000000..f864d5d --- /dev/null +++ b/source/persistent_tables/V2.03__complex_tables.sql @@ -0,0 +1,236 @@ +-- Complex Tables: Tables with triggers, sequences, and multiple relationships +-- These tables demonstrate advanced database features including audit trails and business logic + +-- Sequences for complex tables +CREATE SEQUENCE order_item_seq START WITH 100000 INCREMENT BY 1; +CREATE SEQUENCE audit_log_seq START WITH 1 INCREMENT BY 1; +CREATE SEQUENCE inventory_txn_seq START WITH 1 INCREMENT BY 1; + +-- Order Items table (junction table with additional attributes) +CREATE TABLE order_items ( + order_item_id NUMBER DEFAULT order_item_seq.NEXTVAL PRIMARY KEY, + order_id NUMBER NOT NULL, + product_id NUMBER NOT NULL, + quantity NUMBER NOT NULL, + unit_price NUMBER(10,2) NOT NULL, + discount_percent NUMBER(5,2) DEFAULT 0, + line_total NUMBER(12,2) GENERATED ALWAYS AS (quantity * unit_price * (1 - discount_percent/100)) VIRTUAL, + created_date DATE DEFAULT SYSDATE NOT NULL, + CONSTRAINT fk_orderitem_order FOREIGN KEY (order_id) REFERENCES orders(order_id) ON DELETE CASCADE, + CONSTRAINT fk_orderitem_product FOREIGN KEY (product_id) REFERENCES products(product_id), + CONSTRAINT chk_orderitem_qty CHECK (quantity > 0), + CONSTRAINT chk_orderitem_price CHECK (unit_price >= 0), + CONSTRAINT chk_orderitem_discount CHECK (discount_percent >= 0 AND discount_percent <= 100), + CONSTRAINT uk_order_product UNIQUE (order_id, product_id) +); + +-- Audit Log table for tracking all changes +CREATE TABLE audit_log ( + audit_id NUMBER DEFAULT audit_log_seq.NEXTVAL PRIMARY KEY, + table_name VARCHAR2(100) NOT NULL, + operation_type VARCHAR2(10) NOT NULL, + record_id NUMBER, + old_values CLOB, + new_values CLOB, + changed_by VARCHAR2(100) DEFAULT USER, + changed_date TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL, + session_id NUMBER DEFAULT SYS_CONTEXT('USERENV', 'SESSIONID'), + ip_address VARCHAR2(50) DEFAULT SYS_CONTEXT('USERENV', 'IP_ADDRESS'), + CONSTRAINT chk_audit_operation CHECK (operation_type IN ('INSERT', 'UPDATE', 'DELETE')) +); + +-- Inventory Transactions table for tracking stock movements +CREATE TABLE inventory_transactions ( + transaction_id NUMBER DEFAULT inventory_txn_seq.NEXTVAL PRIMARY KEY, + product_id NUMBER NOT NULL, + transaction_type VARCHAR2(20) NOT NULL, + quantity_change NUMBER NOT NULL, + previous_quantity NUMBER NOT NULL, + new_quantity NUMBER NOT NULL, + reference_type VARCHAR2(50), + reference_id NUMBER, + notes VARCHAR2(500), + transaction_date TIMESTAMP DEFAULT SYSTIMESTAMP NOT NULL, + created_by VARCHAR2(100) DEFAULT USER, + CONSTRAINT fk_invtxn_product FOREIGN KEY (product_id) REFERENCES products(product_id), + CONSTRAINT chk_invtxn_type CHECK (transaction_type IN ('SALE', 'PURCHASE', 'ADJUSTMENT', 'RETURN', 'TRANSFER')) +); + +-- Create indexes for complex tables +CREATE INDEX idx_orderitems_order ON order_items(order_id); +CREATE INDEX idx_orderitems_product ON order_items(product_id); +CREATE INDEX idx_audit_table ON audit_log(table_name); +CREATE INDEX idx_audit_date ON audit_log(changed_date); +CREATE INDEX idx_audit_record ON audit_log(table_name, record_id); +CREATE INDEX idx_invtxn_product ON inventory_transactions(product_id); +CREATE INDEX idx_invtxn_date ON inventory_transactions(transaction_date); +CREATE INDEX idx_invtxn_type ON inventory_transactions(transaction_type); + +-- Trigger to update order total when order items change +CREATE OR REPLACE TRIGGER trg_update_order_total +AFTER INSERT OR UPDATE OR DELETE ON order_items +FOR EACH ROW +DECLARE + v_order_id NUMBER; +BEGIN + IF DELETING THEN + v_order_id := :OLD.order_id; + ELSE + v_order_id := :NEW.order_id; + END IF; + + UPDATE orders o + SET total_amount = ( + SELECT NVL(SUM(quantity * unit_price * (1 - discount_percent/100)), 0) + FROM order_items oi + WHERE oi.order_id = o.order_id + ), + updated_date = SYSDATE + WHERE order_id = v_order_id; +END; +/ + +-- Trigger to update product inventory when order items are added +CREATE OR REPLACE TRIGGER trg_update_inventory +AFTER INSERT OR UPDATE OR DELETE ON order_items +FOR EACH ROW +DECLARE + v_prev_qty NUMBER; + v_new_qty NUMBER; + v_qty_change NUMBER; +BEGIN + IF INSERTING THEN + SELECT quantity_in_stock INTO v_prev_qty FROM products WHERE product_id = :NEW.product_id; + v_qty_change := -:NEW.quantity; + v_new_qty := v_prev_qty + v_qty_change; + + UPDATE products SET quantity_in_stock = v_new_qty, updated_date = SYSDATE + WHERE product_id = :NEW.product_id; + + INSERT INTO inventory_transactions (product_id, transaction_type, quantity_change, + previous_quantity, new_quantity, reference_type, reference_id, notes) + VALUES (:NEW.product_id, 'SALE', v_qty_change, v_prev_qty, v_new_qty, + 'ORDER_ITEM', :NEW.order_item_id, 'Stock reduced for order'); + + ELSIF UPDATING AND :OLD.quantity != :NEW.quantity THEN + SELECT quantity_in_stock INTO v_prev_qty FROM products WHERE product_id = :NEW.product_id; + v_qty_change := :OLD.quantity - :NEW.quantity; + v_new_qty := v_prev_qty + v_qty_change; + + UPDATE products SET quantity_in_stock = v_new_qty, updated_date = SYSDATE + WHERE product_id = :NEW.product_id; + + INSERT INTO inventory_transactions (product_id, transaction_type, quantity_change, + previous_quantity, new_quantity, reference_type, reference_id, notes) + VALUES (:NEW.product_id, 'ADJUSTMENT', v_qty_change, v_prev_qty, v_new_qty, + 'ORDER_ITEM', :NEW.order_item_id, 'Stock adjusted for order quantity change'); + + ELSIF DELETING THEN + SELECT quantity_in_stock INTO v_prev_qty FROM products WHERE product_id = :OLD.product_id; + v_qty_change := :OLD.quantity; + v_new_qty := v_prev_qty + v_qty_change; + + UPDATE products SET quantity_in_stock = v_new_qty, updated_date = SYSDATE + WHERE product_id = :OLD.product_id; + + INSERT INTO inventory_transactions (product_id, transaction_type, quantity_change, + previous_quantity, new_quantity, reference_type, reference_id, notes) + VALUES (:OLD.product_id, 'RETURN', v_qty_change, v_prev_qty, v_new_qty, + 'ORDER_ITEM', :OLD.order_item_id, 'Stock returned for deleted order item'); + END IF; +END; +/ + +-- Trigger to audit customer changes +CREATE OR REPLACE TRIGGER trg_audit_customers +AFTER INSERT OR UPDATE OR DELETE ON customers +FOR EACH ROW +DECLARE + v_old_values CLOB; + v_new_values CLOB; + v_operation VARCHAR2(10); + v_record_id NUMBER; +BEGIN + IF INSERTING THEN + v_operation := 'INSERT'; + v_record_id := :NEW.customer_id; + v_new_values := 'first_name=' || :NEW.first_name || ', last_name=' || :NEW.last_name || + ', email=' || :NEW.email || ', status=' || :NEW.status_code; + ELSIF UPDATING THEN + v_operation := 'UPDATE'; + v_record_id := :NEW.customer_id; + v_old_values := 'first_name=' || :OLD.first_name || ', last_name=' || :OLD.last_name || + ', email=' || :OLD.email || ', status=' || :OLD.status_code; + v_new_values := 'first_name=' || :NEW.first_name || ', last_name=' || :NEW.last_name || + ', email=' || :NEW.email || ', status=' || :NEW.status_code; + ELSIF DELETING THEN + v_operation := 'DELETE'; + v_record_id := :OLD.customer_id; + v_old_values := 'first_name=' || :OLD.first_name || ', last_name=' || :OLD.last_name || + ', email=' || :OLD.email || ', status=' || :OLD.status_code; + END IF; + + INSERT INTO audit_log (table_name, operation_type, record_id, old_values, new_values) + VALUES ('CUSTOMERS', v_operation, v_record_id, v_old_values, v_new_values); +END; +/ + +-- Trigger to set updated_date on customers +CREATE OR REPLACE TRIGGER trg_customers_updated +BEFORE UPDATE ON customers +FOR EACH ROW +BEGIN + :NEW.updated_date := SYSDATE; +END; +/ + +-- Trigger to set updated_date on products +CREATE OR REPLACE TRIGGER trg_products_updated +BEFORE UPDATE ON products +FOR EACH ROW +BEGIN + :NEW.updated_date := SYSDATE; +END; +/ + +-- Insert sample data into complex tables +-- First, create some orders +INSERT INTO orders (customer_id, order_date, status_code, shipping_address, notes) +SELECT customer_id, SYSDATE - 5, 'ACTIVE', address_line1 || ', ' || city || ', ' || state || ' ' || postal_code, 'First order' +FROM customers WHERE email = 'john.smith@email.com'; + +INSERT INTO orders (customer_id, order_date, status_code, shipping_address, notes) +SELECT customer_id, SYSDATE - 3, 'PENDING', address_line1 || ', ' || city || ', ' || state || ' ' || postal_code, 'Rush delivery requested' +FROM customers WHERE email = 'jane.doe@email.com'; + +INSERT INTO orders (customer_id, order_date, status_code, shipping_address, notes) +SELECT customer_id, SYSDATE - 1, 'ACTIVE', address_line1 || ', ' || city || ', ' || state || ' ' || postal_code, 'Gift wrapping requested' +FROM customers WHERE email = 'bob.johnson@email.com'; + +-- Insert order items (triggers will automatically update totals and inventory) +INSERT INTO order_items (order_id, product_id, quantity, unit_price, discount_percent) +SELECT o.order_id, p.product_id, 1, p.unit_price, 0 +FROM orders o, products p +WHERE o.notes = 'First order' AND p.product_code = 'ELEC-LP15'; + +INSERT INTO order_items (order_id, product_id, quantity, unit_price, discount_percent) +SELECT o.order_id, p.product_id, 2, p.unit_price, 10 +FROM orders o, products p +WHERE o.notes = 'First order' AND p.product_code = 'ELEC-WM01'; + +INSERT INTO order_items (order_id, product_id, quantity, unit_price, discount_percent) +SELECT o.order_id, p.product_id, 3, p.unit_price, 5 +FROM orders o, products p +WHERE o.notes = 'Rush delivery requested' AND p.product_code = 'CLTH-TS01'; + +INSERT INTO order_items (order_id, product_id, quantity, unit_price, discount_percent) +SELECT o.order_id, p.product_id, 1, p.unit_price, 0 +FROM orders o, products p +WHERE o.notes = 'Rush delivery requested' AND p.product_code = 'BOOK-PG01'; + +INSERT INTO order_items (order_id, product_id, quantity, unit_price, discount_percent) +SELECT o.order_id, p.product_id, 2, p.unit_price, 15 +FROM orders o, products p +WHERE o.notes = 'Gift wrapping requested' AND p.product_code = 'HOME-GT01'; + +COMMIT; diff --git a/source/persistent_tables/install_persistent_tables.sql b/source/persistent_tables/install_persistent_tables.sql new file mode 100644 index 0000000..fed35c3 --- /dev/null +++ b/source/persistent_tables/install_persistent_tables.sql @@ -0,0 +1,14 @@ +-- Installation script for persistent tables with varying complexity +-- This script installs all tables in the correct order to handle dependencies + +PROMPT Installing Simple Tables (Categories and Status Lookup)... +@source/persistent_tables/V2.01__simple_tables.sql + +PROMPT Installing Medium Complexity Tables (Customers, Products, Orders)... +@source/persistent_tables/V2.02__medium_tables.sql + +PROMPT Installing Complex Tables (Order Items, Audit Log, Inventory Transactions, Triggers)... +@source/persistent_tables/V2.03__complex_tables.sql + +PROMPT All persistent tables installed successfully! +exit From 46811673983761519e852e004c73ac1bd967db9e Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Thu, 8 Jan 2026 11:32:55 +0000 Subject: [PATCH 2/2] Fix SQL issues for Oracle compatibility - Add SET DEFINE OFF to prevent & character substitution prompts - Use compound trigger for trg_update_order_total to avoid mutating table error (ORA-04091) - Remove redundant indexes on columns already indexed by UNIQUE constraints Co-Authored-By: sachet.agarwal@windsurf.com --- .../V2.01__simple_tables.sql | 3 + .../V2.02__medium_tables.sql | 6 +- .../V2.03__complex_tables.sql | 61 +++++++++++++------ 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/source/persistent_tables/V2.01__simple_tables.sql b/source/persistent_tables/V2.01__simple_tables.sql index dd4fa95..a01b020 100644 --- a/source/persistent_tables/V2.01__simple_tables.sql +++ b/source/persistent_tables/V2.01__simple_tables.sql @@ -1,6 +1,9 @@ -- Simple Tables: Basic columns with primary key -- These tables demonstrate basic table structures with minimal constraints +-- Disable substitution variable prompting +SET DEFINE OFF + -- Simple table 1: Categories CREATE TABLE categories ( category_id NUMBER PRIMARY KEY, diff --git a/source/persistent_tables/V2.02__medium_tables.sql b/source/persistent_tables/V2.02__medium_tables.sql index 4259e97..d39554b 100644 --- a/source/persistent_tables/V2.02__medium_tables.sql +++ b/source/persistent_tables/V2.02__medium_tables.sql @@ -1,6 +1,9 @@ -- Medium Complexity Tables: Tables with foreign keys, indexes, and constraints -- These tables demonstrate relationships and data integrity constraints +-- Disable substitution variable prompting +SET DEFINE OFF + -- Sequences for auto-incrementing IDs CREATE SEQUENCE customer_seq START WITH 1000 INCREMENT BY 1; CREATE SEQUENCE product_seq START WITH 5000 INCREMENT BY 1; @@ -63,13 +66,12 @@ CREATE TABLE orders ( ); -- Create indexes for better query performance -CREATE INDEX idx_customers_email ON customers(email); +-- Note: idx_customers_email and idx_products_code are not needed as UNIQUE constraints create indexes CREATE INDEX idx_customers_status ON customers(status_code); CREATE INDEX idx_customers_name ON customers(last_name, first_name); CREATE INDEX idx_products_category ON products(category_id); CREATE INDEX idx_products_status ON products(status_code); -CREATE INDEX idx_products_code ON products(product_code); CREATE INDEX idx_orders_customer ON orders(customer_id); CREATE INDEX idx_orders_status ON orders(status_code); diff --git a/source/persistent_tables/V2.03__complex_tables.sql b/source/persistent_tables/V2.03__complex_tables.sql index f864d5d..c435c07 100644 --- a/source/persistent_tables/V2.03__complex_tables.sql +++ b/source/persistent_tables/V2.03__complex_tables.sql @@ -1,6 +1,9 @@ -- Complex Tables: Tables with triggers, sequences, and multiple relationships -- These tables demonstrate advanced database features including audit trails and business logic +-- Disable substitution variable prompting +SET DEFINE OFF + -- Sequences for complex tables CREATE SEQUENCE order_item_seq START WITH 100000 INCREMENT BY 1; CREATE SEQUENCE audit_log_seq START WITH 1 INCREMENT BY 1; @@ -66,28 +69,46 @@ CREATE INDEX idx_invtxn_product ON inventory_transactions(product_id); CREATE INDEX idx_invtxn_date ON inventory_transactions(transaction_date); CREATE INDEX idx_invtxn_type ON inventory_transactions(transaction_type); --- Trigger to update order total when order items change +-- Compound trigger to update order total when order items change +-- Using compound trigger to avoid mutating table error (ORA-04091) CREATE OR REPLACE TRIGGER trg_update_order_total -AFTER INSERT OR UPDATE OR DELETE ON order_items -FOR EACH ROW -DECLARE - v_order_id NUMBER; -BEGIN - IF DELETING THEN - v_order_id := :OLD.order_id; - ELSE - v_order_id := :NEW.order_id; - END IF; +FOR INSERT OR UPDATE OR DELETE ON order_items +COMPOUND TRIGGER + -- Collection to store affected order IDs + TYPE t_order_ids IS TABLE OF NUMBER INDEX BY PLS_INTEGER; + g_order_ids t_order_ids; + g_index PLS_INTEGER := 0; - UPDATE orders o - SET total_amount = ( - SELECT NVL(SUM(quantity * unit_price * (1 - discount_percent/100)), 0) - FROM order_items oi - WHERE oi.order_id = o.order_id - ), - updated_date = SYSDATE - WHERE order_id = v_order_id; -END; + AFTER EACH ROW IS + BEGIN + g_index := g_index + 1; + IF DELETING THEN + g_order_ids(g_index) := :OLD.order_id; + ELSE + g_order_ids(g_index) := :NEW.order_id; + END IF; + END AFTER EACH ROW; + + AFTER STATEMENT IS + v_order_id NUMBER; + BEGIN + -- Process all affected orders + FOR i IN 1..g_index LOOP + v_order_id := g_order_ids(i); + UPDATE orders o + SET total_amount = ( + SELECT NVL(SUM(quantity * unit_price * (1 - discount_percent/100)), 0) + FROM order_items oi + WHERE oi.order_id = o.order_id + ), + updated_date = SYSDATE + WHERE order_id = v_order_id; + END LOOP; + -- Reset for next statement + g_order_ids.DELETE; + g_index := 0; + END AFTER STATEMENT; +END trg_update_order_total; / -- Trigger to update product inventory when order items are added