Skip to content

Commit d42d3a6

Browse files
committed
feat: add basic table definition
1 parent a50941c commit d42d3a6

File tree

16 files changed

+380
-187
lines changed

16 files changed

+380
-187
lines changed

src/drivers/common/MySQLCommonInterface.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import { DatabaseSchemas, TableConstraintTypeSchema } from 'types/SqlSchema';
1+
import {
2+
DatabaseSchemas,
3+
TableConstraintTypeSchema,
4+
TableDefinitionSchema,
5+
} from 'types/SqlSchema';
26
import SQLCommonInterface from './SQLCommonInterface';
37
import { SqlRunnerManager } from 'libs/SqlRunnerManager';
48
import { qb } from 'libs/QueryBuilder';
@@ -147,4 +151,64 @@ export default class MySQLCommonInterface extends SQLCommonInterface {
147151

148152
return databases;
149153
}
154+
155+
async getTableSchema(table: string): Promise<TableDefinitionSchema> {
156+
const response = await this.runner.execute(
157+
[
158+
{
159+
sql: qb()
160+
.table('information_schema.columns')
161+
.select(
162+
'table_schema',
163+
'table_name',
164+
'column_name',
165+
'data_type',
166+
'is_nullable',
167+
'column_comment',
168+
'character_maximum_length',
169+
'numeric_precision',
170+
'numeric_scale',
171+
'column_default'
172+
)
173+
.where({
174+
table_schema: this.currentDatabaseName,
175+
table_name: table,
176+
})
177+
.toRawSQL(),
178+
},
179+
],
180+
{
181+
disableAnalyze: true,
182+
skipProtection: true,
183+
}
184+
);
185+
186+
const columns = response[0].result.rows as [
187+
string,
188+
string,
189+
string,
190+
string,
191+
string,
192+
string,
193+
number | null,
194+
number | null,
195+
number | null,
196+
string | null
197+
][];
198+
199+
return {
200+
name: table,
201+
columns: columns.map((col) => ({
202+
name: col[2],
203+
dataType: col[3],
204+
nullable: col[4] === 'YES',
205+
comment: col[5],
206+
charLength: col[6],
207+
nuermicPrecision: col[7],
208+
numericScale: col[8],
209+
default: col[9],
210+
})),
211+
createSql: '',
212+
};
213+
}
150214
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
import { DatabaseSchemas } from 'types/SqlSchema';
1+
import { DatabaseSchemas, TableDefinitionSchema } from 'types/SqlSchema';
22
import SQLCommonInterface from './SQLCommonInterface';
33

44
export default class NotImplementCommonInterface extends SQLCommonInterface {
55
async getSchema(): Promise<DatabaseSchemas> {
66
throw 'Not implemented';
77
}
8+
9+
async getTableSchema(): Promise<TableDefinitionSchema> {
10+
throw 'Not implemented';
11+
}
812
}
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { DatabaseSchemas } from 'types/SqlSchema';
1+
import { DatabaseSchemas, TableDefinitionSchema } from 'types/SqlSchema';
22

33
export default abstract class SQLCommonInterface {
44
abstract getSchema(): Promise<DatabaseSchemas>;
5+
abstract getTableSchema(table: string): Promise<TableDefinitionSchema>;
56
}

