// JavaScript für Query Builder Funktionalität // Globale Variablen let currentTables = []; let savedQueries = []; let currentConnection = 'oracle'; // Lade verfügbare Datenbankverbindungen function loadConnections() { fetch('/get_connections') .then(response => { if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } return response.json(); }) .then(data => { const selector = document.getElementById('database-selector'); if (!selector) { console.warn('Database selector nicht gefunden'); return; } selector.innerHTML = ''; if (data.success && data.connections && data.connections.length > 0) { data.connections.forEach(conn => { const option = document.createElement('option'); option.value = conn.key; option.textContent = `${conn.name} (${conn.type})`; option.title = conn.description; if (conn.key === 'oracle') { option.selected = true; currentConnection = 'oracle'; } selector.appendChild(option); }); // Lade Tabellen für die ausgewählte Verbindung loadTables(); } else { const option = document.createElement('option'); option.textContent = data.error || 'Keine Verbindungen verfügbar'; selector.appendChild(option); console.error('API-Fehler:', data.error); } }) .catch(error => { console.error('Netzwerk-Fehler beim Laden der Verbindungen:', error); const selector = document.getElementById('database-selector'); if (selector) { selector.innerHTML = ``; } }); } // Datenbankauswahl geändert function onDatabaseChange() { const selector = document.getElementById('database-selector'); if (selector) { currentConnection = selector.value; loadTables(); } } // Utility Functions function showLoading(elementId) { const element = document.getElementById(elementId); if (element) { element.innerHTML = '
Lade...
'; } } function showAlert(message, type = 'info') { const alertClass = type === 'error' ? 'alert-error-custom' : 'alert-success-custom'; const alertHtml = ` `; // Füge Alert am Anfang des Main-Containers hinzu const mainContent = document.querySelector('main .container-fluid'); if (mainContent) { const alertDiv = document.createElement('div'); alertDiv.innerHTML = alertHtml; mainContent.insertBefore(alertDiv.firstElementChild, mainContent.firstElementChild); } } // Tabellen laden async function loadTables() { showLoading('tables-list'); // Aktuelle Datenbankverbindung bestimmen const connectionSelector = document.getElementById('database-selector'); const connection = connectionSelector ? connectionSelector.value : 'oracle'; try { const response = await fetch(`/get_tables?connection=${connection}`); const data = await response.json(); if (data.success) { currentTables = data.tables; displayTables(data.tables); } else { document.getElementById('tables-list').innerHTML = `
Fehler: ${data.error}
`; } } catch (error) { document.getElementById('tables-list').innerHTML = `
Fehler beim Laden der Tabellen: ${error.message}
`; } } function displayTables(tables) { const tablesList = document.getElementById('tables-list'); if (tables.length === 0) { tablesList.innerHTML = '
Keine Tabellen gefunden
'; return; } const tablesHtml = tables.map(table => `
${table}
`).join(''); tablesList.innerHTML = tablesHtml; // Event-Listener für Hover-Effekte hinzufügen (nur wenn Spalten-Anzeige deaktiviert ist) setTimeout(() => attachTableHoverEvents(), 100); } // Tabelle auswählen und SELECT Query generieren async function selectTable(tableName) { const queryInput = document.getElementById('query-input'); const currentQuery = queryInput.value.trim(); // Wenn bereits eine Query vorhanden ist, frage nach if (currentQuery) { if (!confirm('Dies wird die aktuelle Query ersetzen. Fortfahren?')) { return; } } queryInput.value = `SELECT * FROM ${tableName} WHERE ROWNUM <= 100`; // Alle anderen Tabellen-Spalten verstecken document.querySelectorAll('.table-columns').forEach(col => { col.style.display = 'none'; }); // Aktuelle Tabelle als ausgewählt markieren document.querySelectorAll('.table-item').forEach(item => { item.classList.remove('selected'); }); // Die geklickte Tabelle markieren const tableItems = document.querySelectorAll('.table-item'); tableItems.forEach(item => { if (item.textContent.includes(tableName)) { item.classList.add('selected'); } }); // Spalten für die ausgewählte Tabelle laden und anzeigen await showTableColumns(tableName); } // Funktion zum dauerhaften Anzeigen der Tabellenspalten async function showTableColumns(tableName) { try { const connectionSelector = document.getElementById('database-selector'); const connection = connectionSelector ? connectionSelector.value : 'oracle'; const response = await fetch(`/get_table_schema/${tableName}?connection=${connection}`); const data = await response.json(); if (data.success) { const columnsContainer = document.getElementById(`columns-${tableName}`); if (columnsContainer) { const columnsHtml = data.schema.map(col => `
${col.name} ${col.type} ${col.primary_key ? '' : ''} ${col.not_null ? '' : ''}
`).join(''); columnsContainer.innerHTML = columnsHtml; // Nur anzeigen wenn Spalten-View aktiv ist columnsContainer.style.display = columnsVisible ? 'block' : 'none'; } } } catch (error) { console.error('Fehler beim Laden der Spalten:', error); } } // Funktion zum Einfügen einer Spalte in die Query function insertColumn(columnName) { const queryInput = document.getElementById('query-input'); const cursorPos = queryInput.selectionStart; const currentQuery = queryInput.value; // Wenn SELECT * vorhanden ist, ersetze es durch die spezifische Spalte if (currentQuery.includes('SELECT *')) { queryInput.value = currentQuery.replace('SELECT *', `SELECT ${columnName}`); } else { // Andernfalls füge die Spalte an der Cursor-Position ein const beforeCursor = currentQuery.substring(0, cursorPos); const afterCursor = currentQuery.substring(cursorPos); queryInput.value = beforeCursor + columnName + afterCursor; // Cursor-Position nach dem eingefügten Spaltennamen setzen queryInput.selectionStart = queryInput.selectionEnd = cursorPos + columnName.length; } queryInput.focus(); } // Drag & Drop Funktionen function handleTableDrag(event, tableName) { event.dataTransfer.setData('text/plain', `SELECT * FROM ${tableName}`); event.dataTransfer.setData('application/x-table-name', tableName); event.dataTransfer.effectAllowed = 'copy'; // Custom Drag Image const dragImage = document.createElement('div'); dragImage.innerHTML = ` ${tableName}`; dragImage.style.cssText = 'padding: 8px 12px; background: #007bff; color: white; border-radius: 4px; position: absolute; top: -1000px;'; document.body.appendChild(dragImage); event.dataTransfer.setDragImage(dragImage, 0, 0); setTimeout(() => document.body.removeChild(dragImage), 0); } function handleColumnDrag(event, tableName, columnName) { event.dataTransfer.setData('text/plain', `${tableName}.${columnName}`); event.dataTransfer.setData('application/x-column-name', columnName); event.dataTransfer.setData('application/x-table-name', tableName); event.dataTransfer.effectAllowed = 'copy'; // Custom Drag Image const dragImage = document.createElement('div'); dragImage.innerHTML = ` ${columnName}`; dragImage.style.cssText = 'padding: 6px 10px; background: #28a745; color: white; border-radius: 4px; position: absolute; top: -1000px; font-size: 12px;'; document.body.appendChild(dragImage); event.dataTransfer.setDragImage(dragImage, 0, 0); setTimeout(() => document.body.removeChild(dragImage), 0); } // Setup Drag & Drop für Query Input Feld function setupQueryInputDropZone(queryInput) { queryInput.addEventListener('dragover', function(event) { event.preventDefault(); event.dataTransfer.dropEffect = 'copy'; this.classList.add('drag-over'); }); queryInput.addEventListener('dragleave', function(event) { event.preventDefault(); this.classList.remove('drag-over'); }); queryInput.addEventListener('drop', function(event) { event.preventDefault(); this.classList.remove('drag-over'); const droppedText = event.dataTransfer.getData('text/plain'); const tableName = event.dataTransfer.getData('application/x-table-name'); const columnName = event.dataTransfer.getData('application/x-column-name'); if (droppedText) { // Cursor-Position ermitteln const cursorPos = this.selectionStart; const currentQuery = this.value; // Text an Cursor-Position einfügen const beforeCursor = currentQuery.substring(0, cursorPos); const afterCursor = currentQuery.substring(cursorPos); let insertText = droppedText; // Intelligentes Einfügen basierend auf Kontext if (columnName && !tableName.includes('.')) { // Wenn es eine Spalte ist, prüfe ob wir eine qualifizierte Referenz brauchen const needsTablePrefix = shouldUseTablePrefix(currentQuery, cursorPos); insertText = needsTablePrefix ? `${tableName}.${columnName}` : columnName; } // Füge Leerzeichen hinzu wenn nötig if (beforeCursor && !beforeCursor.endsWith(' ') && !beforeCursor.endsWith('\n')) { insertText = ' ' + insertText; } if (afterCursor && !afterCursor.startsWith(' ') && !afterCursor.startsWith('\n')) { insertText = insertText + ' '; } this.value = beforeCursor + insertText + afterCursor; // Cursor nach dem eingefügten Text positionieren const newCursorPos = cursorPos + insertText.length; this.selectionStart = this.selectionEnd = newCursorPos; // Focus setzen this.focus(); // Auto-resize triggern this.style.height = 'auto'; this.style.height = Math.max(150, this.scrollHeight) + 'px'; } }); } // Hilfsfunktion um zu bestimmen ob ein Tabellenpräfix nötig ist function shouldUseTablePrefix(query, cursorPos) { const beforeCursor = query.substring(0, cursorPos).toUpperCase(); // Wenn mehrere FROM-Klauseln vorhanden sind, verwende Tabellenpräfix const fromCount = (beforeCursor.match(/\bFROM\b/g) || []).length; const joinCount = (beforeCursor.match(/\bJOIN\b/g) || []).length; return fromCount > 1 || joinCount > 0; } // Event-Listener für Hover-Effekte an Tabellen-Items anhängen function attachTableHoverEvents() { const tableItems = document.querySelectorAll('.table-item'); tableItems.forEach(item => { const tableName = item.getAttribute('data-table'); // Entferne vorherige Event-Listener item.removeEventListener('mouseenter', item._hoverHandler); item.removeEventListener('mouseleave', item._leaveHandler); // Neue Event-Listener nur hinzufügen, wenn Spalten-Anzeige deaktiviert ist if (!columnsVisible) { item._hoverHandler = () => showTableSchema(tableName, item); item._leaveHandler = () => hideTableSchema(); item.addEventListener('mouseenter', item._hoverHandler); item.addEventListener('mouseleave', item._leaveHandler); } }); } // Tabellenschema anzeigen async function showTableSchema(tableName, element) { try { // Aktuelle Datenbankverbindung bestimmen const connectionSelector = document.getElementById('database-selector'); const connection = connectionSelector ? connectionSelector.value : 'demo'; const response = await fetch(`/get_table_schema/${tableName}?connection=${connection}`); const data = await response.json(); if (data.success) { const rect = element.getBoundingClientRect(); const schemaHtml = data.schema.map(col => `
${col.name} ${col.type} ${col.primary_key ? '' : ''} ${col.not_null ? '' : ''}
`).join(''); const popup = document.createElement('div'); popup.className = 'schema-popup'; popup.id = 'schema-popup'; popup.innerHTML = `
${tableName}
${schemaHtml}`; popup.style.left = (rect.right + 10) + 'px'; popup.style.top = rect.top + 'px'; document.body.appendChild(popup); } } catch (error) { console.error('Fehler beim Laden des Schemas:', error); } } function hideTableSchema() { const popup = document.getElementById('schema-popup'); if (popup) { popup.remove(); } } // Query ausführen async function executeQuery() { const queryInput = document.getElementById('query-input'); const query = queryInput.value.trim(); if (!query) { showAlert('Bitte geben Sie eine Query ein', 'error'); return; } const resultsContainer = document.getElementById('results-container'); resultsContainer.innerHTML = `

