Skip to content

Commit

Permalink
feat(MySQL): check constraints management support
Browse files Browse the repository at this point in the history
  • Loading branch information
Fabio286 committed Oct 25, 2024
1 parent f083a8a commit 6365e07
Show file tree
Hide file tree
Showing 15 changed files with 1,081 additions and 582 deletions.
1 change: 1 addition & 0 deletions src/common/customizations/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const defaults: Customizations = {
tableArray: false,
tableRealCount: false,
tableDuplicate: false,
tableCheck: false,
viewSettings: false,
triggerSettings: false,
triggerFunctionSettings: false,
Expand Down
1 change: 1 addition & 0 deletions src/common/customizations/mysql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ export const customizations: Customizations = {
tableTruncateDisableFKCheck: true,
tableDuplicate: true,
tableDdl: true,
tableCheck: true,
viewAdd: true,
triggerAdd: true,
routineAdd: true,
Expand Down
13 changes: 13 additions & 0 deletions src/common/interfaces/antares.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,21 @@ export interface TableForeign {
oldName?: string;
}

export interface TableCheck {
// eslint-disable-next-line camelcase
_antares_id?: string;
name: string;
clause: string;
}

export interface CreateTableParams {
/** Connection UID */
uid?: string;
schema: string;
fields: TableField[];
foreigns: TableForeign[];
indexes: TableIndex[];
checks: TableCheck[];
options: TableOptions;
}

Expand Down Expand Up @@ -193,6 +201,11 @@ export interface AlterTableParams {
changes: TableForeign[];
deletions: TableForeign[];
};
checkChanges: {
additions: TableCheck[];
changes: TableCheck[];
deletions: TableCheck[];
};
options: TableOptions;
}

Expand Down
1 change: 1 addition & 0 deletions src/common/interfaces/customizations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export interface Customizations {
tableArray?: boolean;
tableRealCount?: boolean;
tableTruncateDisableFKCheck?: boolean;
tableCheck?: boolean;
tableDdl?: boolean;
viewAdd?: boolean;
viewSettings?: boolean;
Expand Down
13 changes: 13 additions & 0 deletions src/main/ipc-handlers/tables.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,19 @@ export default (connections: Record<string, antares.Client>) => {
}
});

ipcMain.handle('get-table-checks', async (event, params) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };

try {
const result = await connections[params.uid].getTableChecks(params);

return { status: 'success', response: result };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});

