feat: MariaDB type layer — union, capabilities, icons, ports, URI parse/copy, filter (Task 2)
This commit is contained in:
@@ -125,6 +125,30 @@ describe("parseConnectionString", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("parses mariadb:// with the 3306 default port", () => {
|
||||
const result = parseConnectionString("mariadb://root:pw@localhost/app");
|
||||
expect(result).toEqual({
|
||||
db_type: "mariadb",
|
||||
host: "localhost",
|
||||
port: 3306,
|
||||
username: "root",
|
||||
password: "pw",
|
||||
database: "app",
|
||||
});
|
||||
});
|
||||
|
||||
it("keeps mysql:// mapped to the mysql db_type", () => {
|
||||
const result = parseConnectionString("mysql://root:pw@localhost:3307/app");
|
||||
expect(result).toEqual({
|
||||
db_type: "mysql",
|
||||
host: "localhost",
|
||||
port: 3307,
|
||||
username: "root",
|
||||
password: "pw",
|
||||
database: "app",
|
||||
});
|
||||
});
|
||||
|
||||
it("defaults Redis port to 6379 when omitted", () => {
|
||||
const result = parseConnectionString("redis://localhost");
|
||||
expect(result).toEqual({
|
||||
@@ -212,6 +236,22 @@ describe("buildConnectionUrl", () => {
|
||||
expect(url).toBe("mysql://root:pw@127.0.0.1:3306/app");
|
||||
});
|
||||
|
||||
it("emits mariadb:// for a mariadb connection", () => {
|
||||
const url = buildConnectionUrl(
|
||||
makeConn({ db_type: "mariadb", host: "127.0.0.1", port: 3306, username: "root", database: "app" }),
|
||||
"pw",
|
||||
);
|
||||
expect(url).toBe("mariadb://root:pw@127.0.0.1:3306/app");
|
||||
});
|
||||
|
||||
it("omits the password in the mariadb no-password variant", () => {
|
||||
const url = buildConnectionUrl(
|
||||
makeConn({ db_type: "mariadb", host: "127.0.0.1", port: 3306, username: "root", database: "app" }),
|
||||
null,
|
||||
);
|
||||
expect(url).toBe("mariadb://root@127.0.0.1:3306/app");
|
||||
});
|
||||
|
||||
it("builds a Redis URL with password but no username", () => {
|
||||
const url = buildConnectionUrl(
|
||||
makeConn({ db_type: "redis", host: "localhost", port: 6379, username: null, database: "0" }),
|
||||
|
||||
@@ -13,6 +13,7 @@ const DB_PROTOCOLS: Record<string, DbType> = {
|
||||
"postgresql:": "postgresql",
|
||||
"postgres:": "postgresql",
|
||||
"mysql:": "mysql",
|
||||
"mariadb:": "mariadb",
|
||||
"sqlite:": "sqlite",
|
||||
"file:": "sqlite",
|
||||
"redis:": "redis",
|
||||
@@ -22,6 +23,7 @@ const DB_PROTOCOLS: Record<string, DbType> = {
|
||||
const DEFAULT_PORTS: Record<DbType, number | null> = {
|
||||
postgresql: 5432,
|
||||
mysql: 3306,
|
||||
mariadb: 3306,
|
||||
sqlite: null,
|
||||
redis: 6379,
|
||||
};
|
||||
@@ -127,6 +129,8 @@ export function buildConnectionUrl(conn: Connection, password: string | null): s
|
||||
}
|
||||
case "mysql":
|
||||
return `mysql://${auth}${conn.host}${port}${db}`;
|
||||
case "mariadb":
|
||||
return `mariadb://${auth}${conn.host}${port}${db}`;
|
||||
case "sqlite":
|
||||
// host stores the file path; `sqlite:///abs/path` round-trips through
|
||||
// parseConnectionString (pathname → host).
|
||||
|
||||
@@ -23,6 +23,16 @@ describe("dbCapabilities", () => {
|
||||
expect(c.visualizer).toBe(false);
|
||||
});
|
||||
|
||||
it("gives MariaDB the same capability set as MySQL", () => {
|
||||
expect(DB_CAPABILITIES.mariadb).toEqual(DB_CAPABILITIES.mysql);
|
||||
});
|
||||
|
||||
it("covers exactly the five DbTypes", () => {
|
||||
expect(Object.keys(DB_CAPABILITIES).sort()).toEqual(
|
||||
["mariadb", "mysql", "postgresql", "redis", "sqlite"],
|
||||
);
|
||||
});
|
||||
|
||||
it("gives SQLite explorer/queries/visualizer/editing/import/ddl/tools/tableManagement but not objects", () => {
|
||||
const c = DB_CAPABILITIES.sqlite;
|
||||
expect(c.explorer).toBe(true);
|
||||
|
||||
@@ -36,6 +36,7 @@ const ALL_FALSE: DbCapabilities = {
|
||||
export const DB_CAPABILITIES: Record<DbType, DbCapabilities> = {
|
||||
postgresql: { ...ALL_FALSE, explorer: true, queries: true, objects: true, visualizer: true, tools: true, editing: true, import: true, ddl: true, objectCrud: true, maintenance: true, roles: true, tableManagement: true },
|
||||
mysql: { ...ALL_FALSE, explorer: true, queries: true, editing: true, import: true, ddl: true, tools: true },
|
||||
mariadb: { ...ALL_FALSE, explorer: true, queries: true, editing: true, import: true, ddl: true, tools: true },
|
||||
sqlite: { ...ALL_FALSE, explorer: true, queries: true, visualizer: true, editing: true, import: true, ddl: true, tools: true, tableManagement: true },
|
||||
redis: { ...ALL_FALSE },
|
||||
};
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { siPostgresql, siMysql, siSqlite, siRedis, siSupabase, siNeon } from "simple-icons";
|
||||
import { siPostgresql, siMysql, siMariadb, siSqlite, siRedis, siSupabase, siNeon } from "simple-icons";
|
||||
import type { DbType } from "./types";
|
||||
|
||||
// Brand colors from simple-icons
|
||||
const DB_COLORS: Record<DbType, string> = {
|
||||
postgresql: `#${siPostgresql.hex}`,
|
||||
mysql: `#${siMysql.hex}`,
|
||||
mariadb: `#${siMariadb.hex}`,
|
||||
redis: `#${siRedis.hex}`,
|
||||
sqlite: `#${siSqlite.hex}`,
|
||||
};
|
||||
@@ -13,6 +14,7 @@ const DB_COLORS: Record<DbType, string> = {
|
||||
const DB_PATHS: Record<DbType, string> = {
|
||||
postgresql: siPostgresql.path,
|
||||
mysql: siMysql.path,
|
||||
mariadb: siMariadb.path,
|
||||
redis: siRedis.path,
|
||||
sqlite: siSqlite.path,
|
||||
};
|
||||
@@ -20,6 +22,7 @@ const DB_PATHS: Record<DbType, string> = {
|
||||
export const DB_LABELS: Record<DbType, string> = {
|
||||
postgresql: "PostgreSQL",
|
||||
mysql: "MySQL",
|
||||
mariadb: "MariaDB",
|
||||
redis: "Redis",
|
||||
sqlite: "SQLite",
|
||||
};
|
||||
@@ -54,6 +57,7 @@ export function DbIcon({ type, size = 20, className }: DbIconProps) {
|
||||
export const DB_ICONS: Record<DbType, string> = {
|
||||
postgresql: "🐘",
|
||||
mysql: "🐬",
|
||||
mariadb: "🦭",
|
||||
redis: "⚡",
|
||||
sqlite: "🗄️",
|
||||
};
|
||||
|
||||
@@ -2,12 +2,19 @@ import { describe, it, expect } from "vitest";
|
||||
import { PROVIDER_TABS, getProviderById, SETUP_GUIDES } from "./providers";
|
||||
|
||||
describe("providers", () => {
|
||||
it("defines exactly 6 provider tabs in order", () => {
|
||||
it("defines exactly 7 provider tabs in order", () => {
|
||||
expect(PROVIDER_TABS.map((p) => p.id)).toEqual([
|
||||
"postgresql", "mysql", "sqlite", "redis", "supabase", "neon",
|
||||
"postgresql", "mysql", "mariadb", "sqlite", "redis", "supabase", "neon",
|
||||
]);
|
||||
});
|
||||
|
||||
it("includes a plain (non-managed) MariaDB tab persisting db_type mariadb", () => {
|
||||
const tab = getProviderById("mariadb")!;
|
||||
expect(tab.dbType).toBe("mariadb");
|
||||
expect(tab.isManagedPreset).toBe(false);
|
||||
expect(tab.label).toBe("MariaDB");
|
||||
});
|
||||
|
||||
it("marks only supabase and neon as managed presets mapping to postgresql", () => {
|
||||
const pg = getProviderById("postgresql")!;
|
||||
expect(pg.dbType).toBe("postgresql");
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { DbType } from "./types";
|
||||
|
||||
export type ProviderId = "postgresql" | "mysql" | "sqlite" | "redis" | "supabase" | "neon";
|
||||
export type ProviderId = "postgresql" | "mysql" | "mariadb" | "sqlite" | "redis" | "supabase" | "neon";
|
||||
|
||||
export interface ProviderTab {
|
||||
id: ProviderId;
|
||||
@@ -15,6 +15,7 @@ export interface ProviderTab {
|
||||
export const PROVIDER_TABS: ProviderTab[] = [
|
||||
{ id: "postgresql", label: "PostgreSQL", dbType: "postgresql", isManagedPreset: false },
|
||||
{ id: "mysql", label: "MySQL", dbType: "mysql", isManagedPreset: false },
|
||||
{ id: "mariadb", label: "MariaDB", dbType: "mariadb", isManagedPreset: false },
|
||||
{ id: "sqlite", label: "SQLite", dbType: "sqlite", isManagedPreset: false },
|
||||
{ id: "redis", label: "Redis", dbType: "redis", isManagedPreset: false },
|
||||
{ id: "supabase", label: "Supabase", dbType: "postgresql", isManagedPreset: true },
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type DbType = "postgresql" | "mysql" | "sqlite" | "redis";
|
||||
export type DbType = "postgresql" | "mysql" | "mariadb" | "sqlite" | "redis";
|
||||
|
||||
export type Theme = "dark" | "light" | "system";
|
||||
export type FontSize = "small" | "medium" | "large";
|
||||
|
||||
@@ -31,7 +31,7 @@ export function abbreviateType(dataType: string): string {
|
||||
return TYPE_ABBREV[lower] ?? dataType;
|
||||
}
|
||||
|
||||
const VALID_DB_TYPES: DbType[] = ["postgresql", "mysql", "sqlite", "redis"];
|
||||
const VALID_DB_TYPES: DbType[] = ["postgresql", "mysql", "mariadb", "sqlite", "redis"];
|
||||
|
||||
export function validateConnectionInput(input: ConnectionInput): ValidationResult {
|
||||
if (!input.name || input.name.length === 0) return { ok: false, error: "name is required" };
|
||||
|
||||
Reference in New Issue
Block a user