src/libs/QueryBuilder.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -73,14 +73,16 @@ export class QueryBuilder {
7373

7474
where(conditions: Record<string, unknown>) {
7575
for (const [field, value] of Object.entries(conditions)) {
76-
this.states.where.push({
77-
mode: 'AND',
78-
condition: {
79-
field,
80-
value,
81-
op: '=',
82-
},
83-
});
76+
if (value !== undefined) {
77+
this.states.where.push({
78+
mode: 'AND',
79+
condition: {
80+
field,
81+
value,
82+
op: '=',
83+
},
84+
});
85+
}
8486
}
8587

8688
return this;

src/libs/SqlRunnerManager.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ export class SqlRunnerManager {
7474

7575
if (options?.onStart) options.onStart();
7676

77+
console.log(statement.sql);
78+
7779
const returnedResult = await this.executor(
7880
statement.sql,
7981
statement.params
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import {
2+
PropsWithChildren,
3+
ReactElement,
4+
useEffect,
5+
useRef,
6+
useState,
7+
} from 'react';
8+
import styles from './styles.module.scss';
9+
10+
interface ResizableTableProps {
11+
headers: { name: string; icon?: ReactElement }[];
12+
}
13+
14+
function ResizeHandler({ idx }: { idx: number }) {
15+
const handlerRef = useRef<HTMLDivElement>(null);
16+
const [resizing, setResizing] = useState(false);
17+
18+
useEffect(() => {
19+
if (handlerRef.current && resizing) {
20+
const table = handlerRef.current?.parentNode?.parentNode?.parentNode
21+
?.parentNode as HTMLTableElement;
22+
23+
if (table) {
24+
const onMouseMove = (e: MouseEvent) =>
25+
requestAnimationFrame(() => {
26+
const scrollOffset = table.scrollLeft;
27+
const width =
28+
scrollOffset +
29+
e.clientX -
30+
((
31+
handlerRef.current?.parentNode as HTMLTableCellElement
32+
).getBoundingClientRect().x || 0);
33+
34+
if (table) {
35+
const columns = table.style.gridTemplateColumns.split(' ');
36+
columns[idx] = width + 'px';
37+
table.style.gridTemplateColumns = columns.join(' ');
38+
}
39+
});
40+
41+
const onMouseUp = () => {
42+
setResizing(false);
43+
};
44+
45+
table.addEventListener('mousemove', onMouseMove);
46+
table.addEventListener('mouseup', onMouseUp);
47+
48+
return () => {
49+
table.removeEventListener('mousemove', onMouseMove);
50+
table.removeEventListener('mouseup', onMouseUp);
51+
};
52+
}
53+
}
54+
}, [handlerRef, idx, resizing, setResizing]);
55+
56+
return (
57+
<div
58+
className={styles.handler}
59+
ref={handlerRef}
60+
onMouseDown={() => setResizing(true)}
61+
></div>
62+
);
63+
}
64+
65+
export default function ResizableTable({
66+
headers,
67+
children,
68+
}: PropsWithChildren<ResizableTableProps>) {
69+
const tableRef = useRef<HTMLTableElement>(null);
70+
const [isGridCSSPrepared, setGridCSSPrepared] = useState(false);
71+
72+
useEffect(() => {
73+
if (tableRef.current) {
74+
tableRef.current.style.gridTemplateColumns =
75+
headers.map(() => '150px').join(' ') || '';
76+
setGridCSSPrepared(true);
77+
}
78+
}, [headers, tableRef, setGridCSSPrepared]);
79+
80+
return (
81+
<table ref={tableRef} className={styles.table}>
82+
{isGridCSSPrepared && (
83+
<>
84+
<thead>
85+
<tr>
86+
{headers.map((header, idx) => (
87+
<th key={header.name}>
88+
<div className={styles.headerContent}>
89+
<div className={styles.headerContentTitle}>
90+
{header.name}
91+
</div>
92+
{header.icon && (
93+
<div className={styles.headerContentIcon}>
94+
header.icon
95+
</div>
96+
)}
97+
</div>
98+
<ResizeHandler idx={idx} />
99+
</th>
100+
))}
101+
</tr>
102+
</thead>
103+
104+
<tbody>{children}</tbody>
105+
</>
106+
)}
107+
</table>
108+
);
109+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
.table {
2+
display: grid;
3+
position: relative;
4+
user-select: none;
5+
6+
thead, tbody, tr {
7+
display: contents;
8+
}
9+
10+
.headerContent {
11+
display: flex;
12+
gap: 5px;
13+
width: 100%;
14+
text-align: left;
15+
}
16+
17+
.headerContentIcon {
18+
display: flex;
19+
justify-content: center;
20+
align-items: center;
21+
}
22+
23+
.headerContentIcon img {
24+
width: 16px;
25+
height: 16px;
26+
}
27+
28+
.headerContentTitle {
29+
flex-grow: 1;
30+
padding-left: 2px;
31+
padding-right: 2px;
32+
overflow: hidden;
33+
}
34+
35+
td, th {
36+
text-overflow: ellipsis;
37+
white-space: nowrap;
38+
border-bottom: 1px solid var(--color-table-grid);
39+
border-right: 1px solid var(--color-table-grid);
40+
}
41+
42+
td {
43+
overflow: hidden;
44+
}
45+
46+
th {
47+
padding: 5px 10px;
48+
position: sticky;
49+
top: 0;
50+
z-index: 5;
51+
background: var(--color-surface);
52+
}
53+
54+
.handler {
55+
position: absolute;
56+
right: 0;
57+
top: 0;
58+
bottom: 0;
59+
width: 3px;
60+
cursor: col-resize;
61+
background: var(--color-critical);
62+
opacity: 0;
63+
}
64+
65+
.handler:hover {
66+
opacity: 0.5;
67+
}
68+
}

src/renderer/components/WindowTab/DatabaseTableList.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,20 @@ export default function DatabaseTableList() {
4545
{
4646
text: 'Open Structure',
4747
onClick: () => {
48-
newWindow(tableName, (key, name) => {
49-
return (
50-
<SqlTableSchemaTab
51-
tabKey={key}
52-
name={name}
53-
database={databaseName}
54-
table={tableName}
55-
/>
56-
);
57-
});
48+
newWindow(
49+
tableName,
50+
(key, name) => {
51+
return (
52+
<SqlTableSchemaTab
53+
tabKey={key}
54+
name={name}
55+
database={databaseName}
56+
table={tableName}
57+
/>
58+
);
59+
},
60+
<FontAwesomeIcon icon={faTableList} color="#3498db" />
61+
);
5862
},
5963
},
6064
];

src/renderer/components/WindowTab/index.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
import { faAdd, faClose, faCode } from '@fortawesome/free-solid-svg-icons';
22
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
3-
import { ReactNode } from 'react';
3+
import { ReactElement, ReactNode } from 'react';
44
import styles from './styles.module.scss';
55

66
interface WindowTabItem {
77
key: string;
88
name: string;
9+
icon?: ReactElement;
910
component: ReactNode;
1011
}
1112

@@ -55,7 +56,7 @@ export default function WindowTab({
5556
}}
5657
>
5758
<span className={styles.icon}>
58-
<FontAwesomeIcon icon={faCode} />
59+
{tab.icon ? tab.icon : <FontAwesomeIcon icon={faCode} />}
5960
</span>
6061
<span>{tab.name}</span>
6162
{tabs.length > 1 && onTabClosed && (

src/renderer/contexts/SqlExecuteProvider.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ export function SqlExecuteProvider({ children }: PropsWithChildren) {
3131
new MySQLCommonInterface(
3232
runner,
3333
(setting?.config as MySqlConnectionConfig)?.database
34+
? (setting?.config as MySqlConnectionConfig)?.database
35+
: undefined
3436
),
3537
[runner, setting]
3638
);

0 commit comments

Comments
 (0)