(string) $host, 'MYSQL_PORT' => (string) $port, 'MYSQL_USER' => Env::require('REMOTE_MYSQL_USER'), 'MYSQL_PASSWORD' => Env::require('REMOTE_MYSQL_PASSWORD'), 'MAILSHOTS_REMOTE_MYSQL_DB' => Env::require('MAILSHOTS_REMOTE_MYSQL_DB'), 'MEMBERS_REMOTE_MYSQL_DB' => Env::require('MEMBERS_REMOTE_MYSQL_DB'), 'FEN_REMOTE_MYSQL_DB' => Env::require('FEN_REMOTE_MYSQL_DB'), ]; $router = new PdoDatabaseRouter($dbConfig); $metadata = new DatabaseSourceMetadataProvider($router); $parser = new DslParser(); $validator = new DslValidator($metadata); $compiler = new DslCompiler($metadata); $queries = new MailshotQueryRepository($router); $mailshots = new MailshotRepository($router); $service = new DataSourceService($queries, $router, $parser, $validator, $compiler, $metadata, $mailshots); $membersDb = $router->membersDbName(); $mailshotsDb = $router->mailshotsDbName(); $stmt = $router->membersPdo()->prepare( 'SELECT table_name FROM information_schema.tables WHERE table_schema = :schema AND table_name REGEXP :rx ORDER BY table_name ASC LIMIT 1' ); $stmt->execute(['schema' => $membersDb, 'rx' => '^[A-Za-z][A-Za-z0-9_]*$']); $firstTable = $stmt->fetchColumn(); if ($firstTable === false || $firstTable === null || $firstTable === '') { fwrite(STDERR, "No usable tables found in MEMBERS db for integration preview test.\n"); exit(1); } $source = $membersDb . '.' . $firstTable; $dsl = $source; $validation = $service->validateDsl($dsl); if (($validation['errors'] ?? []) !== []) { fwrite(STDERR, "Validation failed for source {$source}: " . json_encode($validation['errors']) . "\n"); exit(1); } $preview = $service->preview($dsl, 5); if (($preview['errors'] ?? []) !== []) { fwrite(STDERR, "Preview failed for source {$source}: " . json_encode($preview['errors']) . "\n"); exit(1); } if (!isset($preview['count']) || !is_int($preview['count'])) { fwrite(STDERR, "Preview response missing integer count.\n"); exit(1); } if (!isset($preview['columns']) || !is_array($preview['columns'])) { fwrite(STDERR, "Preview response missing columns array.\n"); exit(1); } $name = 'phase2_db_access_' . gmdate('Ymd_His') . '_' . bin2hex(random_bytes(3)); $savedId = null; try { $save = $service->save(null, $name, $dsl); if (($save['ok'] ?? false) !== true) { fwrite(STDERR, "Save failed: " . json_encode($save) . "\n"); exit(1); } $savedId = (int) ($save['id'] ?? 0); if ($savedId <= 0) { fwrite(STDERR, "Save did not return a valid ID.\n"); exit(1); } $row = $queries->find($savedId); if ($row === null) { fwrite(STDERR, "Saved mailshot_queries row not found for ID {$savedId}.\n"); exit(1); } if (($row['name'] ?? '') !== $name || trim((string) ($row['dsl_text'] ?? '')) !== $dsl) { fwrite(STDERR, "Saved row contents mismatch for ID {$savedId}.\n"); exit(1); } $service->delete($savedId); $savedId = null; echo "Phase 2 DB integration test passed\n"; echo "members_db={$membersDb}\n"; echo "mailshots_db={$mailshotsDb}\n"; echo "preview_source={$source}\n"; echo "preview_count=" . $preview['count'] . "\n"; } finally { if ($savedId !== null) { try { $service->delete($savedId); } catch (Throwable $e) { fwrite(STDERR, "Cleanup warning for ID {$savedId}: {$e->getMessage()}\n"); } } }