2025-10-14 21:27:41 +02:00

1628 lines
59 KiB
JavaScript

// 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 = `<option>Fehler: ${error.message}</option>`;
}
});
}
// 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 = '<div class="text-center"><div class="loading"></div> Lade...</div>';
}
}
function showAlert(message, type = 'info') {
const alertClass = type === 'error' ? 'alert-error-custom' : 'alert-success-custom';
const alertHtml = `
<div class="alert ${alertClass} alert-dismissible fade show" role="alert">
${message}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
`;
// 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 =
`<div class="text-danger">Fehler: ${data.error}</div>`;
}
} catch (error) {
document.getElementById('tables-list').innerHTML =
`<div class="text-danger">Fehler beim Laden der Tabellen: ${error.message}</div>`;
}
}
function displayTables(tables) {
const tablesList = document.getElementById('tables-list');
if (tables.length === 0) {
tablesList.innerHTML = '<div class="text-muted">Keine Tabellen gefunden</div>';
return;
}
const tablesHtml = tables.map(table => `
<div class="table-container">
<div class="table-item"
onclick="selectTable('${table}')"
data-table="${table}"
draggable="true"
ondragstart="handleTableDrag(event, '${table}')">
<i class="fas fa-table"></i> ${table}
<i class="fas fa-arrows-alt drag-icon"></i>
</div>
<div class="table-columns" id="columns-${table}" style="display: none;"></div>
</div>
`).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 => `
<div class="column-item"
onclick="insertColumn('${col.name}')"
draggable="true"
ondragstart="handleColumnDrag(event, '${tableName}', '${col.name}')">
<i class="fas fa-columns text-muted"></i>
<span class="column-name">${col.name}</span>
<small class="column-type text-muted">${col.type}</small>
${col.primary_key ? '<i class="fas fa-key text-warning ms-1" title="Primary Key"></i>' : ''}
${col.not_null ? '<i class="fas fa-exclamation text-info ms-1" title="Not Null"></i>' : ''}
<i class="fas fa-arrows-alt drag-icon"></i>
</div>
`).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 = `<i class="fas fa-table"></i> ${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 = `<i class="fas fa-columns"></i> ${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 => `
<div class="schema-column">
<strong>${col.name}</strong> <span class="text-muted">${col.type}</span>
${col.primary_key ? '<i class="fas fa-key text-warning" title="Primary Key"></i>' : ''}
${col.not_null ? '<i class="fas fa-exclamation text-info" title="Not Null"></i>' : ''}
</div>
`).join('');
const popup = document.createElement('div');
popup.className = 'schema-popup';
popup.id = 'schema-popup';
popup.innerHTML = `<h6>${tableName}</h6>${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 = `
<div class="card-body text-center">
<div class="loading"></div>
<p class="mt-2">Query wird ausgeführt...</p>
</div>
`;
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 = `
<div class="card-body">
<div class="alert alert-danger">
<h6>Query-Fehler:</h6>
<pre class="mb-0">${data.error}</pre>
</div>
</div>
`;
}
} catch (error) {
resultsContainer.innerHTML = `
<div class="card-body">
<div class="alert alert-danger">
<h6>Netzwerk-Fehler:</h6>
<p class="mb-0">${error.message}</p>
</div>
</div>
`;
}
}
function displayQueryResults(results) {
const resultsContainer = document.getElementById('results-container');
if (results.message) {
// Für INSERT, UPDATE, DELETE etc.
resultsContainer.innerHTML = `
<div class="card-body">
<div class="alert alert-success">
<i class="fas fa-check-circle"></i> ${results.message}
</div>
</div>
`;
return;
}
if (!results.data || results.data.length === 0) {
resultsContainer.innerHTML = `
<div class="card-body">
<div class="alert alert-info">
<i class="fas fa-info-circle"></i> Query erfolgreich, aber keine Ergebnisse gefunden.
</div>
</div>
`;
return;
}
// Erstelle Tabelle mit Ergebnissen
const tableHtml = `
<div class="card-body">
<div class="d-flex justify-content-between align-items-center mb-3">
<h6 class="mb-0">
<i class="fas fa-table"></i>
${results.row_count} Zeile(n) gefunden
</h6>
<button class="btn btn-sm btn-outline-success" onclick="exportResults('csv')">
<i class="fas fa-download"></i> CSV Export
</button>
</div>
<div class="results-table">
<table class="table table-striped table-hover">
<thead>
<tr>
${results.columns.map(col => `<th>${col}</th>`).join('')}
</tr>
</thead>
<tbody>
${results.data.map(row => `
<tr>
${row.map(cell => `<td>${cell !== null ? cell : '<em class="text-muted">NULL</em>'}</td>`).join('')}
</tr>
`).join('')}
</tbody>
</table>
</div>
</div>
`;
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 = '<i class="fas fa-eye"></i>';
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 = '<i class="fas fa-eye-slash"></i>';
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 = '<div class="autocomplete-header">Allgemeine Spalten</div>';
filteredColumns.forEach((column, index) => {
dropdownHtml += `
<div class="autocomplete-item" data-index="${index}" data-column="${column}" onclick="selectColumnFromAutocomplete('${column}')">
<i class="fas fa-columns"></i>
${column}
</div>
`;
});
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 = '<div class="autocomplete-header">Alle verfügbaren Spalten</div>';
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 += `
<div class="autocomplete-item" data-index="${index}" data-column="${columnName}" onclick="selectColumnFromAutocomplete('${columnName}')">
<i class="fas fa-columns"></i>
<strong>${columnName}</strong>
<small class="text-muted">${tableInfo}</small>
</div>
`;
});
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 = `<div class="autocomplete-header">Spalten von ${tableName}</div>`;
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 += `
<div class="autocomplete-item" data-index="${index}" data-column="${column}" onclick="selectColumnFromAutocomplete('${column}')">
<i class="${icon}"></i>
${column}${typeInfo}
</div>
`;
});
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 = '<div class="autocomplete-header">Verfügbare Tabellen</div>';
filteredTables.forEach((table, index) => {
dropdownHtml += `
<div class="autocomplete-item" data-index="${index}" data-table="${table}" onclick="selectTableFromAutocomplete('${table}')">
<i class="fas fa-table"></i>
${table}
</div>
`;
});
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();
});