mirror of
https://github.com/ershisan99/db-studio.git
synced 2025-12-16 12:33:05 +00:00
feat: mysql driver working, tests passing.
This commit is contained in:
@@ -17,16 +17,49 @@ done
|
|||||||
|
|
||||||
# Set up PostgreSQL test data
|
# Set up PostgreSQL test data
|
||||||
docker exec -i db-postgres-test psql -U postgres <<EOF
|
docker exec -i db-postgres-test psql -U postgres <<EOF
|
||||||
CREATE TABLE IF NOT EXISTS test_table (id SERIAL PRIMARY KEY, name VARCHAR(50));
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
INSERT INTO test_table (name) VALUES ('John Doe');
|
user_id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(50) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS orders (
|
||||||
|
order_id SERIAL PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
order_date DATE NOT NULL,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users (user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_orders_user_id ON orders(user_id);
|
||||||
|
|
||||||
|
INSERT INTO users (name) VALUES ('John Doe');
|
||||||
|
INSERT INTO users (name) VALUES ('Alice Smith');
|
||||||
|
INSERT INTO orders (user_id, order_date) VALUES (1, '2023-01-01');
|
||||||
|
INSERT INTO orders (user_id, order_date) VALUES (2, '2023-02-01');
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Set up MySQL test data
|
# Set up MySQL test data
|
||||||
docker exec -i db-mysql-test mysql -uroot -pmysecretpassword<<EOF
|
docker exec -i db-mysql-test mysql -uroot -pmysecretpassword<<EOF
|
||||||
CREATE DATABASE IF NOT EXISTS test_db;
|
CREATE DATABASE IF NOT EXISTS test_db;
|
||||||
USE test_db;
|
USE test_db;
|
||||||
CREATE TABLE IF NOT EXISTS test_table (id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(50));
|
|
||||||
INSERT INTO test_table (name) VALUES ('Jane Doe');
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
user_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
name VARCHAR(50) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS orders (
|
||||||
|
order_id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id INT NOT NULL,
|
||||||
|
order_date DATE NOT NULL,
|
||||||
|
FOREIGN KEY (user_id) REFERENCES users (user_id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX idx_orders_user_id ON orders(user_id);
|
||||||
|
|
||||||
|
INSERT INTO users (name) VALUES ('Jane Doe');
|
||||||
|
INSERT INTO users (name) VALUES ('Bob Brown');
|
||||||
|
INSERT INTO orders (user_id, order_date) VALUES (1, '2023-03-01');
|
||||||
|
INSERT INTO orders (user_id, order_date) VALUES (2, '2023-04-01');
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
echo "Test databases are ready"
|
echo "Test databases are ready"
|
||||||
|
|||||||
@@ -2,12 +2,14 @@
|
|||||||
|
|
||||||
# Clean up PostgreSQL test data
|
# Clean up PostgreSQL test data
|
||||||
docker exec -i db-postgres-test psql -U postgres <<EOF
|
docker exec -i db-postgres-test psql -U postgres <<EOF
|
||||||
DROP TABLE IF EXISTS test_table;
|
DROP TABLE IF EXISTS users;
|
||||||
|
DROP TABLE IF EXISTS orders;
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Clean up MySQL test data
|
# Clean up MySQL test data
|
||||||
docker exec -i db-mysql-test mysql -uroot -pmysecretpassword --database=test_db<<EOF
|
docker exec -i db-mysql-test mysql -uroot -pmysecretpassword --database=test_db<<EOF
|
||||||
DROP TABLE IF EXISTS test_table;
|
DROP TABLE IF EXISTS users;
|
||||||
|
DROP TABLE IF EXISTS orders;
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
# Stop and remove Docker containers and volumes
|
# Stop and remove Docker containers and volumes
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import mysql, { type ResultSetHeader } from "mysql2/promise";
|
import mysql from "mysql2/promise";
|
||||||
import type {
|
import type {
|
||||||
Credentials,
|
Credentials,
|
||||||
Driver,
|
Driver,
|
||||||
@@ -6,35 +6,17 @@ import type {
|
|||||||
WithSortPagination,
|
WithSortPagination,
|
||||||
} from "./driver.interface";
|
} from "./driver.interface";
|
||||||
|
|
||||||
const isResultSetHeader = (data: unknown): data is ResultSetHeader => {
|
|
||||||
if (!data || typeof data !== "object") return false;
|
|
||||||
|
|
||||||
const keys = [
|
|
||||||
"fieldCount",
|
|
||||||
"affectedRows",
|
|
||||||
"insertId",
|
|
||||||
"info",
|
|
||||||
"serverStatus",
|
|
||||||
"warningStatus",
|
|
||||||
"changedRows",
|
|
||||||
];
|
|
||||||
|
|
||||||
return keys.every((key) => key in data);
|
|
||||||
};
|
|
||||||
|
|
||||||
export class MySQLDriver implements Driver {
|
export class MySQLDriver implements Driver {
|
||||||
parseCredentials({
|
parseCredentials({
|
||||||
username,
|
username,
|
||||||
password,
|
password,
|
||||||
host,
|
host,
|
||||||
type,
|
|
||||||
port,
|
port,
|
||||||
database,
|
database,
|
||||||
}: {
|
}: {
|
||||||
username: string;
|
username: string;
|
||||||
password: string;
|
password: string;
|
||||||
host: string;
|
host: string;
|
||||||
type: string;
|
|
||||||
port: string;
|
port: string;
|
||||||
database: string;
|
database: string;
|
||||||
ssl: string;
|
ssl: string;
|
||||||
@@ -43,7 +25,6 @@ export class MySQLDriver implements Driver {
|
|||||||
user: username,
|
user: username,
|
||||||
password,
|
password,
|
||||||
host,
|
host,
|
||||||
type,
|
|
||||||
port: Number.parseInt(port, 10),
|
port: Number.parseInt(port, 10),
|
||||||
database,
|
database,
|
||||||
ssl: {
|
ssl: {
|
||||||
@@ -58,9 +39,9 @@ export class MySQLDriver implements Driver {
|
|||||||
if ("connectionString" in credentials) {
|
if ("connectionString" in credentials) {
|
||||||
connection = await mysql.createConnection(credentials.connectionString);
|
connection = await mysql.createConnection(credentials.connectionString);
|
||||||
} else {
|
} else {
|
||||||
connection = await mysql.createConnection(
|
const creds = this.parseCredentials(credentials);
|
||||||
this.parseCredentials(credentials),
|
console.log(creds);
|
||||||
);
|
connection = await mysql.createConnection(creds);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@@ -69,22 +50,6 @@ export class MySQLDriver implements Driver {
|
|||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
}
|
}
|
||||||
private getActions(matchString: string) {
|
|
||||||
const onActions = "RESTRICT|NO ACTION|CASCADE|SET NULL|SET DEFAULT";
|
|
||||||
const onDeleteRegex = new RegExp(`ON DELETE (${onActions})`);
|
|
||||||
const onUpdateRegex = new RegExp(`ON UPDATE (${onActions})`);
|
|
||||||
|
|
||||||
const onDeleteMatch = matchString.match(onDeleteRegex);
|
|
||||||
const onUpdateMatch = matchString.match(onUpdateRegex);
|
|
||||||
|
|
||||||
const onDeleteAction = onDeleteMatch ? onDeleteMatch[1] : "NO ACTION";
|
|
||||||
const onUpdateAction = onUpdateMatch ? onUpdateMatch[1] : "NO ACTION";
|
|
||||||
|
|
||||||
return {
|
|
||||||
onDelete: onDeleteAction,
|
|
||||||
onUpdate: onUpdateAction,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async getAllDatabases(credentials: Credentials) {
|
async getAllDatabases(credentials: Credentials) {
|
||||||
console.log("Get all databases");
|
console.log("Get all databases");
|
||||||
@@ -102,11 +67,15 @@ export class MySQLDriver implements Driver {
|
|||||||
|
|
||||||
async getAllTables(
|
async getAllTables(
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
{ sortDesc, sortField, dbName }: WithSort<{ dbName: string }>,
|
{
|
||||||
|
sortDesc,
|
||||||
|
sortField = "schema_name",
|
||||||
|
dbName,
|
||||||
|
}: WithSort<{ dbName: string }>,
|
||||||
) {
|
) {
|
||||||
const connection = await this.queryRunner(credentials);
|
const connection = await this.queryRunner(credentials);
|
||||||
|
|
||||||
const tablesQuery = `
|
let tablesQuery = `
|
||||||
SELECT
|
SELECT
|
||||||
TABLE_NAME as table_name,
|
TABLE_NAME as table_name,
|
||||||
TABLE_SCHEMA as schema_name,
|
TABLE_SCHEMA as schema_name,
|
||||||
@@ -118,8 +87,11 @@ export class MySQLDriver implements Driver {
|
|||||||
FROM
|
FROM
|
||||||
information_schema.tables
|
information_schema.tables
|
||||||
WHERE
|
WHERE
|
||||||
table_schema = ?;
|
table_schema = ?
|
||||||
`;
|
`;
|
||||||
|
if (sortField) {
|
||||||
|
tablesQuery += ` ORDER BY ${sortField} ${sortDesc ? "DESC" : "ASC"}`;
|
||||||
|
}
|
||||||
|
|
||||||
const [tables] = await connection.execute(tablesQuery, [dbName]);
|
const [tables] = await connection.execute(tablesQuery, [dbName]);
|
||||||
|
|
||||||
@@ -301,74 +273,71 @@ export class MySQLDriver implements Driver {
|
|||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
{ dbName, tableName }: { dbName: string; tableName: string },
|
{ dbName, tableName }: { dbName: string; tableName: string },
|
||||||
) {
|
) {
|
||||||
const sql = await this.queryRunner(credentials);
|
const connection = await this.queryRunner(credentials);
|
||||||
|
|
||||||
const result = await sql`
|
try {
|
||||||
SELECT
|
const [rows] = await connection.execute(
|
||||||
conname,
|
`
|
||||||
condeferrable::int AS deferrable,
|
SELECT
|
||||||
pg_get_constraintdef(oid) AS definition
|
rc.CONSTRAINT_NAME as conname,
|
||||||
FROM
|
rc.UPDATE_RULE as on_update,
|
||||||
pg_constraint
|
rc.DELETE_RULE as on_delete,
|
||||||
WHERE
|
kcu.COLUMN_NAME as source,
|
||||||
conrelid = (
|
kcu.REFERENCED_TABLE_NAME as \`table\`,
|
||||||
SELECT pc.oid
|
kcu.REFERENCED_COLUMN_NAME as target
|
||||||
FROM pg_class AS pc
|
FROM
|
||||||
INNER JOIN pg_namespace AS pn ON (pn.oid = pc.relnamespace)
|
information_schema.REFERENTIAL_CONSTRAINTS rc
|
||||||
WHERE pc.relname = ${tableName}
|
JOIN
|
||||||
AND pn.nspname = ${dbName}
|
information_schema.KEY_COLUMN_USAGE kcu
|
||||||
)
|
ON
|
||||||
AND contype = 'f'::char
|
rc.CONSTRAINT_SCHEMA = kcu.CONSTRAINT_SCHEMA
|
||||||
ORDER BY conkey, conname
|
AND rc.CONSTRAINT_NAME = kcu.CONSTRAINT_NAME
|
||||||
`;
|
WHERE
|
||||||
|
kcu.TABLE_SCHEMA = ?
|
||||||
void sql.end();
|
AND kcu.TABLE_NAME = ?
|
||||||
|
`,
|
||||||
return result.map((row) => {
|
[dbName, tableName],
|
||||||
const match = row.definition.match(
|
|
||||||
/FOREIGN KEY\s*\((.+)\)\s*REFERENCES (.+)\((.+)\)(.*)$/iy,
|
|
||||||
);
|
);
|
||||||
if (match) {
|
|
||||||
const sourceColumns = match[1]
|
await connection.end();
|
||||||
.split(",")
|
|
||||||
.map((col) => col.replaceAll('"', "").trim());
|
const foreignKeys: { [key: string]: ForeignKeyInfo } = {};
|
||||||
const targetTableMatch = match[2].match(
|
(rows as any[]).forEach((row) => {
|
||||||
/^(("([^"]|"")+"|[^"]+)\.)?"?("([^"]|"")+"|[^"]+)$/,
|
if (!foreignKeys[row.conname]) {
|
||||||
);
|
foreignKeys[row.conname] = {
|
||||||
const targetTable = targetTableMatch
|
conname: row.conname,
|
||||||
? targetTableMatch[0].trim()
|
deferrable: false,
|
||||||
: null;
|
definition: `FOREIGN KEY (${row.source}) REFERENCES ${row.table}(${row.target})`,
|
||||||
const targetColumns = match[3]
|
source: [],
|
||||||
.split(",")
|
ns: row.table,
|
||||||
.map((col) => col.replaceAll('"', "").trim());
|
table: row.table,
|
||||||
const { onDelete, onUpdate } = this.getActions(match[4]);
|
target: [],
|
||||||
return {
|
on_delete: row.on_delete,
|
||||||
conname: row.conname,
|
on_update: row.on_update,
|
||||||
deferrable: Boolean(row.deferrable),
|
};
|
||||||
definition: row.definition,
|
}
|
||||||
source: sourceColumns,
|
foreignKeys[row.conname].source.push(row.source);
|
||||||
ns: targetTableMatch
|
foreignKeys[row.conname].target.push(row.target);
|
||||||
? targetTableMatch[0].replaceAll('"', "").trim()
|
});
|
||||||
: null,
|
|
||||||
table: targetTable.replaceAll('"', ""),
|
return Object.values(foreignKeys);
|
||||||
target: targetColumns,
|
} catch (error) {
|
||||||
on_delete: onDelete ?? "NO ACTION",
|
console.error("Error fetching foreign keys:", error);
|
||||||
on_update: onUpdate ?? "NO ACTION",
|
await connection.end();
|
||||||
};
|
throw error;
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async executeQuery(credentials: Credentials, query: string) {
|
async executeQuery(credentials: Credentials, query: string) {
|
||||||
const sql = await this.queryRunner(credentials);
|
const connection = await this.queryRunner(credentials);
|
||||||
|
|
||||||
const result = await sql.unsafe(query);
|
const [rows, fields] = await connection.execute(query);
|
||||||
|
|
||||||
void sql.end();
|
await connection.end();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
count: result.length,
|
count: rows?.length,
|
||||||
data: result,
|
data: rows,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,22 +6,19 @@ import type { AppType } from "../index";
|
|||||||
|
|
||||||
const fetch = edenFetch<AppType>("http://localhost:3000");
|
const fetch = edenFetch<AppType>("http://localhost:3000");
|
||||||
|
|
||||||
|
const testDBName = "users";
|
||||||
|
|
||||||
const pgCookie = cookie.serialize(
|
const pgCookie = cookie.serialize(
|
||||||
"auth",
|
"auth",
|
||||||
jwt.sign(
|
jwt.sign(
|
||||||
// {
|
|
||||||
// type: "postgres",
|
|
||||||
// username: "postgres",
|
|
||||||
// password: "mysecretpassword",
|
|
||||||
// host: "localhost",
|
|
||||||
// port: "5432",
|
|
||||||
// database: "postgres",
|
|
||||||
// ssl: "prefer",
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
type: "postgres",
|
type: "postgres",
|
||||||
connectionString:
|
username: "postgres",
|
||||||
"postgresql://flashcards_owner:pBYW18waUHtV@ep-gentle-heart-a225yqws.eu-central-1.aws.neon.tech/flashcards?sslmode=require&schema=flashcards",
|
password: "mysecretpassword",
|
||||||
|
host: "localhost",
|
||||||
|
port: "5432",
|
||||||
|
database: "postgres",
|
||||||
|
ssl: "prefer",
|
||||||
},
|
},
|
||||||
"Fischl von Luftschloss Narfidort",
|
"Fischl von Luftschloss Narfidort",
|
||||||
{ noTimestamp: true },
|
{ noTimestamp: true },
|
||||||
@@ -213,7 +210,7 @@ describe("databases/:dbName/tables/:tableName/data", () => {
|
|||||||
const res = await fetch("/api/databases/:dbName/tables/:tableName/data", {
|
const res = await fetch("/api/databases/:dbName/tables/:tableName/data", {
|
||||||
params: {
|
params: {
|
||||||
dbName: "public",
|
dbName: "public",
|
||||||
tableName: "test_table",
|
tableName: testDBName,
|
||||||
},
|
},
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -240,7 +237,7 @@ describe("databases/:dbName/tables/:tableName/data", () => {
|
|||||||
const res = await fetch("/api/databases/:dbName/tables/:tableName/data", {
|
const res = await fetch("/api/databases/:dbName/tables/:tableName/data", {
|
||||||
params: {
|
params: {
|
||||||
dbName: "test_db",
|
dbName: "test_db",
|
||||||
tableName: "test_table",
|
tableName: testDBName,
|
||||||
},
|
},
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -271,7 +268,7 @@ describe("databases/:dbName/tables/:tableName/indexes", () => {
|
|||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
dbName: "public",
|
dbName: "public",
|
||||||
tableName: "test_table",
|
tableName: testDBName,
|
||||||
},
|
},
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -302,7 +299,7 @@ describe("databases/:dbName/tables/:tableName/indexes", () => {
|
|||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
dbName: "test_db",
|
dbName: "test_db",
|
||||||
tableName: "test_table",
|
tableName: testDBName,
|
||||||
},
|
},
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -335,7 +332,7 @@ describe("databases/:dbName/tables/:tableName/columns", () => {
|
|||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
dbName: "public",
|
dbName: "public",
|
||||||
tableName: "test_table",
|
tableName: testDBName,
|
||||||
},
|
},
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -375,7 +372,7 @@ describe("databases/:dbName/tables/:tableName/columns", () => {
|
|||||||
{
|
{
|
||||||
params: {
|
params: {
|
||||||
dbName: "test_db",
|
dbName: "test_db",
|
||||||
tableName: "test_table",
|
tableName: testDBName,
|
||||||
},
|
},
|
||||||
method: "GET",
|
method: "GET",
|
||||||
headers: {
|
headers: {
|
||||||
@@ -407,81 +404,149 @@ describe("databases/:dbName/tables/:tableName/columns", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("databases/:dbName/tables/:tableName/foreign-keys", () => {
|
describe("databases/:dbName/tables/:tableName/foreign-keys", () => {
|
||||||
// it("should return correct data from PostgreSQL", async () => {
|
it("should return correct data from PostgreSQL", async () => {
|
||||||
// const res = await fetch(
|
const res = await fetch(
|
||||||
// "/api/databases/:dbName/tables/:tableName/foreign-keys",
|
"/api/databases/:dbName/tables/:tableName/foreign-keys",
|
||||||
// {
|
{
|
||||||
// params: {
|
params: {
|
||||||
// dbName: "public",
|
dbName: "public",
|
||||||
// tableName: "test_table",
|
tableName: "orders",
|
||||||
// },
|
},
|
||||||
// method: "GET",
|
method: "GET",
|
||||||
// headers: {
|
headers: {
|
||||||
// cookie: pgCookie,
|
cookie: pgCookie,
|
||||||
// },
|
},
|
||||||
// },
|
},
|
||||||
// );
|
);
|
||||||
// expect(res.status).toEqual(200);
|
expect(res.status).toEqual(200);
|
||||||
//
|
|
||||||
// expect(res.data).not.toEqual("Unauthorized");
|
expect(res.data).not.toEqual("Unauthorized");
|
||||||
// if (res.data === "Unauthorized") return;
|
if (res.data === "Unauthorized") return;
|
||||||
//
|
|
||||||
// console.log(res.data);
|
expect(res.data).toBeDefined();
|
||||||
//
|
if (!res.data) return;
|
||||||
// expect(res.data).toBeDefined();
|
|
||||||
// if (!res.data) return;
|
expect(res.data).toBeArray();
|
||||||
//
|
|
||||||
// expect(res.data).toBeArray();
|
for (const row of res.data) {
|
||||||
//
|
expect(row).toContainAllKeys([
|
||||||
// for (const row of res.data) {
|
"conname",
|
||||||
// expect(row).toContainAllKeys([
|
"deferrable",
|
||||||
// "column_name",
|
"definition",
|
||||||
// "data_type",
|
"source",
|
||||||
// "udt_name",
|
"ns",
|
||||||
// "column_comment",
|
"on_delete",
|
||||||
// ]);
|
"on_update",
|
||||||
// expect(row.column_name).toBeString();
|
"table",
|
||||||
// expect(row.data_type).toBeString();
|
"target",
|
||||||
// expect(row.udt_name).toBeString();
|
]);
|
||||||
// expect(
|
expect(row.conname).toBeString();
|
||||||
// row.column_comment === null || typeof row.column_comment === "string",
|
expect(row.deferrable).toBeBoolean();
|
||||||
// ).toBeTrue();
|
expect(row.definition).toBeString();
|
||||||
// }
|
expect(row.source).toBeArray();
|
||||||
// });
|
expect(row.ns).toBeString();
|
||||||
// it("should return correct data from MySQL", async () => {
|
expect(row.on_delete).toBeString();
|
||||||
// const res = await fetch(
|
expect(row.on_update).toBeString();
|
||||||
// "/api/databases/:dbName/tables/:tableName/columns",
|
expect(row.table).toBeString();
|
||||||
// {
|
expect(row.target).toBeArray();
|
||||||
// params: {
|
}
|
||||||
// dbName: "test_db",
|
});
|
||||||
// tableName: "test_table",
|
it("should return correct data from MySQL", async () => {
|
||||||
// },
|
const res = await fetch(
|
||||||
// method: "GET",
|
"/api/databases/:dbName/tables/:tableName/foreign-keys",
|
||||||
// headers: {
|
{
|
||||||
// cookie: mysqlCookie,
|
params: {
|
||||||
// },
|
dbName: "test_db",
|
||||||
// },
|
tableName: "orders",
|
||||||
// );
|
},
|
||||||
// console.log(res.data);
|
method: "GET",
|
||||||
//
|
headers: {
|
||||||
// expect(res.data).toBeDefined();
|
cookie: mysqlCookie,
|
||||||
// if (!res.data) return;
|
},
|
||||||
//
|
},
|
||||||
// expect(res.data).toBeArray();
|
);
|
||||||
//
|
|
||||||
// for (const row of res.data) {
|
expect(res.data).toBeDefined();
|
||||||
// expect(row).toContainAllKeys([
|
if (!res.data) return;
|
||||||
// "column_name",
|
|
||||||
// "data_type",
|
expect(res.data).toBeArray();
|
||||||
// "udt_name",
|
|
||||||
// "column_comment",
|
for (const row of res.data) {
|
||||||
// ]);
|
expect(row).toContainAllKeys([
|
||||||
// expect(row.column_name).toBeString();
|
"conname",
|
||||||
// expect(row.data_type).toBeString();
|
"deferrable",
|
||||||
// expect(row.udt_name).toBeString();
|
"definition",
|
||||||
// expect(
|
"source",
|
||||||
// row.column_comment === null || typeof row.column_comment === "string",
|
"ns",
|
||||||
// ).toBeTrue();
|
"on_delete",
|
||||||
// }
|
"on_update",
|
||||||
// });
|
"table",
|
||||||
|
"target",
|
||||||
|
]);
|
||||||
|
expect(row.conname).toBeString();
|
||||||
|
expect(row.deferrable).toBeBoolean();
|
||||||
|
expect(row.definition).toBeString();
|
||||||
|
expect(row.source).toBeArray();
|
||||||
|
expect(row.ns).toBeString();
|
||||||
|
expect(row.on_delete).toBeString();
|
||||||
|
expect(row.on_update).toBeString();
|
||||||
|
expect(row.table).toBeString();
|
||||||
|
expect(row.target).toBeArray();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("raw", () => {
|
||||||
|
it("should return correct data from PostgreSQL", async () => {
|
||||||
|
const query = "SELECT * FROM information_schema.tables;";
|
||||||
|
const res = await fetch("/api/raw", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
cookie: pgCookie,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(res.status).toEqual(200);
|
||||||
|
|
||||||
|
expect(res.data).not.toEqual("Unauthorized");
|
||||||
|
if (res.data === "Unauthorized") return;
|
||||||
|
|
||||||
|
expect(res.data).toBeDefined();
|
||||||
|
if (!res.data) return;
|
||||||
|
|
||||||
|
expect(res.data.count).toBeNumber();
|
||||||
|
expect(res.data.count).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
expect(res.data.data).toBeArray();
|
||||||
|
expect(res.data.data.length).toBeGreaterThan(0);
|
||||||
|
expect(res.data.data[0]).toBeObject();
|
||||||
|
});
|
||||||
|
it("should return correct data from MySQL", async () => {
|
||||||
|
const query = "SELECT * FROM information_schema.tables;";
|
||||||
|
const res = await fetch("/api/raw", {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
cookie: mysqlCookie,
|
||||||
|
},
|
||||||
|
body: {
|
||||||
|
query,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(res.status).toEqual(200);
|
||||||
|
|
||||||
|
expect(res.data).not.toEqual("Unauthorized");
|
||||||
|
if (res.data === "Unauthorized") return;
|
||||||
|
|
||||||
|
expect(res.data).toBeDefined();
|
||||||
|
if (!res.data) return;
|
||||||
|
|
||||||
|
expect(res.data.count).toBeNumber();
|
||||||
|
expect(res.data.count).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
expect(res.data.data).toBeArray();
|
||||||
|
expect(res.data.data.length).toBeGreaterThan(0);
|
||||||
|
expect(res.data.data[0]).toBeObject();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ export const DataTable = ({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
sortable: true,
|
enableSorting: true,
|
||||||
cell: ({ row }) => {
|
cell: ({ row }) => {
|
||||||
const value = row.getValue(column_name) as any;
|
const value = row.getValue(column_name) as any;
|
||||||
let finalValue = value;
|
let finalValue = value;
|
||||||
|
|||||||
@@ -23,7 +23,13 @@ declare module "@tanstack/react-router" {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const queryClient = new QueryClient();
|
const queryClient = new QueryClient({
|
||||||
|
defaultOptions: {
|
||||||
|
queries: {
|
||||||
|
retry: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Render the app
|
// Render the app
|
||||||
const rootElement = document.getElementById("root");
|
const rootElement = document.getElementById("root");
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ function TableView() {
|
|||||||
return (
|
return (
|
||||||
<div className="p-3 h-layout w-layout">
|
<div className="p-3 h-layout w-layout">
|
||||||
<DataTable
|
<DataTable
|
||||||
|
key={tableName}
|
||||||
dbName={dbName}
|
dbName={dbName}
|
||||||
tableName={tableName}
|
tableName={tableName}
|
||||||
pageSize={pageSize}
|
pageSize={pageSize}
|
||||||
|
|||||||
@@ -80,10 +80,12 @@ const createColumns = (dbName: string) => {
|
|||||||
|
|
||||||
columnHelper.accessor("primary_key", {
|
columnHelper.accessor("primary_key", {
|
||||||
header: "Primary key",
|
header: "Primary key",
|
||||||
|
enableSorting: false,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
columnHelper.accessor("indexes", {
|
columnHelper.accessor("indexes", {
|
||||||
header: "Indexes",
|
header: "Indexes",
|
||||||
|
enableSorting: false,
|
||||||
cell: (props) => {
|
cell: (props) => {
|
||||||
const indexes = props.getValue();
|
const indexes = props.getValue();
|
||||||
if (!indexes) return null;
|
if (!indexes) return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user