DEV Community

Query Filter
Query Filter

Posted on

form10


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Offline SQL Formatter</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            background-color: #f5f5f5;
        }
        .container {
            max-width: 1200px;
            margin: 0 auto;
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0,0,0,0.1);
        }
        h1 {
            color: #333;
            text-align: center;
        }
        .editor-container {
            display: flex;
            gap: 20px;
            margin-bottom: 20px;
        }
        @media (max-width: 768px) {
            .editor-container {
                flex-direction: column;
            }
        }
        .editor-panel {
            flex: 1;
        }
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
            color: #555;
        }
        textarea {
            width: 100%;
            height: 300px;
            padding: 10px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-family: 'Courier New', monospace;
            font-size: 14px;
            resize: vertical;
        }
        .controls {
            margin: 20px 0;
            text-align: center;
        }
        button {
            background-color: #007cba;
            color: white;
            border: none;
            padding: 10px 20px;
            margin: 0 5px;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
        }
        button:hover {
            background-color: #005a87;
        }
        .options {
            margin: 10px 0;
            text-align: center;
        }
        select {
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            margin: 0 5px;
        }
        .status {
            text-align: center;
            margin: 10px 0;
            color: #666;
            font-style: italic;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>Offline SQL Formatter</h1>

        <div class="options">
            <label for="dialect">SQL Dialect:</label>
            <select id="dialect">
                <option value="sql">Standard SQL</option>
                <option value="mysql">MySQL</option>
                <option value="postgresql">PostgreSQL</option>
            </select>

            <label for="indent">Indent:</label>
            <select id="indent">
                <option value="2">2 spaces</option>
                <option value="4" selected>4 spaces</option>
                <option value="tab">Tabs</option>
            </select>
        </div>

        <div class="editor-container">
            <div class="editor-panel">
                <label for="inputSQL">Input SQL:</label>
                <textarea id="inputSQL" placeholder="Enter your SQL query here..."></textarea>
            </div>
            <div class="editor-panel">
                <label for="outputSQL">Formatted SQL:</label>
                <textarea id="outputSQL" readonly placeholder="Formatted SQL will appear here..."></textarea>
            </div>
        </div>

        <div class="controls">
            <button id="formatBtn">Format SQL</button>
            <button id="clearBtn">Clear</button>
            <button id="copyBtn">Copy Formatted</button>
        </div>

        <div class="status" id="status">Ready</div>
    </div>

    <script>
        // Simple and reliable SQL Formatter
        const SQLFormatter = (function() {
            'use strict';

            const reservedWords = [
                'SELECT', 'FROM', 'WHERE', 'GROUP BY', 'HAVING', 'ORDER BY', 'LIMIT', 
                'INSERT', 'UPDATE', 'DELETE', 'CREATE', 'ALTER', 'DROP', 'VALUES',
                'SET', 'AND', 'OR', 'NOT', 'IN', 'BETWEEN', 'LIKE', 'IS', 'NULL',
                'JOIN', 'LEFT JOIN', 'RIGHT JOIN', 'INNER JOIN', 'OUTER JOIN', 'ON',
                'AS', 'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'EXISTS', 'DISTINCT',
                'COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'ASC', 'DESC', 'UNION', 'ALL'
            ];

            function formatSQL(sql, options = {}) {
                const {
                    indent: indentStr = '    ',
                    uppercase = true
                } = options;

                // Step 1: Normalize the SQL - preserve strings and add basic spacing
                let formatted = sql
                    // Normalize whitespace
                    .replace(/\s+/g, ' ')
                    // Add space around operators but preserve dots and double operators
                    .replace(/([!=<>]=?|\|\|)/g, ' $1 ')
                    .replace(/([+\-*/%])/g, ' $1 ')
                    // Fix double spaces
                    .replace(/\s+/g, ' ')
                    .trim();

                // Step 2: Add newlines before major keywords
                reservedWords.forEach(keyword => {
                    const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
                    formatted = formatted.replace(regex, `\n${keyword}`);
                });

                // Step 3: Split into lines and apply indentation
                const lines = formatted.split('\n').filter(line => line.trim());
                let indentLevel = 0;
                const result = [];

                for (let i = 0; i < lines.length; i++) {
                    let line = lines[i].trim();
                    const upperLine = line.toUpperCase();

                    // Handle indentation levels
                    if (upperLine.startsWith('FROM') || 
                        upperLine.startsWith('WHERE') || 
                        upperLine.startsWith('GROUP BY') ||
                        upperLine.startsWith('HAVING') ||
                        upperLine.startsWith('ORDER BY') ||
                        upperLine.startsWith('LIMIT')) {
                        indentLevel = 1;
                    } else if (upperLine.startsWith('SELECT')) {
                        indentLevel = 1;
                    } else if (upperLine.startsWith('JOIN') || 
                               upperLine.startsWith('LEFT JOIN') || 
                               upperLine.startsWith('RIGHT JOIN') ||
                               upperLine.startsWith('INNER JOIN') ||
                               upperLine.startsWith('OUTER JOIN')) {
                        indentLevel = 2;
                    } else if (upperLine.startsWith('ON') || 
                               upperLine.startsWith('AND') || 
                               upperLine.startsWith('OR')) {
                        indentLevel = 3;
                    }

                    // Apply uppercase to keywords
                    if (uppercase) {
                        reservedWords.forEach(keyword => {
                            const regex = new RegExp(`\\b${keyword}\\b`, 'gi');
                            line = line.replace(regex, keyword.toUpperCase());
                        });
                    }

                    // Clean up the line
                    line = line
                        .replace(/\s*\.\s*/g, '.')
                        .replace(/\s*,\s*/g, ', ')
                        .replace(/\s*\(\s*/g, '(')
                        .replace(/\s*\)\s*/g, ')')
                        .replace(/\s*=\s*/g, ' = ')
                        .replace(/\s*!=\s*/g, ' != ')
                        .replace(/\s*<>\s*/g, ' <> ')
                        .replace(/\s*<=\s*/g, ' <= ')
                        .replace(/\s*>=\s*/g, ' >= ')
                        .replace(/\s*\|\|\s*/g, ' || ')
                        .replace(/\s*~\s*/g, ' ~ ')
                        .replace(/\s*\^\s*/g, ' ^ ')
                        .replace(/\s*&\s*/g, ' & ')
                        .replace(/\s*\|\s*/g, ' | ')
                        .replace(/\s*\+\s*/g, ' + ')
                        .replace(/\s*-\s*/g, ' - ')
                        .replace(/\s*\*\s*/g, ' * ')
                        .replace(/\s*\/\s*/g, ' / ')
                        .replace(/\s*%\s*/g, ' % ')
                        .replace(/\s+/g, ' ')
                        .trim();

                    result.push(indentStr.repeat(indentLevel) + line);
                }

                return result.join('\n');
            }

            return {
                format: formatSQL
            };
        })();

        // DOM handling
        document.addEventListener('DOMContentLoaded', function() {
            const inputSQL = document.getElementById('inputSQL');
            const outputSQL = document.getElementById('outputSQL');
            const formatBtn = document.getElementById('formatBtn');
            const clearBtn = document.getElementById('clearBtn');
            const copyBtn = document.getElementById('copyBtn');
            const dialectSelect = document.getElementById('dialect');
            const indentSelect = document.getElementById('indent');
            const status = document.getElementById('status');

            function formatSQL() {
                const sql = inputSQL.value.trim();
                if (!sql) {
                    outputSQL.value = '';
                    status.textContent = 'Please enter SQL to format';
                    return;
                }

                try {
                    const indent = indentSelect.value === 'tab' ? '\t' : ' '.repeat(parseInt(indentSelect.value));
                    const formatted = SQLFormatter.format(sql, {
                        indent: indent,
                        uppercase: true
                    });

                    outputSQL.value = formatted;
                    status.textContent = 'SQL formatted successfully';
                } catch (error) {
                    outputSQL.value = 'Error formatting SQL: ' + error.message;
                    status.textContent = 'Formatting failed';
                }
            }

            function clearAll() {
                inputSQL.value = '';
                outputSQL.value = '';
                status.textContent = 'Cleared';
            }

            function copyFormatted() {
                if (!outputSQL.value.trim()) {
                    status.textContent = 'No formatted SQL to copy';
                    return;
                }

                outputSQL.select();
                outputSQL.setSelectionRange(0, 99999);

                try {
                    navigator.clipboard.writeText(outputSQL.value);
                    status.textContent = 'Formatted SQL copied to clipboard';
                } catch (err) {
                    document.execCommand('copy');
                    status.textContent = 'Formatted SQL copied to clipboard';
                }
            }

            formatBtn.addEventListener('click', formatSQL);
            clearBtn.addEventListener('click', clearAll);
            copyBtn.addEventListener('click', copyFormatted);

            inputSQL.addEventListener('keydown', function(e) {
                if (e.ctrlKey && e.key === 'Enter') {
                    formatSQL();
                }
            });

            // Test SQL
            inputSQL.value = `SELECT c.first_name || ' ' || c.last_name as full_name, o.order_date, p.product_name, p.price * o.quantity as total_price FROM customers c INNER JOIN orders o ON c.customer_id = o.customer_id LEFT JOIN products p ON o.product_id = p.product_id WHERE o.order_date >= '2023-01-01' AND p.category = 'Electronics' ORDER BY o.order_date DESC, total_price ASC`;
        });
    </script>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Top comments (0)