diff --git a/src-tauri/src/database/connections.rs b/src-tauri/src/database/connections.rs index 8dbdc39..d17b50e 100644 --- a/src-tauri/src/database/connections.rs +++ b/src-tauri/src/database/connections.rs @@ -26,6 +26,23 @@ pub enum Scheme { Sqlite(FileConnectionMode), } +#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] +pub enum Dialect { + Mysql, + Postgres, + Sqlite, +} + +impl Dialect { + fn as_str(&self) -> &'static str { + match self { + Dialect::Mysql => "mysql", + Dialect::Postgres => "postgres", + Dialect::Sqlite => "sqlite", + } + } +} + #[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] pub struct SocketCredentials { pub username: String, @@ -47,6 +64,7 @@ pub struct HostCredentials { pub struct ConnectionConfig { pub id: Uuid, pub scheme: Scheme, + pub dialect: Dialect, pub name: String, pub color: String, } @@ -90,6 +108,11 @@ impl ConnectionConfig { let id = Uuid::new_v4(); Ok(ConnectionConfig { id, + dialect: match scheme { + Scheme::Mysql(_) => Dialect::Mysql, + Scheme::Postgres(_) => Dialect::Postgres, + Scheme::Sqlite(_) => Dialect::Sqlite, + }, scheme, name: name.to_string(), color: color.to_string(), @@ -188,7 +211,9 @@ impl ConnectedConnection { pub async fn get_constraints(&self) -> Result { match &self.pool { - ConnectionPool::Mysql(pool) => engine::mysql::tables::get_constraints(self, pool, None).await, + ConnectionPool::Mysql(pool) => { + engine::mysql::tables::get_constraints(self, pool, None).await + } // ConnectionPool::Postgres(_pool) => todo!(), // ConnectionPool::Sqlite(_pool) => todo!(), } @@ -212,7 +237,9 @@ impl ConnectedConnection { pub async fn get_triggers(&self) -> Result { match &self.pool { - ConnectionPool::Mysql(pool) => engine::mysql::tables::get_triggers(self, pool, None).await, + ConnectionPool::Mysql(pool) => { + engine::mysql::tables::get_triggers(self, pool, None).await + } // ConnectionPool::Postgres(_pool) => todo!(), // ConnectionPool::Sqlite(_pool) => todo!(), } @@ -232,11 +259,13 @@ pub fn add_connection(db: &Connection, conn: &ConnectionConfig) -> Result<()> { "INSERT INTO connections ( id, scheme, + dialect, name, color ) VALUES ( :id, :scheme, + :dialect, :name, :color )", @@ -246,6 +275,7 @@ pub fn add_connection(db: &Connection, conn: &ConnectionConfig) -> Result<()> { ":id": conn.id, ":name": conn.name, ":scheme": scheme, + ":dialect": conn.dialect.as_str(), ":color": conn.color, })?; @@ -270,6 +300,11 @@ pub fn get_all_connections(db: &Connection) -> Result> { id: row.get("id")?, name: row.get("name")?, color: row.get("color")?, + dialect: match scheme { + Scheme::Mysql(_) => Dialect::Mysql, + Scheme::Postgres(_) => Dialect::Postgres, + Scheme::Sqlite(_) => Dialect::Sqlite, + }, scheme, }); } diff --git a/src-tauri/src/database/database.rs b/src-tauri/src/database/database.rs index 46936a7..a07ac06 100644 --- a/src-tauri/src/database/database.rs +++ b/src-tauri/src/database/database.rs @@ -48,6 +48,7 @@ pub fn create_app_db(app_path: &str) -> Result<()> { "create table `connections` ( `id`TEXT not null, `scheme` TEXT not null, + `dialect` varchar(255) not null, `name` varchar(255) not null, `color` varchar(255) not null )", diff --git a/src/components/Screens/Console/Content/Content.tsx b/src/components/Screens/Console/Content/Content.tsx index 7986025..23c5dc0 100644 --- a/src/components/Screens/Console/Content/Content.tsx +++ b/src/components/Screens/Console/Content/Content.tsx @@ -13,7 +13,6 @@ export const Content = () => { setContentIdx, addContentTab, removeContentTab, - getContent, }, } = useAppSelector(); @@ -23,11 +22,11 @@ export const Content = () => { {(_tab, idx) => (
@@ -44,21 +43,27 @@ export const Content = () => {
)}
-
+
- - - - - - - - + + {(tab, idx) => ( + + + + + + + + + + + )} +
); diff --git a/src/components/Screens/Console/Content/TableStructure/TableStructureTab.tsx b/src/components/Screens/Console/Content/TableStructure/TableStructureTab.tsx index 561e46b..ff5609b 100644 --- a/src/components/Screens/Console/Content/TableStructure/TableStructureTab.tsx +++ b/src/components/Screens/Console/Content/TableStructure/TableStructureTab.tsx @@ -1,28 +1,23 @@ import { createSignal, For, Match, Switch } from "solid-js"; -import { TableStrucureEntity, TableStrucureEntities } from "interfaces"; +import { + TableEntity, + TableStrucureEntities, + TableStrucureEntityType, +} from "interfaces"; import { t } from "utils/i18n"; import { StructureTable } from "./Tabs/StructureTable"; import { useAppSelector } from "services/Context"; -import { TableStructureContentTabData } from "services/ConnectionTabs"; export const TableStructureTab = () => { const { - connectionsService: { getContentData }, + connectionsService: { getContentData, getConnection }, } = useAppSelector(); - const [tab, setTab] = createSignal< - keyof Omit - >(TableStrucureEntity.Columns); - // const [data, setData] = createStore< - // Omit - // >({ - // columns: [], - // indices: [], - // triggers: [], - // constraints: [], - // }); + const [tab, setTab] = createSignal( + TableEntity.columns + ); return ( -
+
@@ -45,6 +40,8 @@ export const TableStructureTab = () => { )} diff --git a/src/components/Screens/Console/Content/TableStructure/Tabs/StructureTable.tsx b/src/components/Screens/Console/Content/TableStructure/Tabs/StructureTable.tsx index 954a868..330dbe6 100644 --- a/src/components/Screens/Console/Content/TableStructure/Tabs/StructureTable.tsx +++ b/src/components/Screens/Console/Content/TableStructure/Tabs/StructureTable.tsx @@ -1,8 +1,15 @@ -import { Row } from "interfaces"; +import { DialectType, Row, TableStrucureEntityType } from "interfaces"; +import { sortTableStructure } from "utils/utils"; -export const StructureTable = (props: { data: Row[] }) => { - const columns = props.data.length ? Object.keys(props.data[0]) : []; - const rows = props.data.map((row) => Object.values(row)); +export const StructureTable = (props: { + data: Row[]; + dialect: DialectType; + type: TableStrucureEntityType; +}) => { + const columns = props.data.length + ? sortTableStructure(Object.keys(props.data[0]), props.dialect, props.type) + : []; + const rows = props.data.map((row) => columns.map((column) => row[column])); return (
@@ -19,7 +26,7 @@ export const StructureTable = (props: { data: Row[] }) => { {row.map((cell) => ( - + ))} ))} diff --git a/src/components/Screens/Console/Sidebar/Sidebar.tsx b/src/components/Screens/Console/Sidebar/Sidebar.tsx index f7437d3..7368200 100644 --- a/src/components/Screens/Console/Sidebar/Sidebar.tsx +++ b/src/components/Screens/Console/Sidebar/Sidebar.tsx @@ -77,11 +77,11 @@ export const Sidebar = () => { {(column) => (
- + {column.name} - + {column.props.COLUMN_TYPE}
diff --git a/src/components/Screens/Console/Sidebar/TableColumnsCollapse.tsx b/src/components/Screens/Console/Sidebar/TableColumnsCollapse.tsx index 01c3e6d..a45d916 100644 --- a/src/components/Screens/Console/Sidebar/TableColumnsCollapse.tsx +++ b/src/components/Screens/Console/Sidebar/TableColumnsCollapse.tsx @@ -40,7 +40,7 @@ export const TableColumnsCollapse = (props: { }; return ( -
show(e)}> +
show(e)}> addTableStructureTab(props.table)}> {t("components.sidebar.show_table_structure")} @@ -82,7 +82,7 @@ export const TableColumnsCollapse = (props: { /> - {props.title} + {props.title}
{open() && props.children}
diff --git a/src/components/Screens/Home/Connections/AddConnectionForm.tsx b/src/components/Screens/Home/Connections/AddConnectionForm.tsx index f999c00..9d5366d 100644 --- a/src/components/Screens/Home/Connections/AddConnectionForm.tsx +++ b/src/components/Screens/Home/Connections/AddConnectionForm.tsx @@ -13,13 +13,13 @@ import { } from "components/UI"; import { PORTS_MAP, - Schemes, + Dialect, AvailableConnectionModes, SocketPathDefaults, connectionColors, ConnectionMode, connectionModes, - schemes, + dialects, HostCredentials, SocketCredentials, FileCredentials, @@ -43,7 +43,7 @@ export const ConnectionFormSchema = z.object({ .string() .min(MIN_LENGTH_STR, messages.length) .max(MAX_LENGTH_STR, messages.length), - scheme: z.enum(schemes), + scheme: z.enum(dialects), mode: z.enum(connectionModes), username: z .string() @@ -114,15 +114,15 @@ export * from "./AddConnectionForm"; const defaultValues = { name: "My Connection", - scheme: Schemes.Mysql, + scheme: Dialect.Mysql, port: 3306, - color: "orange", - mode: AvailableConnectionModes[Schemes.Mysql][0], + color: "cyan", + mode: AvailableConnectionModes[Dialect.Mysql][0], file: "", host: "localhost", username: "root", - password: "noir", - dbname: "noir", + password: "example", + dbname: "world_x", }; const AddConnectionForm = (props: { @@ -178,14 +178,14 @@ const AddConnectionForm = (props: {
- + - +
- +
diff --git a/src/components/Screens/Home/Connections/ConnectionsList/ConnectionItem/ActionsMenu.tsx b/src/components/Screens/Home/Connections/ConnectionsList/ConnectionItem/ActionsMenu.tsx index 268083d..fdcb483 100644 --- a/src/components/Screens/Home/Connections/ConnectionsList/ConnectionItem/ActionsMenu.tsx +++ b/src/components/Screens/Home/Connections/ConnectionsList/ConnectionItem/ActionsMenu.tsx @@ -16,7 +16,7 @@ export const ActionsMenu = (props: { connection: ConnectionConfig }) => { const { result } = await invoke("get_columns", { connId: config.id, }); - const schema = columnsToSchema(result); + const schema = columnsToSchema(result, config.dialect); await addConnectionTab({ id: config.id, label: config.name, diff --git a/src/components/Screens/Home/Connections/ConnectionsList/ConnectionItem/ConnectionItem.tsx b/src/components/Screens/Home/Connections/ConnectionsList/ConnectionItem/ConnectionItem.tsx index 7044b73..8bf4248 100644 --- a/src/components/Screens/Home/Connections/ConnectionsList/ConnectionItem/ConnectionItem.tsx +++ b/src/components/Screens/Home/Connections/ConnectionsList/ConnectionItem/ConnectionItem.tsx @@ -1,10 +1,10 @@ -import { ConnectionConfig, ConnectionModeType, SchemeType } from "interfaces"; +import { ConnectionConfig, ConnectionModeType, DialectType } from "interfaces"; import { firstKey } from "utils/utils"; import { ColorCircle } from "components/UI"; import { ActionsMenu } from "./ActionsMenu"; export const ConnectionItem = (props: { connection: ConnectionConfig }) => { - const scheme = firstKey(props.connection.scheme) as SchemeType; + const scheme = firstKey(props.connection.scheme) as DialectType; const mode = firstKey(props.connection.scheme[scheme]!) as ConnectionModeType; const creds = props.connection.scheme[scheme]![mode]; const connectionString = diff --git a/src/components/Screens/Home/Home.tsx b/src/components/Screens/Home/Home.tsx index c2af8c9..358181a 100644 --- a/src/components/Screens/Home/Home.tsx +++ b/src/components/Screens/Home/Home.tsx @@ -1,21 +1,25 @@ -import { invoke } from '@tauri-apps/api'; -import { createStore } from 'solid-js/store'; -import { onMount } from 'solid-js'; -import { ConnectionsList } from 'components/Screens/Home/Connections/ConnectionsList/ConnectionsList'; -import { AddConnectionForm } from 'components/Screens/Home/Connections/AddConnectionForm'; -import { ConnectionConfig, Scheme } from 'interfaces'; +import { invoke } from "@tauri-apps/api"; +import { createStore } from "solid-js/store"; +import { onMount } from "solid-js"; +import { ConnectionsList } from "components/Screens/Home/Connections/ConnectionsList/ConnectionsList"; +import { AddConnectionForm } from "components/Screens/Home/Connections/AddConnectionForm"; +import { ConnectionConfig, Scheme } from "interfaces"; export const Home = () => { - const addConnection = async (conn: { name: string, scheme: Scheme, color: string }) => { - await invoke('add_connection', conn) - const res = await invoke('get_connections', {}) + const addConnection = async (conn: { + name: string; + scheme: Scheme; + color: string; + }) => { + await invoke("add_connection", conn); + const res = await invoke("get_connections", {}); setConnections(res as ConnectionConfig[]); - } + }; const [connections, setConnections] = createStore([]); onMount(async () => { - const res = await invoke('get_connections', {}) + const res = await invoke("get_connections", {}); setConnections(res as ConnectionConfig[]); }); @@ -24,6 +28,5 @@ export const Home = () => {
- ) -} - + ); +}; diff --git a/src/interfaces.ts b/src/interfaces.ts index 4d9a428..29773a7 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,21 +1,21 @@ -export const Schemes = { +export const Dialect = { Mysql: "Mysql", Postgres: "Postgres", Sqlite: "Sqlite", } as const; -export type SchemeType = keyof typeof Schemes; +export type DialectType = keyof typeof Dialect; -export const PORTS_MAP: Record = { - [Schemes.Mysql]: 3306, - [Schemes.Postgres]: 5432, - [Schemes.Sqlite]: 0, +export const PORTS_MAP: Record = { + [Dialect.Mysql]: 3306, + [Dialect.Postgres]: 5432, + [Dialect.Sqlite]: 0, } as const; -export const schemes = [ - Schemes.Mysql, - Schemes.Postgres, - Schemes.Sqlite, +export const dialects = [ + Dialect.Mysql, + Dialect.Postgres, + Dialect.Sqlite, ] as const; export type HostCredentials = { @@ -46,15 +46,15 @@ export const ConnectionMode = { export type ConnectionModeType = keyof typeof ConnectionMode; export const SocketPathDefaults = { - [Schemes.Mysql]: "/var/run/mysqld/mysqld.sock", - [Schemes.Postgres]: "/var/run/postgresql/.s.PGSQL.5432", - [Schemes.Sqlite]: "", + [Dialect.Mysql]: "/var/run/mysqld/mysqld.sock", + [Dialect.Postgres]: "/var/run/postgresql/.s.PGSQL.5432", + [Dialect.Sqlite]: "", } as const; export const AvailableConnectionModes = { - [Schemes.Mysql]: [ConnectionMode.Host, ConnectionMode.Socket], - [Schemes.Postgres]: [ConnectionMode.Host, ConnectionMode.Socket], - [Schemes.Sqlite]: [ConnectionMode.File], + [Dialect.Mysql]: [ConnectionMode.Host, ConnectionMode.Socket], + [Dialect.Postgres]: [ConnectionMode.Host, ConnectionMode.Socket], + [Dialect.Sqlite]: [ConnectionMode.File], } as const; export const connectionModes = [ @@ -64,13 +64,14 @@ export const connectionModes = [ ] as const; export type Scheme = Partial< - Record>> + Record>> >; export type ConnectionConfig = { id: string; name: string; scheme: Scheme; + dialect: DialectType; color: ConnectionColor; }; @@ -101,25 +102,14 @@ export const connectionColors = [ export type ConnectionColor = (typeof connectionColors)[number]; -export type DbSchema = { - [schema: string]: { - [table: string]: { - [column: string]: { - TABLE_SCHEMA: string; - TABLE_NAME: string; - COLUMN_NAME: string; - COLUMN_DEFAULT: string; - IS_NULLABLE: string; - DATA_TYPE: string; - CHARACTER_MAXIMUM_LENGTH: number; - NUMERIC_PRECISION: number; - NUMERIC_SCALE: number; - }; - }; - }; -}; +type JSONValue = + | string + | number + | boolean + | { [x: string]: JSONValue } + | Array; -export type Row = Record; +export type Row = Record; export type ResultSet = { affected_rows: number; @@ -136,34 +126,90 @@ export type RawQueryResult = { result: Row[]; }; -export const TableStrucureEntity = { - Columns: "columns", - Indices: "indices", - Constraints: "constraints", - Triggers: "triggers", +export const TableEntity = { + columns: "columns", + indices: "indices", + constraints: "constraints", + triggers: "triggers", } as const; +export type TableStrucureEntityType = keyof typeof TableEntity; + export const TableStrucureEntities = [ - TableStrucureEntity.Columns, - TableStrucureEntity.Indices, - TableStrucureEntity.Constraints, - TableStrucureEntity.Triggers, + TableEntity.columns, + TableEntity.indices, + TableEntity.constraints, + TableEntity.triggers, ] as const; -const STRUCTURE_TYPES = { - Column: "Column", - Constraint: "Constraint", - Index: "Index", - Trigger: "Trigger", -} as const; - export const SORT_ORDER = { - [Schemes.Mysql]: { - [STRUCTURE_TYPES.Column]: [ + [Dialect.Mysql]: { + [TableEntity.columns]: [ "COLUMN_NAME", "COLUMN_TYPE", "IS_NULLABLE", "CHARACTER_MAXIMUM_LENGTH", ], + [TableEntity.indices]: ["INDEX_NAME", "NON_UNIQUE", "COLUMN_NAME"], + [TableEntity.constraints]: [ + "CONSTRAINT_NAME", + "TABLE_NAME", + "COLUMN_NAME", + "REFERENCED_COLUMN_NAME", + "REFERENCED_TABLE_NAME", + ], + [TableEntity.triggers]: [ + "TRIGGER_NAME", + "EVENT_MANIPULATION", + "EVENT_OBJECT_TABLE", + "ACTION_TIMING", + "ACTION_STATEMENT", + ], }, -}; + [Dialect.Postgres]: { + [TableEntity.columns]: [ + "COLUMN_NAME", + "DATA_TYPE", + "IS_NULLABLE", + "CHARACTER_MAXIMUM_LENGTH", + ], + [TableEntity.indices]: ["INDEX_NAME", "COLUMN_NAME"], + [TableEntity.constraints]: [ + "CONSTRAINT_NAME", + "TABLE_NAME", + "COLUMN_NAME", + "REFERENCED_COLUMN_NAME", + "REFERENCED_TABLE_NAME", + ], + [TableEntity.triggers]: [ + "TRIGGER_NAME", + "EVENT_MANIPULATION", + "EVENT_OBJECT_TABLE", + "ACTION_TIMING", + "ACTION_STATEMENT", + ], + }, + [Dialect.Sqlite]: { + [TableEntity.columns]: [ + "COLUMN_NAME", + "DATA_TYPE", + "IS_NULLABLE", + "CHARACTER_MAXIMUM_LENGTH", + ], + [TableEntity.indices]: ["INDEX_NAME", "COLUMN_NAME"], + [TableEntity.constraints]: [ + "CONSTRAINT_NAME", + "TABLE_NAME", + "COLUMN_NAME", + "REFERENCED_COLUMN_NAME", + "REFERENCED_TABLE_NAME", + ], + [TableEntity.triggers]: [ + "TRIGGER_NAME", + "EVENT_MANIPULATION", + "EVENT_OBJECT_TABLE", + "ACTION_TIMING", + "ACTION_STATEMENT", + ], + }, +} as const; diff --git a/src/services/ConnectionTabs.ts b/src/services/ConnectionTabs.ts index 5b121c2..6f01ab1 100644 --- a/src/services/ConnectionTabs.ts +++ b/src/services/ConnectionTabs.ts @@ -1,5 +1,5 @@ import { createStore, produce } from "solid-js/store"; -import { ConnectionConfig, DbSchema, ResultSet } from "../interfaces"; +import { ConnectionConfig, ResultSet, Row, TableEntity } from "../interfaces"; import { Store } from "tauri-plugin-store-api"; import { debounce } from "utils/utils"; import { invoke } from "@tauri-apps/api"; @@ -62,10 +62,10 @@ export type QueryContentTabData = { export type TableStructureContentTabData = { table: string; - columns: Record[]; - indices: Record[]; - constraints: Record[]; - triggers: Record[]; + [TableEntity.columns]: Row[]; + [TableEntity.indices]: Row[]; + [TableEntity.triggers]: Row[]; + [TableEntity.constraints]: Row[]; }; export type ContentTab = { @@ -85,7 +85,7 @@ export type ContentTab = { type ConnectionTab = { label: string; id: string; - schema: DbSchema; + schema: Record; connection: ConnectionConfig; }; @@ -128,6 +128,7 @@ export const ConnectionTabsService = () => { const restoreConnectionStore = async () => { const conn_tabs: ConnectionStore = await getSavedData(CONNECTIONS_KEY); + console.log({ conn_tabs }); if (!conn_tabs.tabs) return; const tabs = await conn_tabs.tabs.reduce(async (acc, conn) => { const res = await acc; diff --git a/src/utils/utils.ts b/src/utils/utils.ts index b13c63c..f25b8b7 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1,4 +1,9 @@ -import { SORT_ORDER, TableStructureResult } from "interfaces"; +import { + DialectType, + Row, + SORT_ORDER, + TableStrucureEntityType, +} from "interfaces"; export const omit = (obj: any, ...keys: string[]) => { const copy = { ...obj }; @@ -35,10 +40,24 @@ export const debounce = (func: Function, wait: number) => { }; }; -export const sortTableStructure = (tableStructure: TableStructureResult) => { }; +export const sortTableStructure = ( + order: string[], + dialect: DialectType, + tableStructureEntity: TableStrucureEntityType +) => { + return order.sort((a, b) => { + const sortOrder = SORT_ORDER[dialect][tableStructureEntity]; + if (!sortOrder) return 0; + const aIndex = sortOrder.indexOf(a as never); + const bIndex = sortOrder.indexOf(b as never); + if (aIndex === -1) return 1; + if (bIndex === -1) return -1; + return aIndex - bIndex; + }); +}; -// TODO: for all dialects -export const columnsToSchema = (columns: Record[]) => { +// TODO: handle all dialects +export const columnsToSchema = (columns: Row[], _dialect: DialectType) => { const schema = columns.reduce((acc: any, col: any) => { return { ...acc, diff --git a/tailwind.config.js b/tailwind.config.js index c08beaf..6a59295 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,17 +1,24 @@ /** @type {import('tailwindcss').Config} */ export default { - darkMode: 'class', - content: [ - './src/**/*.{js,jsx,ts,tsx}', - ], + darkMode: "class", + content: ["./src/**/*.{js,jsx,ts,tsx}"], theme: { extend: {}, }, - plugins: [ - require("daisyui") - ], + plugins: [require("daisyui")], daisyui: { - themes: ["dark", "aqua", "synthwave", "dracula", "night", "cupcake"], // true: all themes | false: only + dark | array: specific themes like this ["light", "dark", "cupcake"] + themes: [ + "retro", + "forest", + "autumn", + "garden", + "business", + "dark", + "synthwave", + "dracula", + "night", + "cupcake", + ], // true: all themes | false: only + dark | array: specific themes like this ["light", "dark", "cupcake"] darkTheme: "dark", // name of one of the included themes for dark mode base: true, // applies background color and foreground color for root element by default styled: true, // include daisyUI colors and design decisions for all components @@ -43,6 +50,5 @@ export default { "bg-fuchsia-500", "bg-pink-500", "bg-rose-500", - ] -} - + ], +};
{idx + 1}{cell}{String(cell)}