From ecd6dddfde8e37b9a8a33ac73f4e4fbcab2f4126 Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Mon, 4 Aug 2025 08:53:16 +0200 Subject: [PATCH] Create main.lua --- .../[tools]/nordi_license/server/main.lua | 722 ++++++++++++++++++ 1 file changed, 722 insertions(+) create mode 100644 resources/[tools]/nordi_license/server/main.lua diff --git a/resources/[tools]/nordi_license/server/main.lua b/resources/[tools]/nordi_license/server/main.lua new file mode 100644 index 000000000..7a01a8a88 --- /dev/null +++ b/resources/[tools]/nordi_license/server/main.lua @@ -0,0 +1,722 @@ +local QBCore = exports['qb-core']:GetCoreObject() + +-- Lokale Variablen +local licenseCache = {} +local cacheTimeout = 300000 -- 5 Minuten + +-- Hilfsfunktionen +local function debugPrint(message) + if Config.Debug then + print("^3[License-System Server] " .. message .. "^7") + end +end + +-- Spieler-Name abrufen +local function getPlayerName(src) + local Player = QBCore.Functions.GetPlayer(src) + if not Player then return "Unbekannt" end + + local charinfo = Player.PlayerData.charinfo + if charinfo and charinfo.firstname and charinfo.lastname then + return charinfo.firstname .. " " .. charinfo.lastname + end + + return "Unbekannt" +end + +-- Spieler-Daten abrufen +local function getPlayerData(src) + local Player = QBCore.Functions.GetPlayer(src) + if not Player then return nil end + + return { + citizenid = Player.PlayerData.citizenid, + name = getPlayerName(src), + charinfo = Player.PlayerData.charinfo, + job = Player.PlayerData.job + } +end + +-- Berechtigung prüfen +local function hasPermission(src) + local Player = QBCore.Functions.GetPlayer(src) + if not Player then return false end + + local job = Player.PlayerData.job + if not job then return false end + + local hasAuth = Config.AuthorizedJobs[job.name] or false + debugPrint("Berechtigung für " .. job.name .. ": " .. tostring(hasAuth)) + return hasAuth +end + +-- Cache-Funktionen +local function getCachedLicense(citizenid, licenseType) + local cacheKey = citizenid .. "_" .. licenseType + local cached = licenseCache[cacheKey] + + if cached and (os.time() * 1000 - cached.timestamp) < cacheTimeout then + debugPrint("Cache-Hit für: " .. cacheKey) + return cached.data + end + + return nil +end + +local function setCachedLicense(citizenid, licenseType, data) + local cacheKey = citizenid .. "_" .. licenseType + licenseCache[cacheKey] = { + data = data, + timestamp = os.time() * 1000 + } + debugPrint("Lizenz gecacht: " .. cacheKey) +end + +-- Cache bereinigen +local function cleanupCache() + local now = os.time() * 1000 + local cleaned = 0 + + for key, cached in pairs(licenseCache) do + if (now - cached.timestamp) > cacheTimeout then + licenseCache[key] = nil + cleaned = cleaned + 1 + end + end + + if cleaned > 0 then + debugPrint("Cache bereinigt: " .. cleaned .. " Einträge") + end +end + +-- Cache-Cleanup Thread +CreateThread(function() + while true do + Wait(300000) -- 5 Minuten + cleanupCache() + end +end) + +-- Spieler-Name aus JSON extrahieren (MariaDB-kompatibel) +local function extractPlayerName(charinfo_json) + if not charinfo_json then return "Unbekannt" end + + local success, charinfo = pcall(json.decode, charinfo_json) + if success and charinfo and charinfo.firstname and charinfo.lastname then + return charinfo.firstname .. " " .. charinfo.lastname + end + + return "Unbekannt" +end + +-- Lizenz aus Datenbank abrufen (KORRIGIERT für MariaDB) +local function getLicenseFromDB(citizenid, licenseType) + debugPrint("=== getLicenseFromDB START ===") + debugPrint("CitizenID: " .. tostring(citizenid)) + debugPrint("LicenseType: " .. tostring(licenseType)) + + -- Cache prüfen + local cached = getCachedLicense(citizenid, licenseType) + if cached then + return cached + end + + -- MariaDB-kompatible Query ohne JSON-Operatoren + local query = [[ + SELECT pl.*, + p.charinfo as holder_charinfo, + pi.charinfo as issued_by_charinfo + FROM player_licenses pl + LEFT JOIN players p ON p.citizenid = pl.citizenid + LEFT JOIN players pi ON pi.citizenid = pl.issued_by + WHERE pl.citizenid = ? AND pl.license_type = ? + ORDER BY pl.created_at DESC + LIMIT 1 + ]] + + local result = MySQL.query.await(query, {citizenid, licenseType}) + + if result and #result > 0 then + local license = result[1] + + -- Namen aus JSON extrahieren + license.holder_name = extractPlayerName(license.holder_charinfo) + license.issued_by_name = extractPlayerName(license.issued_by_charinfo) + + -- Fallback für issued_by_name + if license.issued_by_name == "Unbekannt" then + license.issued_by_name = "System" + end + + -- Classes parsen + if license.classes then + local success, classes = pcall(json.decode, license.classes) + if success and type(classes) == "table" then + license.classes = classes + else + license.classes = {} + end + else + license.classes = {} + end + + -- Cleanup - charinfo nicht mehr benötigt + license.holder_charinfo = nil + license.issued_by_charinfo = nil + + debugPrint("Lizenz aus DB geladen: " .. license.license_type) + + -- In Cache speichern + setCachedLicense(citizenid, licenseType, license) + + return license + end + + debugPrint("Keine Lizenz in DB gefunden") + return nil +end + +-- Alle Lizenzen eines Spielers abrufen (KORRIGIERT für MariaDB) +local function getAllPlayerLicenses(citizenid) + debugPrint("=== getAllPlayerLicenses START ===") + debugPrint("CitizenID: " .. tostring(citizenid)) + + -- MariaDB-kompatible Query + local query = [[ + SELECT pl.*, + p.charinfo as holder_charinfo, + pi.charinfo as issued_by_charinfo + FROM player_licenses pl + LEFT JOIN players p ON p.citizenid = pl.citizenid + LEFT JOIN players pi ON pi.citizenid = pl.issued_by + WHERE pl.citizenid = ? + ORDER BY pl.license_type, pl.created_at DESC + ]] + + local result = MySQL.query.await(query, {citizenid}) + + if result and #result > 0 then + local licenses = {} + local seenTypes = {} + + for _, license in ipairs(result) do + -- Nur die neueste Lizenz pro Typ nehmen + if not seenTypes[license.license_type] then + seenTypes[license.license_type] = true + + -- Namen aus JSON extrahieren + license.holder_name = extractPlayerName(license.holder_charinfo) + license.issued_by_name = extractPlayerName(license.issued_by_charinfo) + + -- Fallback für issued_by_name + if license.issued_by_name == "Unbekannt" then + license.issued_by_name = "System" + end + + -- Classes parsen + if license.classes then + local success, classes = pcall(json.decode, license.classes) + if success and type(classes) == "table" then + license.classes = classes + else + license.classes = {} + end + else + license.classes = {} + end + + -- Cleanup + license.holder_charinfo = nil + license.issued_by_charinfo = nil + + table.insert(licenses, license) + end + end + + debugPrint("Gefundene Lizenzen: " .. #licenses) + return licenses + end + + debugPrint("Keine Lizenzen gefunden") + return {} +end + +-- Lizenz in Datenbank speichern (KORRIGIERT) +local function saveLicenseToDB(citizenid, licenseType, issuedBy, classes) + debugPrint("=== saveLicenseToDB START ===") + debugPrint("CitizenID: " .. tostring(citizenid)) + debugPrint("LicenseType: " .. tostring(licenseType)) + debugPrint("IssuedBy: " .. tostring(issuedBy)) + + local config = Config.LicenseTypes[licenseType] + if not config then + debugPrint("^1Fehler: Unbekannter Lizenztyp: " .. licenseType .. "^7") + return false + end + + -- Spieler-Name für das name-Feld abrufen (MariaDB-kompatibel) + local holderQuery = "SELECT charinfo FROM players WHERE citizenid = ?" + local holderResult = MySQL.query.await(holderQuery, {citizenid}) + local holderName = "Unbekannt" + + if holderResult and #holderResult > 0 and holderResult[1].charinfo then + holderName = extractPlayerName(holderResult[1].charinfo) + end + + -- Datum berechnen + local issueDate = os.date("%d.%m.%Y") + local expireDate = nil + + if config.validity_days then + local expireTimestamp = os.time() + (config.validity_days * 24 * 60 * 60) + expireDate = os.date("%d.%m.%Y", expireTimestamp) + end + + -- Classes zu JSON konvertieren + local classesJson = json.encode(classes or {}) + + -- Alte Lizenz deaktivieren + local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?" + MySQL.query.await(deactivateQuery, {citizenid, licenseType}) + + -- Neue Lizenz einfügen + local insertQuery = [[ + INSERT INTO player_licenses + (citizenid, license_type, name, issue_date, expire_date, issued_by, is_active, classes, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ]] + + local insertData = { + citizenid, + licenseType, + holderName, + issueDate, + expireDate, + issuedBy, + 1, + classesJson, + os.time() + } + + debugPrint("Führe INSERT-Query aus...") + debugPrint("Daten: " .. json.encode(insertData)) + + local success, result = pcall(MySQL.insert.await, insertQuery, insertData) + + if success and result then + debugPrint("Lizenz erfolgreich gespeichert. ID: " .. result) + + -- Cache invalidieren + local cacheKey = citizenid .. "_" .. licenseType + licenseCache[cacheKey] = nil + + return true + else + debugPrint("^1Fehler beim Speichern der Lizenz: " .. tostring(result) .. "^7") + return false + end +end + +-- Lizenz entziehen +local function revokeLicenseInDB(citizenid, licenseType) + debugPrint("=== revokeLicenseInDB START ===") + debugPrint("CitizenID: " .. tostring(citizenid)) + debugPrint("LicenseType: " .. tostring(licenseType)) + + local query = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ? AND is_active = 1" + local success, result = pcall(MySQL.query.await, query, {citizenid, licenseType}) + + if success and result then + debugPrint("Lizenz erfolgreich entzogen") + + -- Cache invalidieren + local cacheKey = citizenid .. "_" .. licenseType + licenseCache[cacheKey] = nil + + return true + else + debugPrint("^1Fehler beim Entziehen der Lizenz: " .. tostring(result) .. "^7") + return false + end +end + +-- Erweiterte Fehlerbehandlung für Datenbankoperationen +local function safeDBOperation(operation, errorMessage) + local success, result = pcall(operation) + if not success then + debugPrint("^1DB-Fehler: " .. (errorMessage or "Unbekannt") .. "^7") + debugPrint("^1Details: " .. tostring(result) .. "^7") + return nil + end + return result +end + +-- EVENT HANDLER: Lizenz anfordern +RegisterNetEvent('license-system:server:requestLicense', function(targetId) + local src = source + debugPrint("=== Event: requestLicense ===") + debugPrint("Source: " .. src .. ", Target: " .. targetId) + + if not hasPermission(src) then + debugPrint("Keine Berechtigung für Spieler: " .. src) + TriggerClientEvent('license-system:client:receiveLicense', src, nil) + return + end + + local targetPlayer = QBCore.Functions.GetPlayer(targetId) + if not targetPlayer then + debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) + TriggerClientEvent('license-system:client:receiveLicense', src, nil) + return + end + + local citizenid = targetPlayer.PlayerData.citizenid + + -- Erste verfügbare Lizenz finden + local foundLicense = nil + for licenseType, _ in pairs(Config.LicenseTypes) do + local license = safeDBOperation(function() + return getLicenseFromDB(citizenid, licenseType) + end, "Fehler beim Abrufen der Lizenz") + + if license and license.is_active == 1 then + foundLicense = { + license = license, + config = Config.LicenseTypes[licenseType] + } + break + end + end + + if foundLicense then + debugPrint("Sende Lizenz an Client: " .. foundLicense.license.license_type) + TriggerClientEvent('license-system:client:receiveLicense', src, foundLicense) + else + debugPrint("Keine aktive Lizenz gefunden") + TriggerClientEvent('license-system:client:receiveLicense', src, nil) + end +end) + +-- EVENT HANDLER: Eigene Lizenz anfordern +RegisterNetEvent('license-system:server:requestMyLicense', function(licenseType) + local src = source + debugPrint("=== Event: requestMyLicense ===") + debugPrint("Source: " .. src .. ", LicenseType: " .. licenseType) + + local Player = QBCore.Functions.GetPlayer(src) + if not Player then + debugPrint("Spieler nicht gefunden: " .. src) + TriggerClientEvent('license-system:client:receiveMyLicense', src, nil, licenseType) + return + end + + local citizenid = Player.PlayerData.citizenid + local license = safeDBOperation(function() + return getLicenseFromDB(citizenid, licenseType) + end, "Fehler beim Abrufen der eigenen Lizenz") + + if license and license.is_active == 1 then + local licenseData = { + license = license, + config = Config.LicenseTypes[licenseType] + } + debugPrint("Sende eigene Lizenz an Client: " .. licenseType) + TriggerClientEvent('license-system:client:receiveMyLicense', src, licenseData, licenseType) + else + debugPrint("Keine aktive eigene Lizenz gefunden: " .. licenseType) + TriggerClientEvent('license-system:client:receiveMyLicense', src, nil, licenseType) + end +end) + +-- EVENT HANDLER: Alle Spieler-Lizenzen anfordern +RegisterNetEvent('license-system:server:requestPlayerLicenses', function(targetId) + local src = source + debugPrint("=== Event: requestPlayerLicenses ===") + debugPrint("Source: " .. src .. ", Target: " .. targetId) + + if not hasPermission(src) then + debugPrint("Keine Berechtigung für Spieler: " .. src) + TriggerClientEvent('license-system:client:receivePlayerLicenses', src, {}, targetId, "Unbekannt") + return + end + + local targetPlayer = QBCore.Functions.GetPlayer(targetId) + if not targetPlayer then + debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) + TriggerClientEvent('license-system:client:receivePlayerLicenses', src, {}, targetId, "Unbekannt") + return + end + + local citizenid = targetPlayer.PlayerData.citizenid + local targetName = getPlayerName(targetId) + + local licenses = safeDBOperation(function() + return getAllPlayerLicenses(citizenid) + end, "Fehler beim Abrufen aller Spieler-Lizenzen") or {} + + debugPrint("Sende " .. #licenses .. " Lizenzen für " .. targetName) + TriggerClientEvent('license-system:client:receivePlayerLicenses', src, licenses, targetId, targetName) +end) + +-- EVENT HANDLER: Lizenz ausstellen +RegisterNetEvent('license-system:server:issueLicense', function(targetId, licenseType, classes) + local src = source + debugPrint("=== Event: issueLicense ===") + debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", Type: " .. licenseType) + + if not hasPermission(src) then + debugPrint("Keine Berechtigung für Spieler: " .. src) + TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) + return + end + + local targetPlayer = QBCore.Functions.GetPlayer(targetId) + if not targetPlayer then + debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) + TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error') + return + end + + local issuerPlayer = QBCore.Functions.GetPlayer(src) + if not issuerPlayer then + debugPrint("Aussteller nicht gefunden: " .. src) + return + end + + local targetCitizenId = targetPlayer.PlayerData.citizenid + local issuerCitizenId = issuerPlayer.PlayerData.citizenid + + -- Prüfen ob Lizenz bereits existiert + local existingLicense = safeDBOperation(function() + return getLicenseFromDB(targetCitizenId, licenseType) + end, "Fehler beim Prüfen bestehender Lizenz") + + if existingLicense and existingLicense.is_active == 1 then + debugPrint("Lizenz bereits vorhanden und aktiv") + TriggerClientEvent('QBCore:Notify', src, 'Spieler hat bereits eine aktive ' .. (Config.LicenseTypes[licenseType].label or licenseType) .. '!', 'error') + return + end + + -- Kosten prüfen + local config = Config.LicenseTypes[licenseType] + if config.price and config.price > 0 then + if issuerPlayer.PlayerData.money.cash < config.price then + debugPrint("Nicht genug Geld für Lizenz-Ausstellung") + TriggerClientEvent('QBCore:Notify', src, 'Nicht genug Bargeld! Benötigt: $' .. config.price, 'error') + return + end + + -- Geld abziehen + issuerPlayer.Functions.RemoveMoney('cash', config.price, 'license-issued') + TriggerClientEvent('QBCore:Notify', src, 'Lizenz-Gebühr bezahlt: $' .. config.price, 'success') + end + + -- Lizenz in Datenbank speichern + local success = safeDBOperation(function() + return saveLicenseToDB(targetCitizenId, licenseType, issuerCitizenId, classes) + end, "Fehler beim Speichern der Lizenz") + + if success then + local targetName = getPlayerName(targetId) + local issuerName = getPlayerName(src) + + debugPrint("Lizenz erfolgreich ausgestellt") + + -- Benachrichtigungen + TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich ausgestellt für ' .. targetName, 'success') + TriggerClientEvent('QBCore:Notify', targetId, 'Du hast eine neue Lizenz erhalten: ' .. config.label, 'success') + + -- Events senden + TriggerClientEvent('license-system:client:licenseIssued', src, targetId, licenseType) + TriggerClientEvent('license-system:client:refreshMenu', src) + + -- Log + debugPrint("Lizenz " .. licenseType .. " ausgestellt von " .. issuerName .. " für " .. targetName) + else + debugPrint("^1Fehler beim Ausstellen der Lizenz^7") + TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Ausstellen der Lizenz!', 'error') + end +end) + +-- EVENT HANDLER: Lizenz entziehen +RegisterNetEvent('license-system:server:revokeLicense', function(targetId, licenseType) + local src = source + debugPrint("=== Event: revokeLicense ===") + debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", Type: " .. licenseType) + + if not hasPermission(src) then + debugPrint("Keine Berechtigung für Spieler: " .. src) + TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) + return + end + + local targetPlayer = QBCore.Functions.GetPlayer(targetId) + if not targetPlayer then + debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) + TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error') + return + end + + local targetCitizenId = targetPlayer.PlayerData.citizenid + + -- Lizenz entziehen + local success = safeDBOperation(function() + return revokeLicenseInDB(targetCitizenId, licenseType) + end, "Fehler beim Entziehen der Lizenz") + + if success then + local targetName = getPlayerName(targetId) + local issuerName = getPlayerName(src) + local config = Config.LicenseTypes[licenseType] + + debugPrint("Lizenz erfolgreich entzogen") + + -- Benachrichtigungen + TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich entzogen von ' .. targetName, 'success') + TriggerClientEvent('QBCore:Notify', targetId, 'Deine Lizenz wurde entzogen: ' .. (config.label or licenseType), 'error') + + -- Events senden + TriggerClientEvent('license-system:client:licenseRevoked', src, targetId, licenseType) + TriggerClientEvent('license-system:client:refreshMenu', src) + + -- Log + debugPrint("Lizenz " .. licenseType .. " entzogen von " .. issuerName .. " für " .. targetName) + else + debugPrint("^1Fehler beim Entziehen der Lizenz^7") + TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Entziehen der Lizenz!', 'error') + end +end) + +-- EVENT HANDLER: Foto speichern +RegisterNetEvent('license-system:server:savePhoto', function(citizenid, photoData) + local src = source + debugPrint("=== Event: savePhoto ===") + debugPrint("Source: " .. src .. ", CitizenID: " .. citizenid) + + -- Hier könnte das Foto in der Datenbank oder im Dateisystem gespeichert werden + debugPrint("Foto-Daten erhalten für: " .. citizenid) + + TriggerClientEvent('QBCore:Notify', src, 'Foto gespeichert!', 'success') +end) + +-- EXPORT FUNKTIONEN +exports('hasLicense', function(citizenid, licenseType) + if not citizenid or not licenseType then return false end + + local license = safeDBOperation(function() + return getLicenseFromDB(citizenid, licenseType) + end, "Fehler beim Prüfen der Lizenz") + + return license and license.is_active == 1 +end) + +exports('issueLicense', function(citizenid, licenseType, issuedBy, classes) + if not citizenid or not licenseType then return false end + + issuedBy = issuedBy or 'system' + return safeDBOperation(function() + return saveLicenseToDB(citizenid, licenseType, issuedBy, classes) + end, "Fehler beim Ausstellen der Lizenz") or false +end) + +exports('revokeLicense', function(citizenid, licenseType) + if not citizenid or not licenseType then return false end + + return safeDBOperation(function() + return revokeLicenseInDB(citizenid, licenseType) + end, "Fehler beim Entziehen der Lizenz") or false +end) + +exports('getPlayerLicenses', function(citizenid) + if not citizenid then return {} end + + return safeDBOperation(function() + return getAllPlayerLicenses(citizenid) + end, "Fehler beim Abrufen der Spieler-Lizenzen") or {} +end) + +exports('getPlayerLicense', function(citizenid, licenseType) + if not citizenid or not licenseType then return nil end + + return safeDBOperation(function() + return getLicenseFromDB(citizenid, licenseType) + end, "Fehler beim Abrufen der Lizenz") +end) + +-- INITIALISIERUNG +CreateThread(function() + debugPrint("License-System Server gestartet (MariaDB-kompatibel)") + + -- Warten bis QBCore geladen ist + while not QBCore do + Wait(100) + end + + debugPrint("QBCore erfolgreich geladen") + + -- Datenbank-Verbindung testen + local testResult = safeDBOperation(function() + return MySQL.query.await("SELECT 1 as test") + end, "Datenbank-Verbindungstest") + + if testResult then + debugPrint("Datenbank-Verbindung erfolgreich") + else + debugPrint("^1Datenbank-Verbindung fehlgeschlagen^7") + end + + debugPrint("License-System Server vollständig initialisiert") +end) + +-- CLEANUP +AddEventHandler('onResourceStop', function(resourceName) + if GetCurrentResourceName() == resourceName then + debugPrint("License-System Server gestoppt") + licenseCache = {} + end +end) + +-- DEBUG COMMANDS +RegisterCommand('licensestats', function(source, args, rawCommand) + if source == 0 then -- Console only + local cacheCount = 0 + for _ in pairs(licenseCache) do + cacheCount = cacheCount + 1 + end + + print("=== LICENSE SYSTEM STATS ===") + print("Cache Entries: " .. cacheCount) + print("Config License Types: " .. (Config.LicenseTypes and #Config.LicenseTypes or 0)) + print("============================") + end +end, true) + +RegisterCommand('licenseclearcache', function(source, args, rawCommand) + if source == 0 then -- Console only + local oldCount = 0 + for _ in pairs(licenseCache) do + oldCount = oldCount + 1 + end + + licenseCache = {} + print("License-Cache geleert. Entfernte Einträge: " .. oldCount) + end +end, true) + +-- Erweiterte Logging-Funktion +local function logLicenseAction(action, src, targetId, licenseType, details) + local timestamp = os.date("%Y-%m-%d %H:%M:%S") + local srcName = getPlayerName(src) + local targetName = targetId and getPlayerName(targetId) or "N/A" + + local logMessage = string.format( + "[%s] %s | Quelle: %s (%s) | Ziel: %s (%s) | Typ: %s | Details: %s", + timestamp, action, srcName, src, targetName, targetId or "N/A", licenseType or "N/A", details or "N/A" + ) + + debugPrint("LOG: " .. logMessage) + + -- Hier könnte das Log in eine Datei oder Datenbank geschrieben werden +end + +debugPrint("License-System Server vollständig geladen (MariaDB-kompatibel)")