From bb16b2db43fd29e3d394ae4fc803eaffbca663bb Mon Sep 17 00:00:00 2001 From: andres Date: Sun, 14 Jul 2024 12:50:17 +0200 Subject: [PATCH] add postgres where clause for table data query. add ui for where clause --- TODO.md | 13 +++++ api/src/drivers/driver.interface.ts | 6 +- api/src/drivers/postgres.ts | 25 +++++--- api/src/index.ts | 9 ++- .../components/db-table-view/data-table.tsx | 46 +++++++++++++-- frontend/src/components/ui/index.ts | 1 + frontend/src/components/ui/input.tsx | 29 ++++++++-- frontend/src/components/ui/tooltip.tsx | 58 +++++++++++++++++++ frontend/src/services/db/db.service.ts | 23 +++++++- frontend/src/services/db/db.types.ts | 1 + 10 files changed, 190 insertions(+), 21 deletions(-) create mode 100644 TODO.md create mode 100644 frontend/src/components/ui/tooltip.tsx diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..b5d4b10 --- /dev/null +++ b/TODO.md @@ -0,0 +1,13 @@ +# TODO + +## Databases + +- [ ] Fully support PostgreSQL and MySQL +- [ ] Add support for SQLite +- [ ] Add support for MSSQL +- [ ] Add support for MongoDB +- [ ] Add support for Redis + +## Nice to have + +- [ ] Seed database with fake data (something like mockaroo, but taking advantage of db introspection) \ No newline at end of file diff --git a/api/src/drivers/driver.interface.ts b/api/src/drivers/driver.interface.ts index cc60795..9a22ecb 100644 --- a/api/src/drivers/driver.interface.ts +++ b/api/src/drivers/driver.interface.ts @@ -24,7 +24,11 @@ export interface Driver { ): Promise; getTableData( credentials: Credentials, - args: WithSortPagination<{ tableName: string; dbName: string }>, + args: WithSortPagination<{ + tableName: string; + dbName: string; + whereQuery?: string; + }>, ): Promise<{ count: number; data: Record[]; diff --git a/api/src/drivers/postgres.ts b/api/src/drivers/postgres.ts index 6865dae..37cead4 100644 --- a/api/src/drivers/postgres.ts +++ b/api/src/drivers/postgres.ts @@ -159,22 +159,31 @@ export class PostgresDriver implements Driver { page, sortDesc, sortField, - }: WithSortPagination<{ tableName: string; dbName: string }>, + whereQuery, + }: WithSortPagination<{ + tableName: string; + dbName: string; + whereQuery?: string; + }>, ) { const sql = await this.queryRunner(credentials); const offset = (perPage * page).toString(); - const rows = sql` - SELECT COUNT(*) - FROM ${sql(dbName)}.${sql(tableName)}`; - const tables = sql` + const rowsQuery = ` + SELECT COUNT(*) + FROM "${dbName}"."${tableName}" + ${whereQuery ? `WHERE ${whereQuery}` : ""}`; + + const tablesQuery = ` SELECT * - FROM ${sql(dbName)}.${sql(tableName)} - ${sortField ? sql`ORDER BY ${sql(sortField)} ${sortDesc ? sql`DESC` : sql`ASC`}` : sql``} + FROM "${dbName}"."${tableName}" + ${whereQuery ? `WHERE ${whereQuery}` : ""} + ${sortField ? `ORDER BY "${sortField}" ${sortDesc ? "DESC" : "ASC"}` : ""} LIMIT ${perPage} OFFSET ${offset} `; - + const rows = sql.unsafe(rowsQuery); + const tables = sql.unsafe(tablesQuery); const [[count], data] = await Promise.all([rows, tables]); void sql.end(); diff --git a/api/src/index.ts b/api/src/index.ts index 6d84db6..adc2889 100644 --- a/api/src/index.ts +++ b/api/src/index.ts @@ -95,7 +95,13 @@ const app = new Elysia({ prefix: "/api" }) "/databases/:dbName/tables/:tableName/data", async ({ query, params, jwt, set, cookie: { auth } }) => { const { tableName, dbName } = params; - const { perPage = "50", page = "0", sortField, sortDesc } = query; + const { + perPage = "50", + page = "0", + sortField, + sortDesc, + whereQuery, + } = query; const credentials = await jwt.verify(auth.value); if (!credentials) { @@ -111,6 +117,7 @@ const app = new Elysia({ prefix: "/api" }) page: Number.parseInt(page, 10), sortField, sortDesc: sortDesc === "true", + whereQuery, }); }, ) diff --git a/frontend/src/components/db-table-view/data-table.tsx b/frontend/src/components/db-table-view/data-table.tsx index e0d7ae7..cb7b91c 100644 --- a/frontend/src/components/db-table-view/data-table.tsx +++ b/frontend/src/components/db-table-view/data-table.tsx @@ -5,12 +5,14 @@ import { DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuTrigger, + FormInput, Table, TableBody, TableCell, TableHead, TableHeader, TableRow, + Tooltip, } from "@/components/ui"; import { cn, isImageUrl, isUrl } from "@/lib/utils"; import { useTableColumnsQuery, useTableDataQuery } from "@/services/db"; @@ -25,8 +27,9 @@ import { getCoreRowModel, useReactTable, } from "@tanstack/react-table"; -import { ArrowUp, Rows3 } from "lucide-react"; +import { ArrowUp, Info, Rows3, Search } from "lucide-react"; import { useMemo, useState } from "react"; +import { useForm } from "react-hook-form"; export const DataTable = ({ tableName, @@ -43,14 +46,16 @@ export const DataTable = ({ onPageIndexChange: (pageIndex: number) => void; onPageSizeChange: (pageSize: number) => void; }) => { + const whereQueryForm = useForm<{ whereQuery: string }>(); const formatDates = useSettingsStore.use.formatDates(); const showImagesPreview = useSettingsStore.use.showImagesPreview(); - + const [whereQuery, setWhereQuery] = useState(""); const [sorting, setSorting] = useState([]); const [columnVisibility, setColumnVisibility] = useState({}); const { data: details } = useTableColumnsQuery({ dbName, tableName }); - const { data } = useTableDataQuery({ + const { data, refetch } = useTableDataQuery({ + whereQuery, tableName, dbName, perPage: pageSize, @@ -58,7 +63,6 @@ export const DataTable = ({ sortDesc: sorting[0]?.desc, sortField: sorting[0]?.id, }); - const paginationUpdater: OnChangeFn = (args) => { if (typeof args === "function") { const newArgs = args({ @@ -87,7 +91,7 @@ export const DataTable = ({ return (
@@ -170,6 +174,37 @@ export const DataTable = ({

{tableName} +
{ + if (data.whereQuery === whereQuery) { + void refetch(); + return; + } + setWhereQuery(data.whereQuery); + })} + > + + + + Your input will be prefixed with WHERE and appended to the raw + SQL query. +
You can use AND, OR, NOT, BETWEEN, LIKE, =, etc.
+
To remove the WHERE clause, submit an empty input.
+

+ } + > + + + @@ -227,6 +262,7 @@ export const DataTable = ({