Query wird ausgeführt...

`; try { // Aktuelle Datenbankverbindung bestimmen const connectionSelector = document.getElementById('database-selector'); const connection = connectionSelector ? connectionSelector.value : 'oracle'; const response = await fetch('/execute_query', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ query: query, connection: connection }) }); const data = await response.json(); if (data.success) { displayQueryResults(data.results); showAlert('Query erfolgreich ausgeführt'); } else { resultsContainer.innerHTML = `
Query-Fehler:
${data.error}
`; } } catch (error) { resultsContainer.innerHTML = `
Netzwerk-Fehler:

${error.message}

`; } } function displayQueryResults(results) { const resultsContainer = document.getElementById('results-container'); if (results.message) { // Für INSERT, UPDATE, DELETE etc. resultsContainer.innerHTML = `
${results.message}
`; return; } if (!results.data || results.data.length === 0) { resultsContainer.innerHTML = `
Query erfolgreich, aber keine Ergebnisse gefunden.
`; return; } // Erstelle Tabelle mit Ergebnissen const tableHtml = `
${results.row_count} Zeile(n) gefunden
${results.columns.map(col => ``).join('')} ${results.data.map(row => ` ${row.map(cell => ``).join('')} `).join('')}
${col}
${cell !== null ? cell : 'NULL'}
`; resultsContainer.innerHTML = tableHtml; } // Query speichern function showSaveQueryModal(event) { if (event) { event.preventDefault(); event.stopPropagation(); } const queryInput = document.getElementById('query-input'); const query = queryInput.value.trim(); if (!query) { showAlert('Bitte geben Sie eine Query ein', 'error'); return false; } // Reset des Modal-Formulars const queryNameInput = document.getElementById('query-name'); if (queryNameInput) { queryNameInput.value = ''; } // Modal anzeigen - alternative Methode const modalElement = document.getElementById('saveQueryModal'); if (modalElement) { // Prüfe ob Bootstrap verfügbar ist if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { const modal = new bootstrap.Modal(modalElement, { backdrop: true, keyboard: true, focus: true }); modal.show(); // Fokus auf Name-Input setzen nach Modal-Öffnung modalElement.addEventListener('shown.bs.modal', function() { const nameInput = document.getElementById('query-name'); if (nameInput) { nameInput.focus(); } }, { once: true }); } else { // Fallback: Zeige Modal manuell modalElement.style.display = 'block'; modalElement.classList.add('show'); modalElement.setAttribute('aria-modal', 'true'); modalElement.setAttribute('role', 'dialog'); document.body.classList.add('modal-open'); // Backdrop hinzufügen const backdrop = document.createElement('div'); backdrop.className = 'modal-backdrop fade show'; backdrop.id = 'manual-modal-backdrop'; document.body.appendChild(backdrop); } } return false; } // Funktion zum manuellen Schließen des Modals (Fallback) function closeModalManually() { const modalElement = document.getElementById('saveQueryModal'); const backdrop = document.getElementById('manual-modal-backdrop'); if (modalElement) { modalElement.style.display = 'none'; modalElement.classList.remove('show'); modalElement.removeAttribute('aria-modal'); modalElement.removeAttribute('role'); document.body.classList.remove('modal-open'); } if (backdrop) { backdrop.remove(); } } async function saveQuery() { const name = document.getElementById('query-name').value.trim(); const query = document.getElementById('query-input').value.trim(); if (!name || !query) { showAlert('Name und Query sind erforderlich', 'error'); return; } try { const response = await fetch('/save_query', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: name, description: '', // Keine Beschreibung mehr query: query }) }); const data = await response.json(); if (data.success) { showAlert('Query erfolgreich gespeichert'); // Modal schließen if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { const modal = bootstrap.Modal.getInstance(document.getElementById('saveQueryModal')); if (modal) { modal.hide(); } else { closeModalManually(); } } else { closeModalManually(); } // Input-Feld zurücksetzen document.getElementById('query-name').value = ''; // Seite neu laden um gespeicherte Queries zu aktualisieren setTimeout(() => window.location.reload(), 1000); } else { showAlert(data.error, 'error'); } } catch (error) { showAlert(`Fehler beim Speichern: ${error.message}`, 'error'); } } // Gespeicherte Query laden function loadSavedQuery(queryId, queryText) { const queryInput = document.getElementById('query-input'); if (queryInput.value.trim() && !confirm('Dies wird die aktuelle Query ersetzen. Fortfahren?')) { return; } queryInput.value = queryText; } // Gespeicherte Query löschen async function deleteSavedQuery(queryId) { if (!confirm('Möchten Sie diese gespeicherte Query wirklich löschen?')) { return; } try { const response = await fetch(`/delete_query/${queryId}`, { method: 'DELETE' }); const data = await response.json(); if (data.success) { showAlert('Query erfolgreich gelöscht'); // Query-Element entfernen const queryCard = document.querySelector(`[data-query-id="${queryId}"]`); if (queryCard) { queryCard.remove(); } } else { showAlert(data.error, 'error'); } } catch (error) { showAlert(`Fehler beim Löschen: ${error.message}`, 'error'); } } // Neue Funktionen für die verbesserte Query-Verwaltung // Gespeicherte Queries filtern function filterSavedQueries() { const searchTerm = document.getElementById('search-queries').value.toLowerCase(); const queryCards = document.querySelectorAll('.saved-query-card'); queryCards.forEach(card => { const queryName = card.querySelector('h6').textContent.toLowerCase(); const queryText = card.dataset.queryText ? card.dataset.queryText.toLowerCase() : ''; if (queryName.includes(searchTerm) || queryText.includes(searchTerm)) { card.style.display = 'block'; } else { card.style.display = 'none'; } }); } // Query-Name bearbeiten let currentEditQueryId = null; function editQueryName(queryId, currentName) { currentEditQueryId = queryId; document.getElementById('edit-query-name').value = currentName; // Modal öffnen if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { const modal = new bootstrap.Modal(document.getElementById('editQueryModal')); modal.show(); } else { document.getElementById('editQueryModal').style.display = 'block'; } } async function updateQueryName() { const newName = document.getElementById('edit-query-name').value.trim(); if (!newName) { showAlert('Name ist erforderlich', 'error'); return; } try { const response = await fetch(`/update_query_name/${currentEditQueryId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: newName }) }); const data = await response.json(); if (data.success) { showAlert('Query-Name erfolgreich aktualisiert'); // Modal schließen if (typeof bootstrap !== 'undefined' && bootstrap.Modal) { const modal = bootstrap.Modal.getInstance(document.getElementById('editQueryModal')); if (modal) { modal.hide(); } } else { document.getElementById('editQueryModal').style.display = 'none'; } // Seite neu laden setTimeout(() => window.location.reload(), 1000); } else { showAlert(data.error, 'error'); } } catch (error) { showAlert(`Fehler beim Aktualisieren: ${error.message}`, 'error'); } } // Query duplizieren async function duplicateQuery(queryId, queryName, queryText) { const newName = prompt(`Neuer Name für die Kopie von "${queryName}":`, `${queryName} (Kopie)`); if (!newName || newName.trim() === '') { return; } try { const response = await fetch('/save_query', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ name: newName.trim(), description: '', query: queryText }) }); const data = await response.json(); if (data.success) { showAlert('Query erfolgreich dupliziert'); setTimeout(() => window.location.reload(), 1000); } else { showAlert(data.error, 'error'); } } catch (error) { showAlert(`Fehler beim Duplizieren: ${error.message}`, 'error'); } } // Query per Name ausführen (für API) async function executeQueryByName(queryName) { try { // Öffne Ergebnisse in neuem Tab als CSV window.open(`/api/queries/${encodeURIComponent(queryName)}/export/csv`, '_blank'); } catch (error) { showAlert(`Fehler beim Export: ${error.message}`, 'error'); } } // Query löschen function clearQuery() { if (confirm('Möchten Sie die aktuelle Query löschen?')) { document.getElementById('query-input').value = ''; } } // Ergebnisse exportieren function exportResults(format) { const query = document.getElementById('query-input').value.trim(); if (!query) { showAlert('Keine Query zum Exportieren vorhanden', 'error'); return; } // Erstelle temporäre Form für Export const form = document.createElement('form'); form.method = 'POST'; form.action = '/execute_query'; form.style.display = 'none'; const queryField = document.createElement('input'); queryField.name = 'query'; queryField.value = query; const formatField = document.createElement('input'); formatField.name = 'format'; formatField.value = format; form.appendChild(queryField); form.appendChild(formatField); document.body.appendChild(form); form.submit(); document.body.removeChild(form); } // Keyboard Shortcuts document.addEventListener('keydown', function(e) { // Ctrl+Enter oder Cmd+Enter für Query ausführen if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') { e.preventDefault(); executeQuery(); } // Ctrl+S oder Cmd+S für Query speichern if ((e.ctrlKey || e.metaKey) && e.key === 's') { e.preventDefault(); showSaveQueryModal(); } }); // Auto-resize für Textarea document.addEventListener('DOMContentLoaded', function() { // Lade Verbindungen beim Seitenstart loadConnections(); // Spalten-Anzeige basierend auf gespeicherten Einstellungen initialisieren updateColumnsView(); const queryInput = document.getElementById('query-input'); if (queryInput) { queryInput.addEventListener('input', function() { this.style.height = 'auto'; this.style.height = Math.max(150, this.scrollHeight) + 'px'; }); // Drag & Drop Event-Listener für Query-Input setupQueryInputDropZone(queryInput); } // Event-Listener für Save Query Buttons const saveQueryBtn = document.getElementById('save-query-btn'); if (saveQueryBtn) { saveQueryBtn.addEventListener('click', function(event) { event.preventDefault(); event.stopPropagation(); showSaveQueryModal(event); }); } const saveQueryBtnSidebar = document.getElementById('save-query-btn-sidebar'); if (saveQueryBtnSidebar) { saveQueryBtnSidebar.addEventListener('click', function(event) { event.preventDefault(); event.stopPropagation(); showSaveQueryModal(event); }); } }); // Globale Variable für Spalten-Anzeige Status let columnsVisible = localStorage.getItem('columnsVisible') !== 'false'; // Standard: true // Funktion zum Ein-/Ausblenden der Spalten-Anzeige function toggleColumnsView() { columnsVisible = !columnsVisible; localStorage.setItem('columnsVisible', columnsVisible.toString()); updateColumnsView(); } // Funktion zum Aktualisieren der Spalten-Anzeige basierend auf dem Status function updateColumnsView() { const toggleBtn = document.getElementById('toggle-columns-btn'); const allColumns = document.querySelectorAll('.table-columns'); if (columnsVisible) { // Spalten anzeigen allColumns.forEach(col => { if (col.parentElement.querySelector('.table-item.selected')) { col.style.display = 'block'; } }); if (toggleBtn) { toggleBtn.innerHTML = ''; toggleBtn.classList.remove('btn-outline-secondary'); toggleBtn.classList.add('btn-outline-primary'); toggleBtn.title = 'Spalten ausblenden'; } } else { // Spalten verstecken allColumns.forEach(col => { col.style.display = 'none'; }); if (toggleBtn) { toggleBtn.innerHTML = ''; toggleBtn.classList.remove('btn-outline-primary'); toggleBtn.classList.add('btn-outline-secondary'); toggleBtn.title = 'Spalten einblenden'; } } // Hover-Events entsprechend aktualisieren attachTableHoverEvents(); // Eventuelle Popups schließen hideTableSchema(); } // ============= Autocomplete Funktionalität ============= let autocompleteState = { isVisible: false, selectedIndex: -1, items: [], triggerPosition: 0, type: null // 'table' oder 'column' }; // Initialisiere Autocomplete Event-Listener function initializeAutocomplete() { const queryInput = document.getElementById('query-input'); const autocompleteDropdown = document.getElementById('autocomplete-dropdown'); if (!queryInput || !autocompleteDropdown) return; // Event-Listener für Textänderungen queryInput.addEventListener('input', handleQueryInputChange); queryInput.addEventListener('keydown', handleQueryInputKeydown); // Event-Listener zum Schließen bei Klick außerhalb document.addEventListener('click', function(event) { if (!queryInput.contains(event.target) && !autocompleteDropdown.contains(event.target)) { hideAutocomplete(); } }); } // Behandle Textänderungen im Query-Input function handleQueryInputChange(event) { const input = event.target; const value = input.value; const cursorPosition = input.selectionStart; // Prüfe ob Spalten-Autocomplete nach SELECT angezeigt werden soll if (shouldShowColumnAutocomplete(value, cursorPosition)) { showColumnAutocomplete(value, cursorPosition); } // Prüfe ob Tabellen-Autocomplete nach FROM angezeigt werden soll else if (shouldShowTableAutocomplete(value, cursorPosition)) { showTableAutocomplete(cursorPosition); } else { hideAutocomplete(); } } // Behandle Tastatureingaben im Query-Input function handleQueryInputKeydown(event) { if (!autocompleteState.isVisible) return; switch (event.key) { case 'ArrowDown': event.preventDefault(); moveAutocompleteSelection(1); break; case 'ArrowUp': event.preventDefault(); moveAutocompleteSelection(-1); break; case 'Enter': case 'Tab': event.preventDefault(); selectAutocompleteItem(); break; case 'Escape': hideAutocomplete(); break; } } // Prüfe ob Spalten-Autocomplete angezeigt werden soll function shouldShowColumnAutocomplete(text, cursorPosition) { const textBeforeCursor = text.substring(0, cursorPosition).toUpperCase(); console.log('shouldShowColumnAutocomplete checking:', textBeforeCursor); // Debug // Suche nach SELECT gefolgt von optionalem Leerzeichen const selectMatch = textBeforeCursor.match(/\bSELECT\s*$/); if (selectMatch) { console.log('SELECT pattern matched: SELECT at end'); // Debug autocompleteState.triggerPosition = cursorPosition; return true; } // Suche nach SELECT gefolgt von Spalten (mit Kommas) und angefangener neuer Spalte const columnMatch = textBeforeCursor.match(/\bSELECT\s+((?:[A-Z_0-9*]+(?:\s*,\s*)?)*)\s*([A-Z_0-9]*)$/); if (columnMatch) { console.log('SELECT pattern matched: column selection'); // Debug autocompleteState.triggerPosition = cursorPosition - (columnMatch[2] ? columnMatch[2].length : 0); return true; } // Suche nach Komma in der SELECT-Klausel (vor FROM) const commaMatch = textBeforeCursor.match(/\bSELECT\s+[^,]+(?:\s*,\s*[^,]*)*\s*,\s*([A-Z_0-9]*)$/); if (commaMatch && !textBeforeCursor.includes(' FROM ')) { console.log('SELECT pattern matched: after comma, before FROM'); // Debug autocompleteState.triggerPosition = cursorPosition - (commaMatch[1] ? commaMatch[1].length : 0); return true; } console.log('No SELECT pattern matched'); // Debug return false; } // Prüfe ob Tabellen-Autocomplete angezeigt werden soll function shouldShowTableAutocomplete(text, cursorPosition) { // Extrahiere Text bis zur aktuellen Cursor-Position const textBeforeCursor = text.substring(0, cursorPosition).toUpperCase(); // Suche nach "FROM" gefolgt von optionalem Leerzeichen const fromMatch = textBeforeCursor.match(/\bFROM\s*$/); if (fromMatch) { autocompleteState.triggerPosition = cursorPosition; return true; } // Suche nach "FROM" gefolgt von angefangenem Tabellennamen const partialMatch = textBeforeCursor.match(/\bFROM\s+([A-Z_0-9]*)$/); if (partialMatch) { autocompleteState.triggerPosition = cursorPosition - partialMatch[1].length; return true; } return false; } // Zeige Spalten-Autocomplete an function showColumnAutocomplete(text, cursorPosition) { const queryInput = document.getElementById('query-input'); const dropdown = document.getElementById('autocomplete-dropdown'); console.log('showColumnAutocomplete called for text:', text.substring(0, cursorPosition)); // Debug // Prüfe ob wir eine FROM-Klausel haben const textBeforeCursor = text.substring(0, cursorPosition).toUpperCase(); const hasFromClause = /\bFROM\s+[A-Z_][A-Z0-9_]*/.test(textBeforeCursor); console.log('Has FROM clause:', hasFromClause); // Debug if (hasFromClause) { // Wenn FROM-Klausel vorhanden, zeige Spalten der spezifischen Tabelle const tableName = extractTableNameFromQuery(text); console.log('Extracted table name:', tableName); // Debug if (tableName) { console.log('Loading schema for specific table:', tableName); // Debug loadTableSchemaForAutocomplete(tableName, text, cursorPosition); return; } } // WICHTIG: Auch ohne FROM-Klausel alle verfügbaren Spalten anzeigen console.log('Loading all columns for autocomplete (no FROM clause or no table found)'); // Debug // Fallback: Verwende einfach die erste verfügbare Tabelle als Beispiel if (currentTables && currentTables.length > 0) { console.log('Using first table as fallback:', currentTables[0]); // Debug loadTableSchemaForAutocomplete(currentTables[0], text, cursorPosition); } else { console.log('No tables available, showing generic'); // Debug showGenericColumnAutocomplete(text, cursorPosition); } } // Zeige allgemeine Spalten-Optionen function showGenericColumnAutocomplete(text, cursorPosition) { const dropdown = document.getElementById('autocomplete-dropdown'); // Filtere basierend auf bereits getipptem Text const textBeforeCursor = text.substring(0, cursorPosition).toUpperCase(); const columnMatch = textBeforeCursor.match(/(?:SELECT\s+(?:[^,]+,\s*)*|,\s*)([A-Z_0-9]*)$/); const searchTerm = columnMatch ? columnMatch[1] : ''; // Standard-Spalten-Optionen const genericColumns = ['*', 'COUNT(*)', 'COUNT(1)', 'ROWNUM', 'SYSDATE']; const filteredColumns = genericColumns.filter(col => col.includes(searchTerm) ); if (filteredColumns.length === 0) { hideAutocomplete(); return; } let dropdownHtml = '
Allgemeine Spalten
'; filteredColumns.forEach((column, index) => { dropdownHtml += `
${column}
`; }); dropdown.innerHTML = dropdownHtml; dropdown.style.display = 'block'; autocompleteState.isVisible = true; autocompleteState.selectedIndex = -1; autocompleteState.items = filteredColumns; autocompleteState.type = 'column'; } // Lade alle verfügbaren Spalten für Autocomplete async function loadAllColumnsForAutocomplete(text, cursorPosition) { try { console.log('loadAllColumnsForAutocomplete called'); // Debug console.log('currentTables:', currentTables); // Debug console.log('currentConnection:', currentConnection); // Debug if (!currentTables || currentTables.length === 0) { console.log('No tables available, showing generic'); // Debug showGenericColumnAutocomplete(text, cursorPosition); return; } console.log('Loading schemas for', currentTables.length, 'tables'); // Debug // Sammle alle Spalten von allen Tabellen const allColumns = new Map(); // Map für Spaltenname -> Liste von Tabellen let loadedCount = 0; const maxTables = 5; // Reduziere auf 5 Tabellen für bessere Performance // Teste zuerst eine einzelne Tabelle const testTable = currentTables[0]; console.log('Testing single table first:', testTable); // Debug try { const testResponse = await fetch(`/get_table_schema/${testTable}?connection=${currentConnection}`); const testData = await testResponse.json(); console.log('Test response for', testTable, ':', testData); // Debug if (!testData.success) { console.error('Test table schema failed:', testData); // Debug showGenericColumnAutocomplete(text, cursorPosition); return; } } catch (error) { console.error('Test table request failed:', error); // Debug showGenericColumnAutocomplete(text, cursorPosition); return; } // Wenn Test erfolgreich, lade alle Tabellen const promises = currentTables.slice(0, maxTables).map(async (tableName) => { try { console.log('Loading schema for:', tableName); // Debug const response = await fetch(`/get_table_schema/${tableName}?connection=${currentConnection}`); if (!response.ok) { console.warn(`HTTP ${response.status} for table ${tableName}`); // Debug return; } const data = await response.json(); console.log(`Schema response for ${tableName}:`, data); // Debug if (data.success && data.columns && Array.isArray(data.columns)) { console.log(`Found ${data.columns.length} columns in ${tableName}`); // Debug data.columns.forEach(column => { const columnName = column.name || column.COLUMN_NAME || column; if (columnName && typeof columnName === 'string') { if (!allColumns.has(columnName)) { allColumns.set(columnName, []); } allColumns.get(columnName).push({ table: tableName, type: column.type || column.DATA_TYPE || 'unknown' }); } }); } else { console.warn(`No valid columns found for table ${tableName}:`, data); // Debug } loadedCount++; } catch (error) { console.warn(`Fehler beim Laden von Tabelle ${tableName}:`, error); } }); await Promise.all(promises); console.log(`Loaded ${loadedCount} tables, found ${allColumns.size} unique columns`); // Debug console.log('Sample columns:', Array.from(allColumns.keys()).slice(0, 10)); // Debug if (allColumns.size > 0) { showAllColumnsAutocomplete(allColumns, text, cursorPosition); } else { console.log('No columns found, falling back to generic'); // Debug showGenericColumnAutocomplete(text, cursorPosition); } } catch (error) { console.error('Fehler beim Laden aller Spalten:', error); showGenericColumnAutocomplete(text, cursorPosition); } } // Lade Schema für Autocomplete async function loadTableSchemaForAutocomplete(tableName, text, cursorPosition) { try { console.log('Loading schema for table:', tableName); // Debug // Versuche zuerst ohne connection Parameter let url = `/get_table_schema/${tableName}`; let response = await fetch(url); // Falls das fehlschlägt, versuche mit connection Parameter if (!response.ok && currentConnection) { console.log('Trying with connection parameter:', currentConnection); // Debug url = `/get_table_schema/${tableName}?connection=${currentConnection}`; response = await fetch(url); } if (!response.ok) { console.warn('HTTP Error:', response.status, response.statusText); // Debug showGenericColumnAutocomplete(text, cursorPosition); return; } // Prüfe ob es eine HTML-Antwort ist (Login-Redirect) const contentType = response.headers.get('content-type'); if (contentType && contentType.includes('text/html')) { console.warn('Received HTML response (probably login redirect)'); // Debug showGenericColumnAutocomplete(text, cursorPosition); return; } const data = await response.json(); console.log('Schema response for', tableName, ':', data); // Debug // Prüfe verschiedene Schlüssel für Spalten-Daten let columns = null; if (data.success) { columns = data.columns || data.schema || data.fields; } if (columns && Array.isArray(columns) && columns.length > 0) { console.log('Found', columns.length, 'columns for', tableName); // Debug // Normalisiere die Spalten-Daten (verschiedene Formate unterstützen) const normalizedColumns = columns.map(col => { if (typeof col === 'string') { return { name: col, type: 'unknown' }; } else if (typeof col === 'object') { return { name: col.name || col.COLUMN_NAME || col.column_name || 'unknown', type: col.type || col.DATA_TYPE || col.data_type || 'unknown' }; } return { name: 'unknown', type: 'unknown' }; }); console.log('Normalized columns sample:', normalizedColumns.slice(0, 3)); // Debug showTableColumnsAutocomplete(normalizedColumns, text, cursorPosition, tableName); } else { console.warn('No valid columns found in response:', data); // Debug showGenericColumnAutocomplete(text, cursorPosition); } } catch (error) { console.error('Error loading schema for', tableName, ':', error); showGenericColumnAutocomplete(text, cursorPosition); } } // Zeige alle verfügbaren Spalten im Autocomplete function showAllColumnsAutocomplete(allColumns, text, cursorPosition) { const dropdown = document.getElementById('autocomplete-dropdown'); console.log('showAllColumnsAutocomplete called with', allColumns.size, 'unique columns'); // Debug // Filtere Spalten basierend auf bereits getipptem Text const textBeforeCursor = text.substring(0, cursorPosition).toUpperCase(); const columnMatch = textBeforeCursor.match(/(?:SELECT\s+(?:[^,]+,\s*)*|,\s*)([A-Z_0-9]*)$/); const searchTerm = columnMatch ? columnMatch[1] : ''; console.log('Search term:', searchTerm); // Debug // Konvertiere Map zu Array und sortiere const columnList = Array.from(allColumns.entries()) .filter(([columnName]) => columnName.toUpperCase().includes(searchTerm)) .sort(([a], [b]) => a.localeCompare(b)) .slice(0, 20); // Limitiere auf 20 Einträge console.log('Filtered to', columnList.length, 'columns'); // Debug if (columnList.length === 0) { console.log('No columns found, showing generic'); // Debug showGenericColumnAutocomplete(text, cursorPosition); return; } let dropdownHtml = '
Alle verfügbaren Spalten
'; columnList.forEach(([columnName, tables], index) => { const firstTable = tables[0]; const moreTablesCount = tables.length - 1; const tableInfo = moreTablesCount > 0 ? ` (${firstTable.table} +${moreTablesCount} weitere)` : ` (${firstTable.table})`; dropdownHtml += `
${columnName} ${tableInfo}
`; }); dropdown.innerHTML = dropdownHtml; dropdown.style.display = 'block'; autocompleteState.isVisible = true; autocompleteState.selectedIndex = -1; autocompleteState.items = columnList.map(([columnName]) => columnName); autocompleteState.type = 'column'; console.log('Autocomplete dropdown shown with', columnList.length, 'items'); // Debug } // Zeige Tabellen-Spalten im Autocomplete function showTableColumnsAutocomplete(columns, text, cursorPosition, tableName) { const dropdown = document.getElementById('autocomplete-dropdown'); // Filtere Spalten basierend auf bereits getipptem Text const textBeforeCursor = text.substring(0, cursorPosition).toUpperCase(); const columnMatch = textBeforeCursor.match(/(?:SELECT\s+(?:[^,]+,\s*)*|,\s*)([A-Z_0-9]*)$/); const searchTerm = columnMatch ? columnMatch[1] : ''; // Füge * als erste Option hinzu const allColumns = ['*', ...columns.map(col => col.name)]; const filteredColumns = allColumns.filter(col => col.toUpperCase().includes(searchTerm) ).slice(0, 15); // Limitiere auf 15 Einträge if (filteredColumns.length === 0) { hideAutocomplete(); return; } let dropdownHtml = `
Spalten von ${tableName}
`; filteredColumns.forEach((column, index) => { const columnInfo = columns.find(col => col.name === column); const icon = column === '*' ? 'fas fa-asterisk' : 'fas fa-columns'; const typeInfo = columnInfo ? ` (${columnInfo.type})` : ''; dropdownHtml += `
${column}${typeInfo}
`; }); dropdown.innerHTML = dropdownHtml; dropdown.style.display = 'block'; autocompleteState.isVisible = true; autocompleteState.selectedIndex = -1; autocompleteState.items = filteredColumns; autocompleteState.type = 'column'; } // Zeige Tabellen-Autocomplete an function showTableAutocomplete(cursorPosition) { const queryInput = document.getElementById('query-input'); const dropdown = document.getElementById('autocomplete-dropdown'); if (!currentTables || currentTables.length === 0) { hideAutocomplete(); return; } // Filtere Tabellen basierend auf bereits getipptem Text const textBeforeCursor = queryInput.value.substring(0, cursorPosition).toUpperCase(); const partialMatch = textBeforeCursor.match(/\bFROM\s+([A-Z_0-9]*)$/); const searchTerm = partialMatch ? partialMatch[1] : ''; const filteredTables = currentTables.filter(table => table.toUpperCase().includes(searchTerm) ).slice(0, 10); // Limitiere auf 10 Einträge if (filteredTables.length === 0) { hideAutocomplete(); return; } // Erstelle Dropdown-Inhalt let dropdownHtml = '
Verfügbare Tabellen
'; filteredTables.forEach((table, index) => { dropdownHtml += `
${table}
`; }); dropdown.innerHTML = dropdownHtml; dropdown.style.display = 'block'; autocompleteState.isVisible = true; autocompleteState.selectedIndex = -1; autocompleteState.items = filteredTables; autocompleteState.type = 'table'; } // Extrahiere Tabellenname aus Query function extractTableNameFromQuery(text) { try { const upperText = text.toUpperCase(); // Verschiedene Patterns für FROM-Erkennung const patterns = [ /\bFROM\s+([A-Z_][A-Z0-9_]*)/, // Standard: FROM TABLE /\bFROM\s+([A-Z_][A-Z0-9_]*)\s+[A-Z_]/, // Mit Alias: FROM TABLE ALIAS /\bFROM\s+([A-Z_][A-Z0-9_]*)\s*,/, // Mit weiteren Tabellen: FROM TABLE, /\bFROM\s+([A-Z_][A-Z0-9_]*)\s*$/, // Am Ende: FROM TABLE$ /\bFROM\s+([A-Z_][A-Z0-9_]*)\s+WHERE/, // Mit WHERE: FROM TABLE WHERE /\bFROM\s+([A-Z_][A-Z0-9_]*)\s+ORDER/, // Mit ORDER: FROM TABLE ORDER /\bFROM\s+([A-Z_][A-Z0-9_]*)\s+GROUP/ // Mit GROUP: FROM TABLE GROUP ]; for (const pattern of patterns) { const match = upperText.match(pattern); if (match && match[1]) { return match[1]; } } return null; } catch (error) { console.error('Error extracting table name:', error); return null; } } // Verstecke Autocomplete function hideAutocomplete() { const dropdown = document.getElementById('autocomplete-dropdown'); if (dropdown) { dropdown.style.display = 'none'; } autocompleteState.isVisible = false; autocompleteState.selectedIndex = -1; autocompleteState.items = []; autocompleteState.type = null; } // Bewege Auswahl im Autocomplete function moveAutocompleteSelection(direction) { const dropdown = document.getElementById('autocomplete-dropdown'); const items = dropdown.querySelectorAll('.autocomplete-item'); if (items.length === 0) return; // Entferne vorherige Auswahl items.forEach(item => item.classList.remove('selected')); // Berechne neue Position autocompleteState.selectedIndex += direction; if (autocompleteState.selectedIndex < 0) { autocompleteState.selectedIndex = items.length - 1; } else if (autocompleteState.selectedIndex >= items.length) { autocompleteState.selectedIndex = 0; } // Markiere neue Auswahl items[autocompleteState.selectedIndex].classList.add('selected'); items[autocompleteState.selectedIndex].scrollIntoView({ block: 'nearest' }); } // Wähle Autocomplete-Item aus function selectAutocompleteItem() { if (autocompleteState.selectedIndex >= 0 && autocompleteState.selectedIndex < autocompleteState.items.length) { const selectedItem = autocompleteState.items[autocompleteState.selectedIndex]; if (autocompleteState.type === 'column') { selectColumnFromAutocomplete(selectedItem); } else { selectTableFromAutocomplete(selectedItem); } } } // Wähle Spalte aus Autocomplete aus function selectColumnFromAutocomplete(columnName) { const queryInput = document.getElementById('query-input'); const cursorPosition = queryInput.selectionStart; const text = queryInput.value; const textBeforeCursor = text.substring(0, cursorPosition).toUpperCase(); // Finde Position für Spalten-Einfügung let insertPosition = cursorPosition; let replaceLength = 0; // Prüfe verschiedene SELECT-Muster const selectMatch = textBeforeCursor.match(/\bSELECT\s*$/); const columnMatch = textBeforeCursor.match(/\bSELECT\s+(?:[^,]+,\s*)*([A-Z_0-9]*)$/); const commaMatch = textBeforeCursor.match(/\bSELECT\s+[^,]+(?:\s*,\s*[^,]*)*\s*,\s*([A-Z_0-9]*)$/); if (selectMatch) { // Direkt nach SELECT insertPosition = cursorPosition; } else if (commaMatch) { // Nach Komma in SELECT const partialColumn = commaMatch[1]; insertPosition = cursorPosition - partialColumn.length; replaceLength = partialColumn.length; } else if (columnMatch) { // Erste Spalte nach SELECT const partialColumn = columnMatch[1]; insertPosition = cursorPosition - partialColumn.length; replaceLength = partialColumn.length; } // Füge Spalte ein const beforeInsert = text.substring(0, insertPosition); const afterInsert = text.substring(insertPosition + replaceLength); const newText = beforeInsert + columnName + afterInsert; const newCursorPosition = insertPosition + columnName.length; queryInput.value = newText; queryInput.setSelectionRange(newCursorPosition, newCursorPosition); queryInput.focus(); hideAutocomplete(); } // Wähle Tabelle aus Autocomplete aus function selectTableFromAutocomplete(tableName) { const queryInput = document.getElementById('query-input'); const cursorPosition = queryInput.selectionStart; const text = queryInput.value; // Finde die Position wo "FROM" beginnt const textBeforeCursor = text.substring(0, cursorPosition).toUpperCase(); const fromMatch = textBeforeCursor.match(/\bFROM\s*/); if (fromMatch) { const fromStartPosition = textBeforeCursor.lastIndexOf(fromMatch[0].trim()); const fromEndPosition = fromStartPosition + fromMatch[0].length; // Ersetze den Text nach "FROM " mit der ausgewählten Tabelle const beforeFrom = text.substring(0, fromEndPosition); const afterCursor = text.substring(cursorPosition); const newText = beforeFrom + tableName + ' ' + afterCursor; const newCursorPosition = fromEndPosition + tableName.length + 1; queryInput.value = newText; queryInput.setSelectionRange(newCursorPosition, newCursorPosition); queryInput.focus(); } hideAutocomplete(); } // Initialisiere Autocomplete beim Laden der Seite document.addEventListener('DOMContentLoaded', function() { initializeAutocomplete(); });