PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, ] ); return $pdo; } catch (PDOException $e) { error_log('getLoggingPdo connection error: ' . $e->getMessage()); return null; } } /** * Log a query to dbo.controls_query_log for NL2SQL training. * * @param string $userQuery Natural-language description of the search. * @param string $sqlTemplate Parameterized SQL with placeholders. * @param array|null $params Associative array of parameter values. * @param string|null $sourceForm Originating form identifier. * @param int|null $executionMs Query runtime in milliseconds. * @param int|null $rowCount Number of rows returned. * @return bool True on success, false on failure. */ function logControlsQuery( string $userQuery, string $sqlTemplate, ?array $params = null, ?string $sourceForm = null, ?int $executionMs = null, ?int $rowCount = null ): bool { // Silently skip if inputs are empty if (trim($userQuery) === '' || trim($sqlTemplate) === '') { return false; } $pdo = getLoggingPdo(); if ($pdo === null) { return false; } $sql = " INSERT INTO dbo.controls_query_log (user_query, sql_template, params, source_form, user_session, execution_ms, row_count) VALUES (:user_query, :sql_template, :params, :source_form, :user_session, :execution_ms, :row_count) "; try { $stmt = $pdo->prepare($sql); $result = $stmt->execute([ ':user_query' => mb_substr($userQuery, 0, 1000), ':sql_template' => $sqlTemplate, ':params' => $params !== null ? json_encode($params, JSON_UNESCAPED_UNICODE) : null, ':source_form' => $sourceForm, ':user_session' => session_id() ?: null, ':execution_ms' => $executionMs, ':row_count' => $rowCount, ]); return $result; } catch (Throwable $e) { return false; } } /** * Build a natural-language description from form inputs. * * @param string $searchType 'single_name' or 'multiple_names'. * @param string $searchName Single tag name (if applicable). * @param array $selectedTags Array of tag names for multi-tag search. * @param string $startDate Start datetime string. * @param string $endDate End datetime string. * @param int $limit Row limit. * @param int $timeInterval Sampling interval in seconds. * @return string Human-readable query description. */ function buildUserQueryDescription( string $searchType, string $searchName, array $selectedTags, string $startDate, string $endDate, int $limit, int $timeInterval ): string { $parts = []; // Tag(s) if ($searchType === 'single_name' && $searchName !== '') { $parts[] = "Show data for tag \"{$searchName}\""; } elseif ($searchType === 'multiple_names' && !empty($selectedTags)) { $tagList = implode(', ', array_map(fn($t) => "\"{$t}\"", $selectedTags)); $parts[] = "Compare tags {$tagList}"; } else { return ''; } // Date range if ($startDate !== '' && $endDate !== '') { $parts[] = "from {$startDate} to {$endDate}"; } elseif ($startDate !== '') { $parts[] = "starting from {$startDate}"; } elseif ($endDate !== '') { $parts[] = "up until {$endDate}"; } // Sampling if ($timeInterval > 1) { if ($timeInterval >= 60) { $minutes = (int) ($timeInterval / 60); $parts[] = "sampled every {$minutes} minute" . ($minutes > 1 ? 's' : ''); } else { $parts[] = "sampled every {$timeInterval} seconds"; } } // Limit $parts[] = "limit {$limit} rows"; return implode(' ', $parts); }