ipcMain.handle('get-table-ddl', async (event, params) => {
if (!validateSender(event.senderFrame)) return { status: 'error', response: 'Unauthorized process' };

Expand Down
4 changes: 4 additions & 0 deletions src/main/libs/clients/BaseClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,10 @@ export abstract class BaseClient {
throw new Error('Method "dropSchema" not implemented');
}

getTableChecks (...args: any) {
throw new Error('Method "getTableDll" not implemented');
}

getTableDll (...args: any) {
throw new Error('Method "getTableDll" not implemented');
}
Expand Down
63 changes: 60 additions & 3 deletions src/main/libs/clients/MySQLClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ export class MySQLClient extends BaseClient {

this._ssh = new SSH2Promise({
...this._params.ssh,
reconnect: true,
reconnectTries: 3,
debug: process.env.NODE_ENV !== 'production' ? (s) => console.log(s) : null
});

Expand Down Expand Up @@ -689,6 +691,34 @@ export class MySQLClient extends BaseClient {
return rows.length ? rows[0].count : 0;
}

async getTableChecks ({ schema, table }: { schema: string; table: string }): Promise<antares.TableCheck[]> {
const { rows } = await this.raw(`
SELECT
CONSTRAINT_NAME as name,
CHECK_CLAUSE as clausole
FROM information_schema.CHECK_CONSTRAINTS
WHERE CONSTRAINT_SCHEMA = "${schema}"
AND CONSTRAINT_NAME IN (
SELECT
CONSTRAINT_NAME
FROM
information_schema.TABLE_CONSTRAINTS
WHERE
TABLE_SCHEMA = "${schema}"
AND TABLE_NAME = "${table}"
AND CONSTRAINT_TYPE = 'CHECK'
)
`);

if (rows.length) {
return rows.map(row => ({
name: row.name,
clause: row.clausole
}));
}
return [];
}

async getTableOptions ({ schema, table }: { schema: string; table: string }) {
/* eslint-disable camelcase */
interface TableOptionsResult {
Expand Down Expand Up @@ -865,11 +895,13 @@ export class MySQLClient extends BaseClient {
fields,
foreigns,
indexes,
checks,
options
} = params;
const newColumns: string[] = [];
const newIndexes: string[] = [];
const newForeigns: string[] = [];
const newChecks: string[] = [];

let sql = `CREATE TABLE \`${schema}\`.\`${options.name}\``;

Expand Down Expand Up @@ -910,7 +942,13 @@ export class MySQLClient extends BaseClient {
newForeigns.push(`CONSTRAINT \`${foreign.constraintName}\` FOREIGN KEY (\`${foreign.field}\`) REFERENCES \`${foreign.refTable}\` (\`${foreign.refField}\`) ON UPDATE ${foreign.onUpdate} ON DELETE ${foreign.onDelete}`);
});

sql = `${sql} (${[...newColumns, ...newIndexes, ...newForeigns].join(', ')}) COMMENT='${options.comment}', COLLATE='${options.collation}', ENGINE=${options.engine}`;
// ADD TABLE CHECKS
checks.forEach(check => {
if (!check.clause.trim().length) return;
newChecks.push(`${check.name ? `CONSTRAINT \`${check.name}\` ` : ''}CHECK (${check.clause})`);
});

sql = `${sql} (${[...newColumns, ...newIndexes, ...newForeigns, ...newChecks].join(', ')}) COMMENT='${options.comment}', COLLATE='${options.collation}', ENGINE=${options.engine}`;

return await this.raw(sql);
}
Expand All @@ -924,13 +962,15 @@ export class MySQLClient extends BaseClient {
changes,
indexChanges,
foreignChanges,
checkChanges,
options
} = params;

const sql = `ALTER TABLE \`${schema}\`.\`${table}\` `;
const alterColumnsAdd: string[] = [];
const alterColumnsChange: string[] = [];
const alterColumnsDrop: string[] = [];
const alterQueryes: string[] = [];

// OPTIONS
if ('comment' in options) alterColumnsChange.push(`COMMENT='${options.comment}'`);
Expand Down Expand Up @@ -976,6 +1016,12 @@ export class MySQLClient extends BaseClient {
alterColumnsAdd.push(`ADD CONSTRAINT \`${addition.constraintName}\` FOREIGN KEY (\`${addition.field}\`) REFERENCES \`${addition.refTable}\` (\`${addition.refField}\`) ON UPDATE ${addition.onUpdate} ON DELETE ${addition.onDelete}`);
});

// ADD TABLE CHECKS
checkChanges.additions.forEach(addition => {
if (!addition.clause.trim().length) return;
alterColumnsAdd.push(`ADD ${addition.name ? `CONSTRAINT \`${addition.name}\` ` : ''}CHECK (${addition.clause})`);
});

// CHANGE FIELDS
changes.forEach(change => {
const typeInfo = this.getTypeInfo(change.type);
Expand All @@ -987,9 +1033,9 @@ export class MySQLClient extends BaseClient {
${change.zerofill ? 'ZEROFILL' : ''}
${change.nullable ? 'NULL' : 'NOT NULL'}
${change.autoIncrement ? 'AUTO_INCREMENT' : ''}
${change.collation ? `COLLATE ${change.collation}` : ''}
${change.default !== null ? `DEFAULT ${change.default || '\'\''}` : ''}
${change.comment ? `COMMENT '${change.comment}'` : ''}
${change.collation ? `COLLATE ${change.collation}` : ''}
${change.onUpdate ? `ON UPDATE ${change.onUpdate}` : ''}
${change.after ? `AFTER \`${change.after}\`` : 'FIRST'}`);
});
Expand Down Expand Up @@ -1020,6 +1066,13 @@ export class MySQLClient extends BaseClient {
alterColumnsChange.push(`ADD CONSTRAINT \`${change.constraintName}\` FOREIGN KEY (\`${change.field}\`) REFERENCES \`${change.refTable}\` (\`${change.refField}\`) ON UPDATE ${change.onUpdate} ON DELETE ${change.onDelete}`);
});

// CHANGE CHECK TABLE
checkChanges.changes.forEach(change => {
if (!change.clause.trim().length) return;
alterQueryes.push(`${sql} DROP CONSTRAINT \`${change.name}\``);
alterQueryes.push(`${sql} ADD ${change.name ? `CONSTRAINT \`${change.name}\` ` : ''}CHECK (${change.clause})`);
});

// DROP FIELDS
deletions.forEach(deletion => {
alterColumnsDrop.push(`DROP COLUMN \`${deletion.name}\``);
Expand All @@ -1038,7 +1091,11 @@ export class MySQLClient extends BaseClient {
alterColumnsDrop.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``);
});

const alterQueryes = [];
// DROP CHECK TABLE
checkChanges.deletions.forEach(deletion => {
alterQueryes.push(`${sql} DROP CONSTRAINT \`${deletion.name}\``);
});

if (alterColumnsAdd.length) alterQueryes.push(sql+alterColumnsAdd.join(', '));
if (alterColumnsChange.length) alterQueryes.push(sql+alterColumnsChange.join(', '));
if (alterColumnsDrop.length) alterQueryes.push(sql+alterColumnsDrop.join(', '));
Expand Down
2 changes: 2 additions & 0 deletions src/main/libs/clients/PostgreSQLClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ export class PostgreSQLClient extends BaseClient {
try {
this._ssh = new SSH2Promise({
...this._params.ssh,
reconnect: true,
reconnectTries: 3,
debug: process.env.NODE_ENV !== 'production' ? (s) => console.log(s) : null
});

Expand Down
43 changes: 42 additions & 1 deletion src/renderer/components/WorkspaceTabNewTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,19 @@
/>
<span>{{ t('database.foreignKeys') }}</span>
</button>
<button
class="btn btn-dark btn-sm ml-2 mr-0"
:disabled="isSaving || !localFields.length"
:title="t('database.manageTableChecks')"
@click="showTableChecksModal"
>
<BaseIcon
class="mr-1"
icon-name="mdiTableCheck"
:size="24"
/>
<span>{{ t('database.tableChecks') }}</span>
</button>
</div>
<div class="workspace-query-info">
<div class="d-flex" :title="t('database.schema')">
Expand Down Expand Up @@ -183,11 +196,19 @@
@hide="hideForeignModal"
@foreigns-update="foreignsUpdate"
/>
<WorkspaceTabPropsTableChecksModal
v-if="isTableChecksModal"
:local-checks="localTableChecks"
table="new"
:workspace="workspace"
@hide="hideTableChecksModal"
@checks-update="checksUpdate"
/>
</div>
</template>

<script setup lang="ts">
import { ConnectionParams, TableField, TableForeign, TableIndex, TableOptions } from 'common/interfaces/antares';
import { ConnectionParams, TableCheck, TableField, TableForeign, TableIndex, TableOptions } from 'common/interfaces/antares';
import { uidGen } from 'common/libs/uidGen';
import { ipcRenderer } from 'electron';
import { storeToRefs } from 'pinia';
Expand All @@ -198,6 +219,7 @@ import BaseIcon from '@/components/BaseIcon.vue';
import BaseLoader from '@/components/BaseLoader.vue';
import BaseSelect from '@/components/BaseSelect.vue';
import WorkspaceTabNewTableEmptyState from '@/components/WorkspaceTabNewTableEmptyState.vue';
import WorkspaceTabPropsTableChecksModal from '@/components/WorkspaceTabPropsTableChecksModal.vue';
import WorkspaceTabPropsTableFields from '@/components/WorkspaceTabPropsTableFields.vue';
import WorkspaceTabPropsTableForeignModal from '@/components/WorkspaceTabPropsTableForeignModal.vue';
import WorkspaceTabPropsTableIndexesModal from '@/components/WorkspaceTabPropsTableIndexesModal.vue';
Expand Down Expand Up @@ -236,12 +258,16 @@ const isLoading = ref(false);
const isSaving = ref(false);
const isIndexesModal = ref(false);
const isForeignModal = ref(false);
const isTableChecksModal = ref(false);
const originalFields: Ref<TableField[]> = ref([]);
const localFields: Ref<TableField[]> = ref([]);
const originalKeyUsage: Ref<TableForeign[]> = ref([]);
const localKeyUsage: Ref<TableForeign[]> = ref([]);
const originalIndexes: Ref<TableIndex[]> = ref([]);
const localIndexes: Ref<TableIndex[]> = ref([]);
const originalTableChecks: Ref<TableCheck[]> = ref([]);
const localTableChecks: Ref<TableCheck[]> = ref([]);
const tableOptions: Ref<TableOptions> = ref(null);
const localOptions: Ref<TableOptions> = ref(null);
const newFieldsCounter = ref(0);
Expand Down Expand Up @@ -274,6 +300,7 @@ const isChanged = computed(() => {
return JSON.stringify(originalFields.value) !== JSON.stringify(localFields.value) ||
JSON.stringify(originalKeyUsage.value) !== JSON.stringify(localKeyUsage.value) ||
JSON.stringify(originalIndexes.value) !== JSON.stringify(localIndexes.value) ||
JSON.stringify(originalTableChecks.value) !== JSON.stringify(localTableChecks.value) ||
JSON.stringify(tableOptions.value) !== JSON.stringify(localOptions.value);
});
Expand All @@ -291,6 +318,7 @@ const saveChanges = async () => {
fields: localFields.value,
foreigns: localKeyUsage.value,
indexes: localIndexes.value,
checks: localTableChecks.value,
options: localOptions.value
};
Expand Down Expand Up @@ -326,6 +354,7 @@ const clearChanges = () => {
localFields.value = JSON.parse(JSON.stringify(originalFields.value));
localIndexes.value = JSON.parse(JSON.stringify(originalIndexes.value));
localKeyUsage.value = JSON.parse(JSON.stringify(originalKeyUsage.value));
localTableChecks.value = JSON.parse(JSON.stringify(originalTableChecks.value));
tableOptions.value = {
name: '',
Expand Down Expand Up @@ -446,10 +475,22 @@ const hideForeignModal = () => {
isForeignModal.value = false;
};
const showTableChecksModal = () => {
isTableChecksModal.value = true;
};
const hideTableChecksModal = () => {
isTableChecksModal.value = false;
};
const foreignsUpdate = (foreigns: TableForeign[]) => {
localKeyUsage.value = foreigns;
};
const checksUpdate = (checks: TableCheck[]) => {
localTableChecks.value = checks;
};
const saveContentListener = () => {
const hasModalOpen = !!document.querySelectorAll('.modal.active').length;
if (props.isSelected && !hasModalOpen && isChanged.value)
Expand Down
Loading

0 comments on commit 6365e07

Please sign in to comment.