feat(jdbc): add managed Apache Phoenix profile

This commit is contained in:
Guoyu Su authored and GitHub committed 2026-08-06 23:16:12 +08:00
1 parent c835deb3f9
commit 69f4450aee
38 files changed
+1651 -77

No files matched your search

@@ -0,0 +1,232 @@
import type { ConnectionConfig, DatabaseType, JdbcMavenBundleInfo } from "@/types/database";
import type { ManagedJdbcDriverDefinition } from "@/lib/database/managedJdbcDriver";
export type JdbcProductConnectionFields = {
connectionString: string;
driverClass: string;
};
export type JdbcProductConnectionFieldsByMode = Record<string, JdbcProductConnectionFields>;
export type JdbcProductModeDefinition = {
id: string;
labelKey: string;
hintKey: string;
defaultConnectionString: string;
defaultDriverClass: string;
managedCoordinates: readonly string[];
};
export type JdbcProductProfileDefinition = {
id: string;
label: string;
icon: string;
category: string;
port: number;
user: string;
match: {
dbType: DatabaseType;
driverProfile?: string;
};
managedDriverId: string;
runtimeLabelKey: string;
driverManagerHintPrefixKey: string;
driverManagerHintSuffixKey: string;
docsLabelKey: string;
docsUrl: string;
missingDriverError: string;
missingPluginError: string;
modes: readonly JdbcProductModeDefinition[];
detectMode: (config: Partial<Pick<ConnectionConfig, "connection_string" | "jdbc_driver_class">>) => string;
isCompatibleRuntimePath: (path: string, mode: string) => boolean;
};
export type JdbcProductRuntimeApi = {
jdbcPluginStatus: () => Promise<{ installed: boolean; compatible: boolean }>;
listJdbcMavenBundles: () => Promise<JdbcMavenBundleInfo[]>;
};
export type JdbcProductRuntimeResult = {
profile: JdbcProductProfileDefinition;
mode: JdbcProductModeDefinition;
bundles: JdbcMavenBundleInfo[];
paths: string[];
runtimeSelectionId?: string;
};
export type JdbcProductRuntimeErrorKind = "plugin-missing" | "driver-missing";
export class JdbcProductRuntimeError extends Error {
readonly kind: JdbcProductRuntimeErrorKind;
readonly profileId: string;
readonly managedDriverId: string;
constructor(kind: JdbcProductRuntimeErrorKind, profile: JdbcProductProfileDefinition) {
super(kind === "plugin-missing" ? profile.missingPluginError : profile.missingDriverError);
this.name = "JdbcProductRuntimeError";
this.kind = kind;
this.profileId = profile.id;
this.managedDriverId = profile.managedDriverId;
}
}
export type JdbcProductProfileRegistry = {
profiles: readonly JdbcProductProfileDefinition[];
byId: ReadonlyMap<string, JdbcProductProfileDefinition>;
};
export function createJdbcProductProfileRegistry(profiles: readonly JdbcProductProfileDefinition[]): JdbcProductProfileRegistry {
const byId = new Map<string, JdbcProductProfileDefinition>();
const matcherKeys = new Set<string>();
for (const profile of profiles) {
if (!profile.id.trim()) throw new Error("JDBC product profile id must not be empty");
if (byId.has(profile.id)) throw new Error(`Duplicate JDBC product profile id: ${profile.id}`);
const matcherKey = `${profile.match.dbType}:${profile.match.driverProfile ?? "*"}`;
if (matcherKeys.has(matcherKey)) throw new Error(`Duplicate JDBC product profile matcher: ${matcherKey}`);
if (profile.modes.length === 0) throw new Error(`JDBC product profile must define at least one mode: ${profile.id}`);
byId.set(profile.id, profile);
matcherKeys.add(matcherKey);
}
return { profiles: [...profiles], byId };
}
export function jdbcProductProfileMatches(profile: JdbcProductProfileDefinition, config: Pick<ConnectionConfig, "db_type" | "driver_profile">): boolean {
return config.db_type === profile.match.dbType && (profile.match.driverProfile === undefined || config.driver_profile === profile.match.driverProfile);
}
export function jdbcProductMode(profile: JdbcProductProfileDefinition, modeId: string): JdbcProductModeDefinition {
const mode = profile.modes.find((candidate) => candidate.id === modeId) ?? profile.modes[0];
if (!mode) throw new Error(`JDBC product profile has no modes: ${profile.id}`);
return mode;
}
export function jdbcProductConnectionDefaults(profile: JdbcProductProfileDefinition, modeId: string): JdbcProductConnectionFields {
const mode = jdbcProductMode(profile, modeId);
return {
connectionString: mode.defaultConnectionString,
driverClass: mode.defaultDriverClass,
};
}
export function createJdbcProductConnectionFieldsByMode(profile: JdbcProductProfileDefinition, config?: Partial<Pick<ConnectionConfig, "connection_string" | "jdbc_driver_class">>): JdbcProductConnectionFieldsByMode {
const fields = Object.fromEntries(profile.modes.map((mode) => [mode.id, jdbcProductConnectionDefaults(profile, mode.id)]));
if (!config) return fields;
const modeId = profile.detectMode(config);
const defaults = jdbcProductConnectionDefaults(profile, modeId);
fields[modeId] = {
connectionString: config.connection_string?.trim() || defaults.connectionString,
driverClass: config.jdbc_driver_class?.trim() || defaults.driverClass,
};
return fields;
}
export function rememberJdbcProductConnectionFields(profile: JdbcProductProfileDefinition, fields: JdbcProductConnectionFieldsByMode, modeId: string, current: Partial<JdbcProductConnectionFields>): JdbcProductConnectionFieldsByMode {
const defaults = jdbcProductConnectionDefaults(profile, modeId);
return {
...fields,
[modeId]: {
connectionString: current.connectionString?.trim() || defaults.connectionString,
driverClass: current.driverClass?.trim() || defaults.driverClass,
},
};
}
export function isJdbcProductDefaultDriverClass(profile: JdbcProductProfileDefinition, value: string | undefined): boolean {
const normalized = value?.trim() || "";
return !normalized || profile.modes.some((mode) => mode.defaultDriverClass === normalized);
}
export function jdbcProductManagedCoordinates(profile: JdbcProductProfileDefinition): string[] {
return Array.from(new Set(profile.modes.flatMap((mode) => [...mode.managedCoordinates])));
}
export function validateJdbcProductManagedDriver(profile: JdbcProductProfileDefinition, driver: ManagedJdbcDriverDefinition | undefined): void {
if (!driver || driver.id !== profile.managedDriverId) {
throw new Error(`JDBC product profile ${profile.id} references unknown managed driver: ${profile.managedDriverId}`);
}
const ownedCoordinates = new Set(driver.bundles.map(({ coordinate }) => coordinate));
const unownedCoordinate = jdbcProductManagedCoordinates(profile).find((coordinate) => !ownedCoordinates.has(coordinate));
if (unownedCoordinate) {
throw new Error(`JDBC product profile ${profile.id} requires coordinate not owned by ${driver.id}: ${unownedCoordinate}`);
}
}
export function isJdbcProductManagedMavenCoordinate(profile: JdbcProductProfileDefinition, coordinate: string): boolean {
return jdbcProductManagedCoordinates(profile).includes(coordinate);
}
export function jdbcProductManagedRuntimePaths(profile: JdbcProductProfileDefinition, bundles: readonly JdbcMavenBundleInfo[], modeId: string): string[] {
const bundleByCoordinate = new Map(bundles.map((bundle) => [bundle.coordinate, bundle]));
const requiredBundles = jdbcProductMode(profile, modeId).managedCoordinates.map((coordinate) => bundleByCoordinate.get(coordinate));
if (requiredBundles.some((bundle) => !bundle)) return [];
return requiredBundles.flatMap((bundle) => (bundle?.artifacts ?? []).map((artifact) => artifact.path).filter(Boolean));
}
export function jdbcProductRuntimeSelectionId(profile: JdbcProductProfileDefinition, modeId: string): string {
return `${profile.id}:${modeId}`;
}
function mavenBundleId(coordinate: string): string {
return coordinate
.trim()
.split("")
.map((character) => (/^[A-Za-z0-9.-]$/.test(character) ? character : "_"))
.join("");
}
export function isJdbcProductManagedMavenPath(profile: JdbcProductProfileDefinition, path: string): boolean {
const normalized = path.replaceAll("\\", "/");
return jdbcProductManagedCoordinates(profile).some((coordinate) => normalized.split("/").includes(mavenBundleId(coordinate)));
}
export function isJdbcProductRuntimeInstallError(profile: JdbcProductProfileDefinition, message: string): boolean {
return message.includes(profile.missingDriverError) || message.includes(profile.missingPluginError);
}
export async function ensureJdbcProductRuntimeDrivers(profile: JdbcProductProfileDefinition, config: ConnectionConfig, api: JdbcProductRuntimeApi): Promise<JdbcProductRuntimeResult | undefined> {
if (!jdbcProductProfileMatches(profile, config)) return undefined;
const modeId = profile.detectMode(config);
const mode = jdbcProductMode(profile, modeId);
const defaults = jdbcProductConnectionDefaults(profile, modeId);
config.connection_string = config.connection_string?.trim() || defaults.connectionString;
config.jdbc_driver_class = config.jdbc_driver_class?.trim() || defaults.driverClass;
const configuredPaths = (config.jdbc_driver_paths ?? []).map((path) => path.trim()).filter(Boolean);
const pluginStatus = await api.jdbcPluginStatus();
if (!pluginStatus.installed || !pluginStatus.compatible) {
throw new JdbcProductRuntimeError("plugin-missing", profile);
}
const bundles = await api.listJdbcMavenBundles();
const managedCoordinates = jdbcProductManagedCoordinates(profile);
const managedBundles = bundles.filter((bundle) => managedCoordinates.includes(bundle.coordinate));
const managedPaths = new Set(managedBundles.flatMap((bundle) => bundle.artifacts.map((artifact) => artifact.path).filter(Boolean)));
const hasCustomRuntime = configuredPaths.some((path) => profile.isCompatibleRuntimePath(path, modeId) && !managedPaths.has(path) && !isJdbcProductManagedMavenPath(profile, path));
if (hasCustomRuntime) {
config.jdbc_driver_paths = configuredPaths;
return { profile, mode, bundles, paths: configuredPaths };
}
const installedCoordinates = new Set(bundles.map((bundle) => bundle.coordinate));
if (mode.managedCoordinates.some((coordinate) => !installedCoordinates.has(coordinate))) {
throw new JdbcProductRuntimeError("driver-missing", profile);
}
const customPaths = configuredPaths.filter((path) => !managedPaths.has(path) && !isJdbcProductManagedMavenPath(profile, path));
const runtimePaths = jdbcProductManagedRuntimePaths(profile, bundles, modeId);
if (runtimePaths.length === 0) {
throw new JdbcProductRuntimeError("driver-missing", profile);
}
const paths = Array.from(new Set([...customPaths, ...runtimePaths]));
config.jdbc_driver_paths = paths;
return {
profile,
mode,
bundles,
paths,
runtimeSelectionId: jdbcProductRuntimeSelectionId(profile, modeId),
};
}
@@ -0,0 +1,57 @@
import type { ConnectionConfig } from "@/types/database";
import { PHOENIX_JDBC_PRODUCT_PROFILE } from "@/lib/database/phoenixConnection";
import { createJdbcProductProfileRegistry, ensureJdbcProductRuntimeDrivers, isJdbcProductRuntimeInstallError, jdbcProductProfileMatches, validateJdbcProductManagedDriver, type JdbcProductProfileDefinition, type JdbcProductRuntimeApi } from "@/lib/database/jdbcProductProfile";
import { managedJdbcDriverDefinition } from "@/lib/database/managedJdbcDrivers";
export const JDBC_PRODUCT_PROFILES = [PHOENIX_JDBC_PRODUCT_PROFILE] as const satisfies readonly JdbcProductProfileDefinition[];
const jdbcProductProfileRegistry = createJdbcProductProfileRegistry(JDBC_PRODUCT_PROFILES);
for (const profile of JDBC_PRODUCT_PROFILES) {
validateJdbcProductManagedDriver(profile, managedJdbcDriverDefinition(profile.managedDriverId));
}
export function jdbcProductProfileDefinition(id: string): JdbcProductProfileDefinition | undefined {
return jdbcProductProfileRegistry.byId.get(id);
}
export function jdbcProductProfileForConfig(config: Pick<ConnectionConfig, "db_type" | "driver_profile">): JdbcProductProfileDefinition | undefined {
return JDBC_PRODUCT_PROFILES.find((profile) => jdbcProductProfileMatches(profile, config));
}
export function jdbcProductDriverProfiles() {
return Object.fromEntries(
JDBC_PRODUCT_PROFILES.map((profile) => [
profile.id,
{
type: profile.match.dbType,
port: profile.port,
user: profile.user,
label: profile.label,
icon: profile.icon,
},
]),
);
}
export function jdbcProductPickerOptions() {
return JDBC_PRODUCT_PROFILES.map((profile) => ({ value: profile.id, label: profile.label }));
}
export function jdbcProductIconTypes(): Record<string, string> {
return Object.fromEntries(JDBC_PRODUCT_PROFILES.map((profile) => [profile.id, profile.icon]));
}
export function jdbcProductProfileIdsForCategory(category: string): string[] {
return JDBC_PRODUCT_PROFILES.filter((profile) => profile.category === category).map((profile) => profile.id);
}
export function isRegisteredJdbcProductRuntimeInstallError(config: Pick<ConnectionConfig, "db_type" | "driver_profile">, message: string): boolean {
const profile = jdbcProductProfileForConfig(config);
return Boolean(profile && isJdbcProductRuntimeInstallError(profile, message));
}
export async function ensureRegisteredJdbcProductRuntimeDrivers(config: ConnectionConfig, api: JdbcProductRuntimeApi) {
const profile = jdbcProductProfileForConfig(config);
if (!profile) return undefined;
return ensureJdbcProductRuntimeDrivers(profile, config, api);
}
@@ -0,0 +1,110 @@
import type { AgentDriverInfo } from "@/lib/backend/api";
import type { JdbcDriverInfo, JdbcMavenBundleInfo, JdbcPluginStatus } from "@/types/database";
export type ManagedJdbcBundleDefinition = {
coordinate: string;
repositories: readonly string[];
};
export type ManagedJdbcDriverDefinition = {
id: string;
label: string;
version: string;
jre: string;
bundles: readonly ManagedJdbcBundleDefinition[];
};
export type ManagedJdbcDriverApi = {
installJdbcPlugin: () => Promise<JdbcPluginStatus>;
installJdbcDriverFromMaven: (coordinate: string, repositories?: string[]) => Promise<JdbcDriverInfo[]>;
listJdbcMavenBundles: () => Promise<JdbcMavenBundleInfo[]>;
deleteJdbcMavenBundle: (bundleId: string) => Promise<JdbcDriverInfo[]>;
};
export type ManagedJdbcDriverMutationResult = {
drivers?: JdbcDriverInfo[];
bundles: JdbcMavenBundleInfo[];
pluginStatus?: JdbcPluginStatus;
};
export type ManagedJdbcDriverRegistry = {
definitions: readonly ManagedJdbcDriverDefinition[];
byId: ReadonlyMap<string, ManagedJdbcDriverDefinition>;
};
export function createManagedJdbcDriverRegistry(definitions: readonly ManagedJdbcDriverDefinition[]): ManagedJdbcDriverRegistry {
const byId = new Map<string, ManagedJdbcDriverDefinition>();
const coordinateOwners = new Map<string, string>();
for (const definition of definitions) {
if (!definition.id.trim()) throw new Error("Managed JDBC driver id must not be empty");
if (byId.has(definition.id)) throw new Error(`Duplicate managed JDBC driver id: ${definition.id}`);
if (definition.bundles.length === 0) throw new Error(`Managed JDBC driver must own at least one bundle: ${definition.id}`);
for (const bundle of definition.bundles) {
const owner = coordinateOwners.get(bundle.coordinate);
if (owner) throw new Error(`Managed JDBC coordinate ${bundle.coordinate} is owned by both ${owner} and ${definition.id}`);
coordinateOwners.set(bundle.coordinate, definition.id);
}
byId.set(definition.id, definition);
}
return { definitions: [...definitions], byId };
}
export function managedJdbcDriverBundles(definition: ManagedJdbcDriverDefinition, bundles: readonly JdbcMavenBundleInfo[]): JdbcMavenBundleInfo[] {
const bundleByCoordinate = new Map(bundles.map((bundle) => [bundle.coordinate, bundle]));
return definition.bundles.map(({ coordinate }) => bundleByCoordinate.get(coordinate)).filter((bundle): bundle is JdbcMavenBundleInfo => Boolean(bundle));
}
export function missingManagedJdbcDriverBundles(definition: ManagedJdbcDriverDefinition, bundles: readonly JdbcMavenBundleInfo[]): ManagedJdbcBundleDefinition[] {
const installedCoordinates = new Set(bundles.map((bundle) => bundle.coordinate));
return definition.bundles.filter(({ coordinate }) => !installedCoordinates.has(coordinate));
}
export function managedJdbcDriverPaths(definition: ManagedJdbcDriverDefinition, bundles: readonly JdbcMavenBundleInfo[], coordinates: readonly string[] = definition.bundles.map(({ coordinate }) => coordinate)): string[] {
const bundleByCoordinate = new Map(managedJdbcDriverBundles(definition, bundles).map((bundle) => [bundle.coordinate, bundle]));
const requiredBundles = coordinates.map((coordinate) => bundleByCoordinate.get(coordinate));
if (requiredBundles.some((bundle) => !bundle)) return [];
return requiredBundles.flatMap((bundle) => (bundle?.artifacts ?? []).map((artifact) => artifact.path).filter(Boolean));
}
export function managedJdbcDriverRow(definition: ManagedJdbcDriverDefinition, bundles: readonly JdbcMavenBundleInfo[], pluginStatus?: Pick<JdbcPluginStatus, "installed" | "compatible"> | null): AgentDriverInfo {
const installedBundles = managedJdbcDriverBundles(definition, bundles);
const pluginReady = pluginStatus?.installed === true && pluginStatus.compatible;
const installed = pluginReady && installedBundles.length === definition.bundles.length;
return {
db_type: definition.id,
label: definition.label,
version: definition.version,
size: installedBundles.reduce((bundleTotal, bundle) => bundleTotal + bundle.artifacts.reduce((artifactTotal, artifact) => artifactTotal + Number(artifact.size || 0), 0), 0),
installed,
installed_version: installed ? definition.version : null,
update_available: false,
requires_java_runtime: true,
jre: definition.jre,
jre_installed: true,
};
}
export async function installManagedJdbcDriver(definition: ManagedJdbcDriverDefinition, currentBundles: readonly JdbcMavenBundleInfo[], currentPluginStatus: Pick<JdbcPluginStatus, "installed" | "compatible"> | null | undefined, api: ManagedJdbcDriverApi): Promise<ManagedJdbcDriverMutationResult> {
let pluginStatus: JdbcPluginStatus | undefined;
if (!currentPluginStatus?.installed || !currentPluginStatus.compatible) {
pluginStatus = await api.installJdbcPlugin();
}
let bundles = [...currentBundles];
let drivers: JdbcDriverInfo[] | undefined;
for (const bundle of definition.bundles) {
if (bundles.some(({ coordinate }) => coordinate === bundle.coordinate)) continue;
drivers = await api.installJdbcDriverFromMaven(bundle.coordinate, [...bundle.repositories]);
bundles = await api.listJdbcMavenBundles();
}
return { drivers, bundles, pluginStatus };
}
export async function uninstallManagedJdbcDriver(definition: ManagedJdbcDriverDefinition, currentBundles: readonly JdbcMavenBundleInfo[], api: ManagedJdbcDriverApi): Promise<ManagedJdbcDriverMutationResult> {
let drivers: JdbcDriverInfo[] | undefined;
for (const bundle of managedJdbcDriverBundles(definition, currentBundles)) {
drivers = await api.deleteJdbcMavenBundle(bundle.id);
}
const bundles = await api.listJdbcMavenBundles();
return { drivers, bundles };
}
@@ -0,0 +1,32 @@
import type { JdbcMavenBundleInfo, JdbcPluginStatus } from "@/types/database";
import { PHOENIX_MANAGED_JDBC_DRIVER } from "@/lib/database/phoenixBuiltinDriver";
import { PRESTOSQL_MANAGED_JDBC_DRIVER } from "@/lib/database/prestoSqlBuiltinDriver";
import { createManagedJdbcDriverRegistry, installManagedJdbcDriver, managedJdbcDriverRow, uninstallManagedJdbcDriver, type ManagedJdbcDriverApi, type ManagedJdbcDriverDefinition, type ManagedJdbcDriverMutationResult } from "@/lib/database/managedJdbcDriver";
export const MANAGED_JDBC_DRIVERS = [PRESTOSQL_MANAGED_JDBC_DRIVER, PHOENIX_MANAGED_JDBC_DRIVER] as const satisfies readonly ManagedJdbcDriverDefinition[];
const managedJdbcDriverRegistry = createManagedJdbcDriverRegistry(MANAGED_JDBC_DRIVERS);
export function managedJdbcDriverDefinition(id: string): ManagedJdbcDriverDefinition | undefined {
return managedJdbcDriverRegistry.byId.get(id);
}
export function isManagedJdbcDriver(id: string): boolean {
return managedJdbcDriverRegistry.byId.has(id);
}
export function managedJdbcDriverRows(bundles: readonly JdbcMavenBundleInfo[], pluginStatus?: Pick<JdbcPluginStatus, "installed" | "compatible"> | null) {
return MANAGED_JDBC_DRIVERS.map((definition) => managedJdbcDriverRow(definition, bundles, pluginStatus));
}
export async function installRegisteredManagedJdbcDriver(id: string, bundles: readonly JdbcMavenBundleInfo[], pluginStatus: Pick<JdbcPluginStatus, "installed" | "compatible"> | null | undefined, api: ManagedJdbcDriverApi): Promise<ManagedJdbcDriverMutationResult | undefined> {
const definition = managedJdbcDriverDefinition(id);
if (!definition) return undefined;
return installManagedJdbcDriver(definition, bundles, pluginStatus, api);
}
export async function uninstallRegisteredManagedJdbcDriver(id: string, bundles: readonly JdbcMavenBundleInfo[], api: ManagedJdbcDriverApi): Promise<ManagedJdbcDriverMutationResult | undefined> {
const definition = managedJdbcDriverDefinition(id);
if (!definition) return undefined;
return uninstallManagedJdbcDriver(definition, bundles, api);
}
@@ -0,0 +1,16 @@
import { PHOENIX_DIRECT_LOGGING_MAVEN_COORDINATE, PHOENIX_DIRECT_MAVEN_COORDINATE, PHOENIX_DRIVER_PROFILE, PHOENIX_QUERY_SERVER_MAVEN_COORDINATE } from "@/lib/database/phoenixConnection";
import type { ManagedJdbcDriverDefinition } from "@/lib/database/managedJdbcDriver";
export const PHOENIX_JDBC_DRIVER_VERSION = "5.2.1 / 6.0.0";
export const PHOENIX_MANAGED_MAVEN_COORDINATES = [PHOENIX_DIRECT_MAVEN_COORDINATE, PHOENIX_DIRECT_LOGGING_MAVEN_COORDINATE, PHOENIX_QUERY_SERVER_MAVEN_COORDINATE] as const;
export const PHOENIX_MANAGED_JDBC_DRIVER: ManagedJdbcDriverDefinition = {
id: PHOENIX_DRIVER_PROFILE,
label: "Apache Phoenix JDBC",
version: PHOENIX_JDBC_DRIVER_VERSION,
jre: "21",
bundles: PHOENIX_MANAGED_MAVEN_COORDINATES.map((coordinate) => ({
coordinate,
repositories: ["https://repo.maven.apache.org/maven2/"],
})),
};
@@ -0,0 +1,68 @@
import type { ConnectionConfig } from "@/types/database";
import type { JdbcProductProfileDefinition } from "@/lib/database/jdbcProductProfile";
export const PHOENIX_DRIVER_PROFILE = "phoenix";
export const PHOENIX_DIRECT_JDBC_URL = "jdbc:phoenix:localhost";
export const PHOENIX_DIRECT_JDBC_DRIVER_CLASS = "org.apache.phoenix.jdbc.PhoenixDriver";
export const PHOENIX_QUERY_SERVER_JDBC_URL = "jdbc:phoenix:thin:url=http://127.0.0.1:8765;serialization=PROTOBUF";
export const PHOENIX_QUERY_SERVER_JDBC_DRIVER_CLASS = "org.apache.phoenix.queryserver.client.Driver";
export const PHOENIX_MAVEN_REPOSITORY = "https://repo.maven.apache.org/maven2/";
export const PHOENIX_DIRECT_MAVEN_COORDINATE = "org.apache.phoenix:phoenix-client-embedded-hbase-2.5:5.2.1";
export const PHOENIX_QUERY_SERVER_MAVEN_COORDINATE = "org.apache.phoenix:phoenix-queryserver-client:6.0.0";
export const PHOENIX_DIRECT_LOGGING_MAVEN_COORDINATE = "ch.qos.reload4j:reload4j:1.2.26";
export const PHOENIX_DRIVER_NOT_INSTALLED_ERROR = "Apache Phoenix JDBC driver is not installed. Install it from the Driver Manager, then retry.";
export const PHOENIX_JDBC_PLUGIN_NOT_INSTALLED_ERROR = "DBX JDBC plugin is not installed. Install Apache Phoenix JDBC from the Driver Manager, then retry.";
export type PhoenixConnectionMode = "direct" | "query-server";
type PhoenixConnectionConfig = Partial<Pick<ConnectionConfig, "connection_string" | "jdbc_driver_class">>;
export const PHOENIX_JDBC_PRODUCT_PROFILE: JdbcProductProfileDefinition = {
id: PHOENIX_DRIVER_PROFILE,
label: "Apache Phoenix",
icon: "phoenix",
category: "analytics",
port: 0,
user: "",
match: { dbType: "jdbc", driverProfile: PHOENIX_DRIVER_PROFILE },
managedDriverId: PHOENIX_DRIVER_PROFILE,
runtimeLabelKey: "connection.phoenixRuntimeOption",
driverManagerHintPrefixKey: "connection.phoenixDriverManagerHintPrefix",
driverManagerHintSuffixKey: "connection.phoenixDriverManagerHintSuffix",
docsLabelKey: "connection.phoenixDocs",
docsUrl: "https://phoenix.apache.org/docs/fundamentals/client-classpath-and-jdbc-url/",
missingDriverError: PHOENIX_DRIVER_NOT_INSTALLED_ERROR,
missingPluginError: PHOENIX_JDBC_PLUGIN_NOT_INSTALLED_ERROR,
modes: [
{
id: "direct",
labelKey: "connection.phoenixDirectMode",
hintKey: "connection.phoenixDirectModeHint",
defaultConnectionString: PHOENIX_DIRECT_JDBC_URL,
defaultDriverClass: PHOENIX_DIRECT_JDBC_DRIVER_CLASS,
managedCoordinates: [PHOENIX_DIRECT_MAVEN_COORDINATE, PHOENIX_DIRECT_LOGGING_MAVEN_COORDINATE],
},
{
id: "query-server",
labelKey: "connection.phoenixQueryServerMode",
hintKey: "connection.phoenixQueryServerModeHint",
defaultConnectionString: PHOENIX_QUERY_SERVER_JDBC_URL,
defaultDriverClass: PHOENIX_QUERY_SERVER_JDBC_DRIVER_CLASS,
managedCoordinates: [PHOENIX_QUERY_SERVER_MAVEN_COORDINATE],
},
],
detectMode: (config) => phoenixConnectionModeForConfig(config),
isCompatibleRuntimePath: (path, mode) => isPhoenixRuntimePath(path, mode as PhoenixConnectionMode),
};
export function phoenixConnectionModeForConfig(config: PhoenixConnectionConfig): PhoenixConnectionMode {
const connectionString = config.connection_string?.trim().toLowerCase() || "";
const driverClass = config.jdbc_driver_class?.trim().toLowerCase() || "";
return connectionString.startsWith("jdbc:phoenix:thin:") || driverClass === PHOENIX_QUERY_SERVER_JDBC_DRIVER_CLASS.toLowerCase() ? "query-server" : "direct";
}
export function isPhoenixRuntimePath(path: string, mode: PhoenixConnectionMode): boolean {
const parts = path.split(/[/\\]/);
const fileName = parts[parts.length - 1] || path;
return mode === "direct" ? /^phoenix-client-(?:embedded-)?hbase-.+\.jar$/i.test(fileName) : /^phoenix-queryserver-client-.+\.jar$/i.test(fileName);
}
@@ -1,31 +1,23 @@
import type { AgentDriverInfo } from "@/lib/backend/api";
import type { JdbcMavenBundleInfo } from "@/types/database";
import { managedJdbcDriverPaths, type ManagedJdbcDriverDefinition } from "@/lib/database/managedJdbcDriver";
export const PRESTOSQL_DRIVER_DB_TYPE = "prestosql";
export const PRESTOSQL_JDBC_DRIVER_VERSION = "350";
export const PRESTOSQL_JDBC_DRIVER_COORDINATE = `io.prestosql:presto-jdbc:${PRESTOSQL_JDBC_DRIVER_VERSION}`;
export const PRESTOSQL_JDBC_DRIVER_REPOSITORY = "https://repo.maven.apache.org/maven2/";
export function prestoSqlMavenBundle(bundles: JdbcMavenBundleInfo[]): JdbcMavenBundleInfo | undefined {
return bundles.find((bundle) => bundle.coordinate === PRESTOSQL_JDBC_DRIVER_COORDINATE);
}
export const PRESTOSQL_MANAGED_JDBC_DRIVER: ManagedJdbcDriverDefinition = {
id: PRESTOSQL_DRIVER_DB_TYPE,
label: "PrestoSQL",
version: PRESTOSQL_JDBC_DRIVER_VERSION,
jre: "21",
bundles: [
{
coordinate: PRESTOSQL_JDBC_DRIVER_COORDINATE,
repositories: [PRESTOSQL_JDBC_DRIVER_REPOSITORY],
},
],
};
export function prestoSqlBuiltinDriverPaths(bundles: JdbcMavenBundleInfo[]): string[] {
return (prestoSqlMavenBundle(bundles)?.artifacts ?? []).map((artifact) => artifact.path).filter(Boolean);
}
export function prestoSqlBuiltinDriverRow(bundles: JdbcMavenBundleInfo[]): AgentDriverInfo {
const installedBundle = prestoSqlMavenBundle(bundles);
return {
db_type: PRESTOSQL_DRIVER_DB_TYPE,
label: "PrestoSQL",
version: PRESTOSQL_JDBC_DRIVER_VERSION,
size: installedBundle ? installedBundle.artifacts.reduce((total, artifact) => total + Number(artifact.size || 0), 0) : 0,
installed: Boolean(installedBundle),
installed_version: installedBundle ? PRESTOSQL_JDBC_DRIVER_VERSION : null,
update_available: false,
requires_java_runtime: true,
jre: "21",
jre_installed: true,
};
return managedJdbcDriverPaths(PRESTOSQL_MANAGED_JDBC_DRIVER, bundles);
}