Database Schema Documentation

Audience: Senior Developers Version: 1.0.0 Last Updated: December 2024

Overview

WooAI Chatbot Pro implements a hybrid data persistence strategy combining custom MySQL tables for high-volume transactional data with WordPress native mechanisms (options, transients, post meta) for configuration and caching.

Architecture Philosophy

Data Type Storage Mechanism Rationale
High-frequency events Custom tables Performance, indexing, bulk operations
Session state Custom tables Complex queries, expiration handling
Vector embeddings Custom tables + Supabase Local fallback, external RAG
Configuration wp_options WordPress native, auto-loading
Caching Transients TTL support, object cache integration
Playbook definitions Custom Post Type REST API, WordPress UI integration

Entity Relationship Diagram

                                    +------------------+
                                    |   wp_posts       |
                                    | (waa_playbook)   |
                                    +--------+---------+
                                             |
                                             | playbook_id
                                             v
+------------------+    session_id    +------------------+
|  wp_waa_events   |<---------------->| wp_waa_sessions  |
+------------------+                  +--------+---------+
| id (PK)          |                  | id (PK)          |
| session_id (FK)  |                  | session_id (UK)  |
| event_type       |                  | user_id (FK)     |
| event_data       |                  | ai_provider      |
| user_id (FK)     |                  | expires_at       |
| timestamp        |                  +------------------+
+------------------+                           |
                                               | session_id
                                               v
                                    +----------------------+
                                    | wp_waa_playbook_     |
                                    | sessions             |
                                    +----------------------+
                                    | id (PK)              |
                                    | session_id (FK)      |
                                    | playbook_id (FK)     |
                                    | current_step_index   |
                                    | status (ENUM)        |
                                    +----------------------+

+------------------+                  +------------------+
| wp_waa_vector_   |                  | wp_waa_rag_index |
| index            |                  +------------------+
+------------------+                  | id (PK)          |
| id (PK)          |                  | content_type     |
| product_id (FK)  |  sync/fallback   | content_id (FK)  |
| embedding_id     |<---------------->| embedding_vector |
| embedding_model  |                  | metadata_json    |
| metadata_json    |                  +------------------+
+------------------+

+------------------+
| wp_waa_overrides |
+------------------+
| id (PK)          |
| override_type    |
| entity_id        |
| settings_json    |
| created_by (FK)  |
+------------------+

Custom Tables

All tables use the WordPress table prefix (typically wp_) and are created via dbDelta() for safe, idempotent migrations.

wp_waa_events

Purpose: High-frequency event tracking for KPI analytics and conversion funnel analysis.

CREATE TABLE {prefix}waa_events (
    id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    session_id VARCHAR(255) NOT NULL,
    event_type VARCHAR(50) NOT NULL,
    event_data LONGTEXT,
    user_id BIGINT(20) UNSIGNED NULL,
    timestamp DATETIME NOT NULL,
    PRIMARY KEY (id),
    KEY idx_session (session_id),
    KEY idx_type (event_type),
    KEY idx_timestamp (timestamp),
    KEY idx_user (user_id)
) {charset_collate};

Column Specifications

Column Type Nullable Description
id BIGINT UNSIGNED NO Auto-incrementing primary key
session_id VARCHAR(255) NO References chat session (UUID format)
event_type VARCHAR(50) NO Event classification (see Event Types below)
event_data LONGTEXT YES JSON payload with event-specific data
user_id BIGINT UNSIGNED YES WordPress user ID (NULL for guests)
timestamp DATETIME NO Event occurrence time (MySQL format)

Event Types

// Core events
'chat_started'      // Session initialization
'message_sent'      // User message submitted
'message_received'  // AI response delivered
'chat_ended'        // Session termination

// Conversion events
'product_clicked'   // Product link clicked
'product_viewed'    // Product details viewed
'cart_added'        // Product added to cart
'checkout_started'  // Checkout initiated
'order_completed'   // Purchase completed

// Engagement events
'playbook_started'  // Guided flow initiated
'playbook_completed'// Guided flow finished
'rating_submitted'  // User feedback received

Index Strategy

Index Columns Purpose
PRIMARY id Unique row identification
idx_session session_id Session event retrieval
idx_type event_type Event type aggregation
idx_timestamp timestamp Time-range queries, cleanup
idx_user user_id User activity analysis

Retention Policy

Events older than 90 days are automatically purged via scheduled cleanup:

// Executed daily via wp-cron
$database->clear_old_events( 90 );

Example Data

