mirror of https://github.com/Fabio286/antares.git
feat(MySQL): option to export from results SQL INSERTS in chunks, closes #501
This commit is contained in:
parent
afa61a9bc2
commit
0f24c80e5a
|
@ -94,7 +94,7 @@ export const valueToSqlString = (args: {
|
|||
? escapeAndQuote(moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`), client)
|
||||
: escapeAndQuote(val, client);
|
||||
}
|
||||
else if ('isArray' in field) {
|
||||
else if ('isArray' in field && field.isArray) {
|
||||
let localVal;
|
||||
if (Array.isArray(val))
|
||||
localVal = JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}');
|
||||
|
@ -152,17 +152,47 @@ export const valueToSqlString = (args: {
|
|||
};
|
||||
|
||||
export const jsonToSqlInsert = (args: {
|
||||
json: { [key: string]: any};
|
||||
json: { [key: string]: any}[];
|
||||
client: ClientCode;
|
||||
fields: { [key: string]: {type: string; datePrecision: number}};
|
||||
table: string;
|
||||
options?: {sqlInsertAfter: number; sqlInsertDivider: 'bytes' | 'rows'};
|
||||
}) => {
|
||||
const { client, json, fields, table } = args;
|
||||
const { client, json, fields, table, options } = args;
|
||||
const sqlInsertAfter = options && options.sqlInsertAfter ? options.sqlInsertAfter : 1;
|
||||
const sqlInsertDivider = options && options.sqlInsertDivider ? options.sqlInsertDivider : 'rows';
|
||||
const { elementsWrapper: ew } = customizations[client];
|
||||
const fieldNames = Object.keys(json).map(key => `${ew}${key}${ew}`);
|
||||
const values = Object.keys(json).map(key => (
|
||||
valueToSqlString({ val: json[key], client, field: fields[key] })
|
||||
));
|
||||
const fieldNames = Object.keys(json[0]).map(key => `${ew}${key}${ew}`);
|
||||
let insertStmt = `INSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES `;
|
||||
let insertsString = '';
|
||||
let queryLength = 0;
|
||||
let rowsWritten = 0;
|
||||
|
||||
return `INSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES (${values.join(', ')});`;
|
||||
for (const row of json) {
|
||||
const values = [];
|
||||
|
||||
values.push(Object.keys(row).map(key => (
|
||||
valueToSqlString({ val: row[key], client, field: fields[key] })
|
||||
)));
|
||||
|
||||
if (
|
||||
(sqlInsertDivider === 'bytes' && queryLength >= sqlInsertAfter * 1024) ||
|
||||
(sqlInsertDivider === 'rows' && rowsWritten === sqlInsertAfter)
|
||||
) {
|
||||
insertsString += insertStmt+';';
|
||||
insertStmt = `\nINSERT INTO ${ew}${table}${ew} (${fieldNames.join(', ')}) VALUES `;
|
||||
rowsWritten = 0;
|
||||
}
|
||||
rowsWritten++;
|
||||
|
||||
if (rowsWritten > 1) insertStmt += ',\n';
|
||||
|
||||
insertStmt += `(${values.join(',')})`;
|
||||
queryLength = insertStmt.length;
|
||||
}
|
||||
|
||||
if (rowsWritten > 0)
|
||||
insertsString += insertStmt+';';
|
||||
|
||||
return insertsString;
|
||||
};
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-database-arrow-down mr-1" />
|
||||
<i class="mdi mdi-24px mdi-database-export mr-1" />
|
||||
<span class="cut-text">{{ t('message.exportSchema') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -78,7 +78,7 @@
|
|||
<div ref="table" class="table table-hover">
|
||||
<div class="thead">
|
||||
<div class="tr text-center">
|
||||
<div class="th no-border" style="width: 50%;" />
|
||||
<div class="th no-border" :style="'width: 50%;'" />
|
||||
<div class="th no-border">
|
||||
<label
|
||||
class="form-checkbox m-0 px-2 form-inline"
|
||||
|
@ -120,7 +120,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="tr">
|
||||
<div class="th" style="width: 50%;">
|
||||
<div class="th" :style="'width: 50%;'">
|
||||
<div class="table-column-title">
|
||||
<span>{{ t('word.table') }}</span>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<div class="modal-header pl-2">
|
||||
<div class="modal-title h6">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-database-arrow-up mr-1" />
|
||||
<i class="mdi mdi-24px mdi-database-import mr-1" />
|
||||
<span class="cut-text">{{ t('message.importSchema') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -63,14 +63,14 @@
|
|||
class="context-element"
|
||||
@click="showExportSchemaModal"
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-database-arrow-down text-light pr-1" /> {{ t('word.export') }}</span>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-database-export text-light pr-1" /> {{ t('word.export') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="workspace.customizations.schemaImport"
|
||||
class="context-element"
|
||||
@click="initImport"
|
||||
>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-database-arrow-up text-light pr-1" /> {{ t('word.import') }}</span>
|
||||
<span class="d-flex"><i class="mdi mdi-18px mdi-database-import text-light pr-1" /> {{ t('word.import') }}</span>
|
||||
</div>
|
||||
<div
|
||||
v-if="workspace.customizations.schemaEdit"
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
:class="{'active': resultsetIndex === index}"
|
||||
@click="selectResultset(index)"
|
||||
>
|
||||
<a>{{ result.fields ? result.fields[0].table : '' }} ({{ result.rows.length }})</a>
|
||||
<a>{{ result.fields ? result.fields[0]?.table : '' }} ({{ result.rows.length }})</a>
|
||||
</li>
|
||||
</ul>
|
||||
<div ref="table" class="table table-hover">
|
||||
|
@ -111,6 +111,37 @@
|
|||
</div>
|
||||
</template>
|
||||
</ConfirmModal>
|
||||
|
||||
<ConfirmModal
|
||||
v-if="chunkModalRequest"
|
||||
@confirm="downloadTable('sql', chunkModalRequest as string, true)"
|
||||
@hide="chunkModalRequest = false"
|
||||
>
|
||||
<template #header>
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-file-export mr-1" />
|
||||
<span class="cut-text">{{ t('message.newInserStmtEvery') }}:</span>
|
||||
</div>
|
||||
</template>
|
||||
<template #body>
|
||||
<div class="columns">
|
||||
<div class="column col-6">
|
||||
<input
|
||||
v-model.number="sqlExportOptions.sqlInsertAfter"
|
||||
type="number"
|
||||
class="form-input"
|
||||
>
|
||||
</div>
|
||||
<div class="column col-6">
|
||||
<BaseSelect
|
||||
v-model="sqlExportOptions.sqlInsertDivider"
|
||||
class="form-select"
|
||||
:options="[{value: 'bytes', label: 'KiB'}, {value: 'rows', label: t('word.row', 2)}]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</ConfirmModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -128,6 +159,7 @@ import BaseVirtualScroll from '@/components/BaseVirtualScroll.vue';
|
|||
import WorkspaceTabQueryTableRow from '@/components/WorkspaceTabQueryTableRow.vue';
|
||||
import TableContext from '@/components/WorkspaceTabQueryTableContext.vue';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal.vue';
|
||||
import BaseSelect from '@/components/BaseSelect.vue';
|
||||
import * as moment from 'moment';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { TableField, QueryResult } from 'common/interfaces/antares';
|
||||
|
@ -179,9 +211,15 @@ const scrollElement = ref(null);
|
|||
const rowHeight = ref(23);
|
||||
const selectedField = ref(null);
|
||||
const isEditingRow = ref(false);
|
||||
const chunkModalRequest: Ref<false | string> = ref(false);
|
||||
const sqlExportOptions = ref({
|
||||
sqlInsertAfter: 250,
|
||||
sqlInsertDivider: 'bytes' as 'bytes' | 'rows'
|
||||
});
|
||||
|
||||
const workspaceSchema = computed(() => getWorkspace(props.connUid).breadcrumbs.schema);
|
||||
const workspaceClient = computed(() => getWorkspace(props.connUid).client);
|
||||
const customizations = computed(() => getWorkspace(props.connUid).customizations);
|
||||
|
||||
const primaryField = computed(() => {
|
||||
const primaryFields = fields.value.filter(field => field.key === 'pri');
|
||||
|
@ -219,7 +257,7 @@ const sortedResults = computed(() => {
|
|||
return localResults.value;
|
||||
});
|
||||
|
||||
const resultsWithRows = computed(() => props.results.filter(result => result.rows));
|
||||
const resultsWithRows = computed(() => props.results.filter(result => result.rows.length));
|
||||
const fields = computed(() => resultsWithRows.value.length ? resultsWithRows.value[resultsetIndex.value].fields : []);
|
||||
const keyUsage = computed(() => resultsWithRows.value.length ? resultsWithRows.value[resultsetIndex.value].keys : []);
|
||||
|
||||
|
@ -438,20 +476,17 @@ const copyRow = (format: string) => {
|
|||
if (format === 'json')
|
||||
navigator.clipboard.writeText(JSON.stringify(contentToCopy));
|
||||
else if (format === 'sql') {
|
||||
const sqlInserts = [];
|
||||
if (!Array.isArray(contentToCopy)) contentToCopy = [contentToCopy];
|
||||
|
||||
for (const row of contentToCopy) {
|
||||
sqlInserts.push(jsonToSqlInsert({
|
||||
json: row,
|
||||
client: workspaceClient.value,
|
||||
fields: fieldsObj.value as {
|
||||
[key: string]: {type: string; datePrecision: number};
|
||||
},
|
||||
table: getTable(resultsetIndex.value)
|
||||
}));
|
||||
}
|
||||
navigator.clipboard.writeText(sqlInserts.join('\n'));
|
||||
const sqlInserts = jsonToSqlInsert({
|
||||
json: contentToCopy,
|
||||
client: workspaceClient.value,
|
||||
fields: fieldsObj.value as {
|
||||
[key: string]: {type: string; datePrecision: number};
|
||||
},
|
||||
table: getTable(resultsetIndex.value)
|
||||
});
|
||||
navigator.clipboard.writeText(sqlInserts);
|
||||
}
|
||||
else if (format === 'csv') {
|
||||
const csv = [];
|
||||
|
@ -665,13 +700,22 @@ const selectResultset = (index: number) => {
|
|||
resultsetIndex.value = index;
|
||||
};
|
||||
|
||||
const downloadTable = (format: 'csv' | 'json' | 'sql', table: string) => {
|
||||
const downloadTable = (format: 'csv' | 'json' | 'sql', table: string, chunks = false) => {
|
||||
if (!sortedResults.value) return;
|
||||
|
||||
if (format === 'sql' && !chunks && customizations.value.exportByChunks) {
|
||||
chunkModalRequest.value = table;
|
||||
return;
|
||||
}
|
||||
else
|
||||
chunkModalRequest.value = false;
|
||||
|
||||
const rows = sortedResults.value.map((row: any) => {
|
||||
delete row._antares_id;
|
||||
return row;
|
||||
const clonedRow = { ...row };
|
||||
delete clonedRow._antares_id;
|
||||
return clonedRow;
|
||||
});
|
||||
|
||||
exportRows({
|
||||
type: format,
|
||||
content: rows,
|
||||
|
@ -679,7 +723,8 @@ const downloadTable = (format: 'csv' | 'json' | 'sql', table: string) => {
|
|||
[key: string]: {type: string; datePrecision: number};
|
||||
},
|
||||
client: workspaceClient.value,
|
||||
table
|
||||
table,
|
||||
sqlOptions: chunks ? { ...sqlExportOptions.value }: null
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ export const exportRows = (args: {
|
|||
fields?: {
|
||||
[key: string]: {type: string; datePrecision: number};
|
||||
};
|
||||
sqlOptions?: {sqlInsertAfter: number; sqlInsertDivider: 'bytes' | 'rows'};
|
||||
}) => {
|
||||
let mime;
|
||||
let content;
|
||||
|
@ -35,19 +36,16 @@ export const exportRows = (args: {
|
|||
}
|
||||
case 'sql': {
|
||||
mime = 'text/sql';
|
||||
const sql = [];
|
||||
const sql = jsonToSqlInsert({
|
||||
json: args.content,
|
||||
client:
|
||||
args.client,
|
||||
fields: args.fields,
|
||||
table: args.table,
|
||||
options: args.sqlOptions
|
||||
});
|
||||
|
||||
for (const row of args.content) {
|
||||
sql.push(jsonToSqlInsert({
|
||||
json: row,
|
||||
client:
|
||||
args.client,
|
||||
fields: args.fields,
|
||||
table: args.table
|
||||
}));
|
||||
}
|
||||
|
||||
content = sql.join('\n');
|
||||
content = sql;
|
||||
break;
|
||||
}
|
||||
case 'json':
|
||||
|
|
Loading…
Reference in New Issue