mirror of
https://github.com/ershisan99/db-studio.git
synced 2025-12-16 12:33:05 +00:00
refactor table details queries (indexes, foreign_keys, columns)
This commit is contained in:
@@ -5,7 +5,7 @@ import postgres from "postgres";
|
|||||||
const DB_URL = Bun.env.DB_URL;
|
const DB_URL = Bun.env.DB_URL;
|
||||||
|
|
||||||
if (!DB_URL) {
|
if (!DB_URL) {
|
||||||
console.error("DB_URL not found in environment variables");
|
console.error("❗DB_URL not found in environment variables");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,34 +28,46 @@ const app = new Elysia({ prefix: "/api" })
|
|||||||
|
|
||||||
return new Response(JSON.stringify(tables, null, 2)).json();
|
return new Response(JSON.stringify(tables, null, 2)).json();
|
||||||
})
|
})
|
||||||
.get("databases/:dbName/tables/:name/data", async ({ params, query }) => {
|
.get(
|
||||||
const { name, dbName } = params;
|
"databases/:dbName/tables/:tableName/data",
|
||||||
const { perPage = "50", page = "0" } = query;
|
async ({ params, query }) => {
|
||||||
return getTableData({
|
const { tableName, dbName } = params;
|
||||||
tableName: name,
|
const { perPage = "50", page = "0" } = query;
|
||||||
dbName,
|
return getTableData({
|
||||||
perPage: Number.parseInt(perPage, 10),
|
tableName,
|
||||||
page: Number.parseInt(page, 10),
|
dbName,
|
||||||
});
|
perPage: Number.parseInt(perPage, 10),
|
||||||
})
|
page: Number.parseInt(page, 10),
|
||||||
.get("db/tables/:name/columns", async ({ params, query }) => {
|
});
|
||||||
const { name } = params;
|
},
|
||||||
|
)
|
||||||
|
.get(
|
||||||
|
"databases/:dbName/tables/:tableName/columns",
|
||||||
|
async ({ params, query }) => {
|
||||||
|
const { tableName, dbName } = params;
|
||||||
|
|
||||||
const columns = await getColumns(name);
|
const columns = await getColumns(dbName, tableName);
|
||||||
return new Response(JSON.stringify(columns, null, 2)).json();
|
return new Response(JSON.stringify(columns, null, 2)).json();
|
||||||
})
|
},
|
||||||
.get("db/tables/:name/indexes", async ({ params, query }) => {
|
)
|
||||||
const { name } = params;
|
.get(
|
||||||
|
"databases/:dbName/tables/:tableName/indexes",
|
||||||
|
async ({ params, query }) => {
|
||||||
|
const { tableName, dbName } = params;
|
||||||
|
|
||||||
const indexes = await getIndexes(name);
|
const indexes = await getIndexes(dbName, tableName);
|
||||||
return new Response(JSON.stringify(indexes, null, 2)).json();
|
return new Response(JSON.stringify(indexes, null, 2)).json();
|
||||||
})
|
},
|
||||||
.get("db/tables/:name/foreign-keys", async ({ params, query }) => {
|
)
|
||||||
const { name } = params;
|
.get(
|
||||||
|
"databases/:dbName/tables/:tableName/foreign-keys",
|
||||||
|
async ({ params, query }) => {
|
||||||
|
const { tableName, dbName } = params;
|
||||||
|
|
||||||
const foreignKeys = await getForeignKeys(name);
|
const foreignKeys = await getForeignKeys(dbName, tableName);
|
||||||
return new Response(JSON.stringify(foreignKeys, null, 2)).json();
|
return new Response(JSON.stringify(foreignKeys, null, 2)).json();
|
||||||
})
|
},
|
||||||
|
)
|
||||||
.use(cors())
|
.use(cors())
|
||||||
.listen(3000);
|
.listen(3000);
|
||||||
|
|
||||||
@@ -63,13 +75,12 @@ console.log(
|
|||||||
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
|
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`,
|
||||||
);
|
);
|
||||||
|
|
||||||
async function getIndexes(table: string) {
|
async function getIndexes(dbName: string, tableName: string) {
|
||||||
const returnObj = {};
|
|
||||||
|
|
||||||
const [tableOidResult] = await sql`
|
const [tableOidResult] = await sql`
|
||||||
SELECT oid
|
SELECT oid
|
||||||
FROM pg_class
|
FROM pg_class
|
||||||
WHERE relname = ${table}
|
WHERE relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = ${dbName})
|
||||||
|
AND relname = ${tableName}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const tableOid = tableOidResult.oid;
|
const tableOid = tableOidResult.oid;
|
||||||
@@ -114,7 +125,7 @@ async function getIndexes(table: string) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getColumns(tableName: string) {
|
async function getColumns(dbName: string, tableName: string) {
|
||||||
return await sql`
|
return await sql`
|
||||||
SELECT
|
SELECT
|
||||||
cols.column_name,
|
cols.column_name,
|
||||||
@@ -129,12 +140,14 @@ async function getColumns(tableName: string) {
|
|||||||
pg_catalog.pg_description AS pgd ON pgd.objoid = st.relid AND pgd.objsubid = cols.ordinal_position
|
pg_catalog.pg_description AS pgd ON pgd.objoid = st.relid AND pgd.objsubid = cols.ordinal_position
|
||||||
WHERE
|
WHERE
|
||||||
cols.table_name = ${tableName}
|
cols.table_name = ${tableName}
|
||||||
|
AND cols.table_schema = ${dbName}
|
||||||
|
|
||||||
ORDER BY
|
ORDER BY
|
||||||
cols.ordinal_position;
|
cols.ordinal_position;
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getForeignKeys(table: string) {
|
async function getForeignKeys(dbName: string, tableName: string) {
|
||||||
const result = await sql`
|
const result = await sql`
|
||||||
SELECT
|
SELECT
|
||||||
conname,
|
conname,
|
||||||
@@ -147,7 +160,8 @@ async function getForeignKeys(table: string) {
|
|||||||
SELECT pc.oid
|
SELECT pc.oid
|
||||||
FROM pg_class AS pc
|
FROM pg_class AS pc
|
||||||
INNER JOIN pg_namespace AS pn ON (pn.oid = pc.relnamespace)
|
INNER JOIN pg_namespace AS pn ON (pn.oid = pc.relnamespace)
|
||||||
WHERE pc.relname = ${table}
|
WHERE pc.relname = ${tableName}
|
||||||
|
AND pn.nspname = ${dbName}
|
||||||
)
|
)
|
||||||
AND contype = 'f'::char
|
AND contype = 'f'::char
|
||||||
ORDER BY conkey, conname
|
ORDER BY conkey, conname
|
||||||
@@ -157,10 +171,6 @@ async function getForeignKeys(table: string) {
|
|||||||
const match = row.definition.match(
|
const match = row.definition.match(
|
||||||
/FOREIGN KEY\s*\((.+)\)\s*REFERENCES (.+)\((.+)\)(.*)$/iy,
|
/FOREIGN KEY\s*\((.+)\)\s*REFERENCES (.+)\((.+)\)(.*)$/iy,
|
||||||
);
|
);
|
||||||
console.log(match);
|
|
||||||
console.log("match[0]", match[0]);
|
|
||||||
console.log("match[1]", match[1]);
|
|
||||||
console.log("match[2]", match[2]);
|
|
||||||
if (match) {
|
if (match) {
|
||||||
const sourceColumns = match[1]
|
const sourceColumns = match[1]
|
||||||
.split(",")
|
.split(",")
|
||||||
@@ -168,7 +178,6 @@ async function getForeignKeys(table: string) {
|
|||||||
const targetTableMatch = match[2].match(
|
const targetTableMatch = match[2].match(
|
||||||
/^(("([^"]|"")+"|[^"]+)\.)?"?("([^"]|"")+"|[^"]+)$/,
|
/^(("([^"]|"")+"|[^"]+)\.)?"?("([^"]|"")+"|[^"]+)$/,
|
||||||
);
|
);
|
||||||
console.log("targetTableMatch", targetTableMatch);
|
|
||||||
const targetTable = targetTableMatch ? targetTableMatch[0].trim() : null;
|
const targetTable = targetTableMatch ? targetTableMatch[0].trim() : null;
|
||||||
const targetColumns = match[3]
|
const targetColumns = match[3]
|
||||||
.split(",")
|
.split(",")
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ export const DataTable = ({
|
|||||||
onPageIndexChange: (pageIndex: number) => void;
|
onPageIndexChange: (pageIndex: number) => void;
|
||||||
onPageSizeChange: (pageSize: number) => void;
|
onPageSizeChange: (pageSize: number) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const { data: details } = useTableColumnsQuery({ name: tableName });
|
const { data: details } = useTableColumnsQuery({ dbName, tableName });
|
||||||
const { data } = useTableDataQuery({
|
const { data } = useTableDataQuery({
|
||||||
tableName,
|
tableName,
|
||||||
dbName,
|
dbName,
|
||||||
|
|||||||
@@ -79,17 +79,20 @@ const tableForeignKeysColumns = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
function TableDetailsTable() {
|
function TableDetailsTable() {
|
||||||
const { tableName: name } = Route.useParams();
|
const { tableName, dbName } = Route.useParams();
|
||||||
const { data: tableColumns } = useTableColumnsQuery({ name });
|
const { data: tableColumns } = useTableColumnsQuery({ tableName, dbName });
|
||||||
const { data: tableIndexes } = useTableIndexesQuery({ name });
|
const { data: tableIndexes } = useTableIndexesQuery({ tableName, dbName });
|
||||||
const { data: tableForeignKeys } = useTableForeignKeysQuery({ name });
|
const { data: tableForeignKeys } = useTableForeignKeysQuery({
|
||||||
|
tableName,
|
||||||
|
dbName,
|
||||||
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={"p-3 w-layout"}>
|
<div className={"p-3 w-layout"}>
|
||||||
<div className={"flex flex-col gap-4 flex-1 max-h-full pb-3"}>
|
<div className={"flex flex-col gap-4 flex-1 max-h-full pb-3"}>
|
||||||
<div className={"flex gap-4 items-center justify-between"}>
|
<div className={"flex gap-4 items-center justify-between"}>
|
||||||
<h1 className={"text-2xl font-bold flex items-center gap-2"}>
|
<h1 className={"text-2xl font-bold flex items-center gap-2"}>
|
||||||
<Table2 /> {name}
|
<Table2 /> {tableName}
|
||||||
</h1>
|
</h1>
|
||||||
<Link
|
<Link
|
||||||
from={Route.fullPath}
|
from={Route.fullPath}
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
import { keepPreviousData, useQuery } from "@tanstack/react-query";
|
||||||
import { DB_QUERY_KEYS } from "./db.query-keys";
|
import { DB_QUERY_KEYS } from "./db.query-keys";
|
||||||
import { dbService } from "./db.service";
|
import { dbService } from "./db.service";
|
||||||
import type { GetTableDataArgs, GetTablesListArgs } from "./db.types";
|
import type {
|
||||||
|
GetTableColumnsArgs,
|
||||||
|
GetTableDataArgs,
|
||||||
|
GetTableForeignKeysArgs,
|
||||||
|
GetTableIndexesArgs,
|
||||||
|
GetTablesListArgs,
|
||||||
|
} from "./db.types";
|
||||||
|
|
||||||
export const useDatabasesListQuery = () => {
|
export const useDatabasesListQuery = () => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
@@ -38,27 +44,36 @@ export const useTableDataQuery = (args: GetTableDataArgs) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTableColumnsQuery = (args: { name?: string }) => {
|
export const useTableColumnsQuery = (args: GetTableColumnsArgs) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [DB_QUERY_KEYS.TABLES.COLUMNS, args],
|
queryKey: [DB_QUERY_KEYS.TABLES.COLUMNS, args],
|
||||||
queryFn: () => dbService.getTableColumns(args.name ?? ""),
|
queryFn: () => dbService.getTableColumns(args),
|
||||||
placeholderData: keepPreviousData,
|
placeholderData: (previousData, previousQuery) => {
|
||||||
enabled: !!args.name,
|
if (
|
||||||
|
typeof previousQuery?.queryKey[1] !== "string" &&
|
||||||
|
(previousQuery?.queryKey[1].dbName !== args.dbName ||
|
||||||
|
previousQuery?.queryKey[1].tableName !== args.tableName)
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return previousData;
|
||||||
|
},
|
||||||
|
enabled: !!args.tableName && !!args.dbName,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTableIndexesQuery = (args: { name?: string }) => {
|
export const useTableIndexesQuery = (args: GetTableIndexesArgs) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [DB_QUERY_KEYS.TABLES.INDEXES, args],
|
queryKey: [DB_QUERY_KEYS.TABLES.INDEXES, args],
|
||||||
queryFn: () => dbService.getTableIndexes(args.name ?? ""),
|
queryFn: () => dbService.getTableIndexes(args),
|
||||||
enabled: !!args.name,
|
enabled: !!args.tableName && !!args.dbName,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
export const useTableForeignKeysQuery = (args: { name?: string }) => {
|
export const useTableForeignKeysQuery = (args: GetTableForeignKeysArgs) => {
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: [DB_QUERY_KEYS.TABLES.FOREIGN_KEYS, args],
|
queryKey: [DB_QUERY_KEYS.TABLES.FOREIGN_KEYS, args],
|
||||||
queryFn: () => dbService.getTableForeignKeys(args.name ?? ""),
|
queryFn: () => dbService.getTableForeignKeys(args),
|
||||||
enabled: !!args.name,
|
enabled: !!args.tableName && !!args.dbName,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,8 +2,11 @@ import { getValuable } from "@/lib/utils";
|
|||||||
import { dbInstance } from "@/services/db/db.instance";
|
import { dbInstance } from "@/services/db/db.instance";
|
||||||
import type {
|
import type {
|
||||||
DatabasesResponse,
|
DatabasesResponse,
|
||||||
|
GetTableColumnsArgs,
|
||||||
GetTableDataArgs,
|
GetTableDataArgs,
|
||||||
GetTableDataResponse,
|
GetTableDataResponse,
|
||||||
|
GetTableForeignKeysArgs,
|
||||||
|
GetTableIndexesArgs,
|
||||||
GetTablesListArgs,
|
GetTablesListArgs,
|
||||||
GetTablesListResponse,
|
GetTablesListResponse,
|
||||||
TableColumns,
|
TableColumns,
|
||||||
@@ -32,17 +35,21 @@ class DbService {
|
|||||||
.json<GetTableDataResponse>();
|
.json<GetTableDataResponse>();
|
||||||
}
|
}
|
||||||
|
|
||||||
getTableColumns(name: string) {
|
getTableColumns({ dbName, tableName }: GetTableColumnsArgs) {
|
||||||
return dbInstance.get(`api/db/tables/${name}/columns`).json<TableColumns>();
|
|
||||||
}
|
|
||||||
|
|
||||||
getTableIndexes(name: string) {
|
|
||||||
return dbInstance.get(`api/db/tables/${name}/indexes`).json<TableIndexes>();
|
|
||||||
}
|
|
||||||
|
|
||||||
getTableForeignKeys(name: string) {
|
|
||||||
return dbInstance
|
return dbInstance
|
||||||
.get(`api/db/tables/${name}/foreign-keys`)
|
.get(`api/databases/${dbName}/tables/${tableName}/columns`)
|
||||||
|
.json<TableColumns>();
|
||||||
|
}
|
||||||
|
|
||||||
|
getTableIndexes({ dbName, tableName }: GetTableIndexesArgs) {
|
||||||
|
return dbInstance
|
||||||
|
.get(`api/databases/${dbName}/tables/${tableName}/indexes`)
|
||||||
|
.json<TableIndexes>();
|
||||||
|
}
|
||||||
|
|
||||||
|
getTableForeignKeys({ dbName, tableName }: GetTableForeignKeysArgs) {
|
||||||
|
return dbInstance
|
||||||
|
.get(`api/databases/${dbName}/tables/${tableName}/foreign-keys`)
|
||||||
.json<TableForeignKeys>();
|
.json<TableForeignKeys>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,12 @@ export type GetTableDataResponse = {
|
|||||||
data: Array<Record<string, any>>;
|
data: Array<Record<string, any>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Table Columns
|
||||||
|
export type GetTableColumnsArgs = {
|
||||||
|
tableName: string;
|
||||||
|
dbName: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TableColumn = {
|
export type TableColumn = {
|
||||||
column_name: string;
|
column_name: string;
|
||||||
data_type: string;
|
data_type: string;
|
||||||
@@ -41,6 +47,12 @@ export type TableColumn = {
|
|||||||
};
|
};
|
||||||
export type TableColumns = TableColumn[];
|
export type TableColumns = TableColumn[];
|
||||||
|
|
||||||
|
// Table Indexes
|
||||||
|
export type GetTableIndexesArgs = {
|
||||||
|
tableName: string;
|
||||||
|
dbName: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TableIndexEntry = {
|
export type TableIndexEntry = {
|
||||||
key: string;
|
key: string;
|
||||||
type: string;
|
type: string;
|
||||||
@@ -50,6 +62,11 @@ export type TableIndexEntry = {
|
|||||||
};
|
};
|
||||||
export type TableIndexes = TableIndexEntry[];
|
export type TableIndexes = TableIndexEntry[];
|
||||||
|
|
||||||
|
// Table Foreign Keys
|
||||||
|
export type GetTableForeignKeysArgs = {
|
||||||
|
tableName: string;
|
||||||
|
dbName: string;
|
||||||
|
};
|
||||||
export type TableForeignKey = {
|
export type TableForeignKey = {
|
||||||
conname: string;
|
conname: string;
|
||||||
deferrable: boolean;
|
deferrable: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user