{
    "id": 12847,
    "session_id": "550e8400-e29b-41d4-a716-446655440000",
    "event_type": "product_clicked",
    "event_data": {
        "product_id": 1234,
        "product_name": "Wireless Headphones",
        "price": 79.99,
        "source": "ai_recommendation"
    },
    "user_id": 42,
    "timestamp": "2025-12-15 14:32:18"
}

wp_waa_sessions

Purpose: Persistent storage for chat sessions with full conversation history and contextual data.

CREATE TABLE {prefix}waa_sessions (
    id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    session_id VARCHAR(255) NOT NULL,
    user_id BIGINT(20) UNSIGNED NULL,
    conversation_history LONGTEXT,
    context_data LONGTEXT,
    ai_provider VARCHAR(50),
    model_used VARCHAR(100),
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    expires_at DATETIME,
    PRIMARY KEY (id),
    UNIQUE KEY unique_session (session_id),
    KEY idx_user (user_id),
    KEY idx_created (created_at),
    KEY idx_expires (expires_at)
) {charset_collate};

Column Specifications

Column Type Nullable Description
id BIGINT UNSIGNED NO Auto-incrementing primary key
session_id VARCHAR(255) NO Unique session identifier (UUID)
user_id BIGINT UNSIGNED YES WordPress user ID (NULL for guests)
conversation_history LONGTEXT YES JSON array of message objects
context_data LONGTEXT YES JSON object with cart, viewed products, etc.
ai_provider VARCHAR(50) YES Active provider: gemini, claude, openai
model_used VARCHAR(100) YES Specific model: gemini-2.5-flash-lite
created_at DATETIME NO Session creation timestamp
updated_at DATETIME NO Last activity timestamp
expires_at DATETIME YES Session expiration (NULL = no expiry)

Session Lifecycle

┌─────────────┐     ┌─────────────┐     ┌─────────────┐
│   CREATED   │────>│   ACTIVE    │────>│   EXPIRED   │
└─────────────┘     └─────────────┘     └─────────────┘
                          │
                          v
                    ┌─────────────┐
                    │   CLEANED   │
                    └─────────────┘
  1. Created: Session initialized on first user message
  2. Active: Messages exchanged, updated_at refreshed
  3. Expired: expires_at passed (default: 1 hour of inactivity)
  4. Cleaned: Removed by scheduled cleanup job

Expiration Handling

// Default session duration
$expires_at = gmdate( 'Y-m-d H:i:s', strtotime( '+1 hour' ) );

// Cleanup expired sessions
$deleted_count = $database->clear_expired_sessions();

Context Data Structure

{
    "cart_items": [
        {"product_id": 123, "quantity": 2, "price": 29.99}
    ],
    "viewed_products": [456, 789, 123],
    "current_page": "product",
    "current_product_id": 456,
    "user_preferences": {
        "language": "en",
        "currency": "USD"
    }
}

wp_waa_vector_index

Purpose: Maps WooCommerce products to external vector embeddings stored in Supabase pgvector.

CREATE TABLE {prefix}waa_vector_index (
    id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    product_id BIGINT(20) UNSIGNED NOT NULL,
    embedding_id VARCHAR(255),
    embedding_model VARCHAR(100),
    metadata_json LONGTEXT,
    indexed_at DATETIME NOT NULL,
    PRIMARY KEY (id),
    KEY idx_product (product_id),
    KEY idx_embedding (embedding_id),
    KEY idx_indexed (indexed_at)
) {charset_collate};

Column Specifications

Column Type Nullable Description
id BIGINT UNSIGNED NO Auto-incrementing primary key
product_id BIGINT UNSIGNED NO WooCommerce product ID
embedding_id VARCHAR(255) YES Supabase vector record UUID
embedding_model VARCHAR(100) YES Model used: text-embedding-3-small
metadata_json LONGTEXT YES Cached product metadata for search
indexed_at DATETIME NO Last sync timestamp

Relationship with Supabase

WordPress (wp_waa_vector_index)     Supabase (embeddings table)
+---------------------------+       +---------------------------+
| product_id: 123           |       | id: uuid-abc-123          |
| embedding_id: uuid-abc-123|<----->| embedding: [0.12, 0.34...]|
| metadata_json: {...}      |       | content: "Product desc"   |
+---------------------------+       +---------------------------+

wp_waa_overrides

Purpose: Store admin-defined customizations for playbooks, topics, and promotional behavior.

