refactor table details queries (indexes, foreign_keys, columns)

This commit is contained in:
2024-07-07 01:40:04 +02:00
parent bf40c7d308
commit 5d1658c522
6 changed files with 117 additions and 66 deletions

View File

@@ -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(",")

View File

@@ -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,

View File

@@ -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}

View File

@@ -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,
}); });
}; };

View File

@@ -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>();
} }
} }

View File

@@ -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;