CREATE TABLE {prefix}waa_overrides (
    id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    override_type VARCHAR(50) NOT NULL,
    entity_id VARCHAR(100) NOT NULL,
    settings_json LONGTEXT,
    created_by BIGINT(20) UNSIGNED,
    created_at DATETIME NOT NULL,
    updated_at DATETIME NOT NULL,
    PRIMARY KEY (id),
    KEY idx_type (override_type),
    KEY idx_entity (entity_id),
    KEY idx_type_entity (override_type, entity_id),
    KEY idx_created_by (created_by)
) {charset_collate};

Override Types

Type Entity ID Format Use Case
playbook Playbook post ID Custom playbook behavior
topic Topic slug Topic-specific AI instructions
promotion Product/category ID Promotional boost settings
category WC category ID Category-level AI context

Example Settings JSON

{
    "priority_boost": 10,
    "custom_prompt": "Always mention free shipping...",
    "enabled_pages": ["shop", "product"],
    "excluded_products": [123, 456]
}

wp_waa_rag_index

Purpose: Local vector embeddings fallback when Supabase is unavailable or for on-premise deployments.

CREATE TABLE {prefix}waa_rag_index (
    id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    content_type VARCHAR(50) NOT NULL,
    content_id BIGINT(20) UNSIGNED NOT NULL,
    content_text LONGTEXT,
    embedding_vector LONGTEXT,
    metadata_json LONGTEXT,
    indexed_at DATETIME NOT NULL,
    PRIMARY KEY (id),
    KEY idx_content (content_type, content_id),
    KEY idx_type (content_type),
    KEY idx_indexed (indexed_at)
) {charset_collate};

Content Types

Type Description
product WooCommerce product
page WordPress page
post WordPress post
category Product category
faq FAQ entries

Embedding Vector Storage

Vectors are stored as JSON-encoded float arrays:

{
    "embedding_vector": "[0.0123, -0.0456, 0.0789, ...]",
    "dimensions": 384
}

Note: For production with >10k products, Supabase pgvector is recommended for efficient similarity search.


wp_waa_playbook_sessions

Purpose: State management for multi-step guided conversation flows (playbooks).

CREATE TABLE {prefix}waa_playbook_sessions (
    id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
    session_id VARCHAR(64) NOT NULL,
    playbook_id BIGINT(20) UNSIGNED NOT NULL,
    current_step_index INT(11) NOT NULL DEFAULT 0,
    step_responses LONGTEXT DEFAULT NULL,
    collected_variables LONGTEXT DEFAULT NULL,
    status ENUM('active','paused','completed','expired','abandoned') NOT NULL DEFAULT 'active',
    started_at DATETIME NOT NULL,
    expires_at DATETIME NOT NULL,
    last_activity_at DATETIME NOT NULL,
    PRIMARY KEY (id),
    KEY idx_session_status (session_id, status),
    KEY idx_expires (expires_at),
    KEY idx_playbook_id (playbook_id),
    KEY idx_last_activity (last_activity_at)
) {charset_collate};

Status States

Status Description
active User currently in playbook flow
paused User temporarily left mid-flow
completed All steps successfully completed
expired 24-hour timeout exceeded
abandoned User explicitly exited or closed chat

State Management Example

{
    "session_id": "abc-123",
    "playbook_id": 42,
    "current_step_index": 2,
    "step_responses": [
        {"step": 0, "response": "Blue", "timestamp": "2025-12-15T10:00:00Z"},
        {"step": 1, "response": "Large", "timestamp": "2025-12-15T10:01:00Z"}
    ],
    "collected_variables": {
        "color": "Blue",
        "size": "Large",
        "email": "user@example.com"
    },
    "status": "active"
}

WordPress Options

Plugin configuration stored in wp_options table using WordPress Options API.

woo_ai_chatbot_settings

Type: JSON string Autoload: Yes

$settings = json_decode( get_option( 'woo_ai_chatbot_settings', '{}' ), true );

Structure

{
    "ai_provider": "gemini",
    "model": "gemini-2.5-flash-lite",
    "chat_enabled": true,
    "widget_position": "bottom-right",
    "max_session_duration": 3600,
    "max_conversation_history": 50,
    "enable_rag": true,
    "enable_analytics": true,
    "license_status": "active",
    "trial_expires": 1735689600
}

woo_ai_chatbot_version

Type: String Example: "0.1.0"

Tracks installed plugin version for upgrade routines.

woo_ai_chatbot_license

Type: Array (serialized)

[
    'key'          => 'encrypted_key_data',
    'key_hash'     => 'sha256_hash',
    'status'       => 'active',
    'type'         => 'remote',
    'plan'         => 'pro',
    'expires_at'   => '2026-12-31',
    'domain'       => 'domain_hash',
    'activated_at' => '2025-01-15 10:30:00',
    'checksum'     => 'integrity_checksum'
]

Widget Options

Individual options for backward compatibility:

Option Key Type Default Description
woo_ai_chat_enabled bool true Widget visibility
woo_ai_chat_position string bottom-right Widget placement
woo_ai_chat_welcome_message string Localized Initial greeting
woo_ai_chat_show_mobile bool true Mobile display
woo_ai_chat_show_desktop bool true Desktop display
woo_ai_chat_disable_cart_checkout bool false Hide on cart/checkout
woo_ai_chat_excluded_pages array [] Page IDs to exclude

Transients & Caching

Transients provide TTL-based caching with automatic object cache integration.

Usage Statistics Cache

// Key: woo_ai_chatbot_stats_{period}
// TTL: 1 hour
$stats = get_transient( 'woo_ai_chatbot_stats_daily' );

Provider Priority Cache

// Key: woo_ai_priority_order
// TTL: 1 hour
// Structure: ['gemini', 'claude', 'openai']
set_transient( self::PRIORITY_CACHE_KEY, $priority, HOUR_IN_SECONDS );

License Status Cache

// Key: woo_ai_license_status
// TTL: 72 hours
// Values: 'active', 'inactive', 'trial'
set_transient( self::CACHE_KEY, 'active', 72 * HOUR_IN_SECONDS );

Rate Limiting

// Key: woo_ai_chatbot_rate_{session_id}
// TTL: 60 seconds
// Tracks request count per session
$count = (int) get_transient( $cache_key );
set_transient( $cache_key, $count + 1, 60 );

Indexing Lock

// Key: woo_ai_chatbot_indexing
// TTL: 30 minutes
// Prevents concurrent indexing operations
set_transient( 'woo_ai_chatbot_indexing', true, 30 * MINUTE_IN_SECONDS );

Playbook Cache

// Key: woo_ai_playbooks_cache
// TTL: 1 hour
// Caches active playbook configurations
set_transient( self::CACHE_KEY, $playbooks, HOUR_IN_SECONDS );

Custom Post Types

waa_playbook

Post Type Slug: waa_playbook REST Base: /wp-json/wp/v2/waa-playbooks

register_post_type( 'waa_playbook', [
    'public'       => false,
    'show_in_rest' => true,
    'rest_base'    => 'waa-playbooks',
    'supports'     => ['title', 'custom-fields'],
    'capabilities' => ['manage_woocommerce'],
] );

Registered Meta Fields

Meta Key Type Description
_waa_intent string Trigger phrase/intent
_waa_priority integer Priority 0-100 (default: 50)
_waa_steps string (JSON) Step definitions array
_waa_conditions string (JSON) Activation conditions
_waa_trigger string auto or manual
_waa_status string draft or active

Steps JSON Structure

[
    {
        "id": "step_1",
        "type": "question",
        "message": "What color do you prefer?",
        "options": ["Red", "Blue", "Green"],
        "variable": "color",
        "required": true
    },
    {
        "id": "step_2",
        "type": "action",
        "action": "show_products",
        "filters": {"color": "{{color}}"}
    }
]

Data Migration

Version Tracking

Database schema version is tracked separately from plugin version:

private const DB_VERSION = '1.0.0';
private const DB_VERSION_OPTION = 'woo_ai_chatbot_db_version';

Migration Flow

public function create_tables() {
    $current = get_option( self::DB_VERSION_OPTION, '0.0.0' );

    if ( version_compare( $current, '1.0.0', '<' ) ) {
        // Initial schema creation
        $this->create_events_table();
        $this->create_sessions_table();
        // ...
    }

    if ( version_compare( $current, '1.1.0', '<' ) ) {
        // Add new column
        $this->migrate_to_1_1_0();
    }

    update_option( self::DB_VERSION_OPTION, self::DB_VERSION );
}

Adding New Columns

private function migrate_to_1_1_0() {
    global $wpdb;

    $table = $wpdb->prefix . 'waa_sessions';

    // Check if column exists
    $column_exists = $wpdb->get_var(
        "SHOW COLUMNS FROM {$table} LIKE 'sentiment_score'"
    );

    if ( ! $column_exists ) {
        $wpdb->query(
            "ALTER TABLE {$table}
             ADD COLUMN sentiment_score FLOAT NULL
             AFTER model_used"
        );
    }
}

Query Examples

Retrieve Session with Events

global $wpdb;
$prefix = $wpdb->prefix;

$session = $wpdb->get_row(
    $wpdb->prepare(
        "SELECT s.*,
                COUNT(e.id) as event_count,
                MAX(e.timestamp) as last_event
         FROM {$prefix}waa_sessions s
         LEFT JOIN {$prefix}waa_events e ON s.session_id = e.session_id
         WHERE s.session_id = %s
         GROUP BY s.id",
        $session_id
    ),
    ARRAY_A
);

Analytics: Conversion Funnel

$funnel = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT
            event_type,
            COUNT(*) as count,
            COUNT(DISTINCT session_id) as unique_sessions
         FROM {$prefix}waa_events
         WHERE timestamp >= %s
           AND event_type IN ('chat_started', 'product_clicked', 'cart_added', 'order_completed')
         GROUP BY event_type
         ORDER BY FIELD(event_type, 'chat_started', 'product_clicked', 'cart_added', 'order_completed')",
        $start_date
    ),
    ARRAY_A
);

Active Playbook Sessions

$active_sessions = $wpdb->get_results(
    $wpdb->prepare(
        "SELECT ps.*, p.post_title as playbook_name
         FROM {$prefix}waa_playbook_sessions ps
         JOIN {$prefix}posts p ON ps.playbook_id = p.ID
         WHERE ps.status = 'active'
           AND ps.expires_at > %s
         ORDER BY ps.last_activity_at DESC
         LIMIT %d",
        current_time( 'mysql' ),
        100
    ),
    ARRAY_A
);

Performance: Bulk Event Insert

// Use single INSERT with multiple VALUES for bulk operations
$values = [];
$placeholders = [];

foreach ( $events as $event ) {
    $placeholders[] = '(%s, %s, %s, %d, %s)';
    $values[] = $event['session_id'];
    $values[] = $event['event_type'];
    $values[] = wp_json_encode( $event['data'] );
    $values[] = $event['user_id'];
    $values[] = $event['timestamp'];
}

$sql = "INSERT INTO {$prefix}waa_events
        (session_id, event_type, event_data, user_id, timestamp)
        VALUES " . implode( ', ', $placeholders );

$wpdb->query( $wpdb->prepare( $sql, $values ) );

Cleanup: Remove Stale Data

// Remove expired sessions and their events atomically
$wpdb->query( 'START TRANSACTION' );

try {
    // Get expired session IDs
    $expired = $wpdb->get_col(
        $wpdb->prepare(
            "SELECT session_id FROM {$prefix}waa_sessions
             WHERE expires_at < %s",
            current_time( 'mysql' )
        )
    );

    if ( ! empty( $expired ) ) {
        $placeholders = implode( ',', array_fill( 0, count( $expired ), '%s' ) );

        // Delete related events
        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$prefix}waa_events
                 WHERE session_id IN ({$placeholders})",
                $expired
            )
        );

        // Delete sessions
        $wpdb->query(
            $wpdb->prepare(
                "DELETE FROM {$prefix}waa_sessions
                 WHERE session_id IN ({$placeholders})",
                $expired
            )
        );
    }

    $wpdb->query( 'COMMIT' );
} catch ( Exception $e ) {
    $wpdb->query( 'ROLLBACK' );
    throw $e;
}

Performance Considerations

Index Usage

Always verify queries use appropriate indexes:

EXPLAIN SELECT * FROM wp_waa_events
WHERE event_type = 'product_clicked'
AND timestamp > '2025-12-01';

Query Optimization Tips

  1. Use LIMIT: Always paginate large result sets
  2. *Avoid SELECT : Request only needed columns
  3. Prepared Statements: Always use $wpdb->prepare()
  4. Batch Operations: Use multi-row INSERT/DELETE
  5. Index Awareness: Structure WHERE clauses to match indexes

Recommended Table Sizes

Table Max Recommended Rows Action at Threshold
waa_events 1,000,000 Aggressive 30-day cleanup
waa_sessions 100,000 Enable hourly cleanup
waa_playbook_sessions 50,000 7-day retention

Security

SQL Injection Prevention

// ALWAYS use prepared statements
$result = $wpdb->get_var(
    $wpdb->prepare(
        "SELECT COUNT(*) FROM {$table} WHERE user_id = %d",
        $user_id
    )
);

// NEVER do this
$wpdb->query( "DELETE FROM {$table} WHERE id = {$id}" ); // UNSAFE

Data Sanitization

$session_id = sanitize_text_field( $_POST['session_id'] );
$user_id    = absint( $_POST['user_id'] );
$event_data = wp_json_encode( $data ); // Auto-escapes

Related Documentation


Author: WooAI Development Team Contact: leowebmark@gmail.com