Merge pull request #1349 from dbgate/feature/mysql-indexes

MySQL FULLTEXT support #1305
This commit is contained in:
Jan Prochazka
2026-02-09 13:16:37 +01:00
committed by GitHub
6 changed files with 99 additions and 12 deletions
+6 -1
View File
@@ -544,9 +544,14 @@ export class SqlDumper implements AlterProcessor {
}
this.endCommand();
}
indexType(ix: IndexInfo) {
if (ix.isUnique) {
this.put(' ^unique');
}
}
createIndex(ix: IndexInfo) {
this.put('^create');
if (ix.isUnique) this.put(' ^unique');
this.indexType(ix);
this.put(' ^index %i &n^on %f (&>&n', ix.constraintName, ix);
this.putCollection(',&n', ix.columns, col => {
this.put('%i %k', col.columnName, col.isDescending == true ? 'DESC' : 'ASC');
+6
View File
@@ -50,6 +50,12 @@ export interface SqlDialect {
multipleSchema?: boolean;
filteredIndexes?: boolean;
namedDefaultConstraint?: boolean;
indexTypes?: {
value: string;
label: string;
isUnique?: boolean;
indexType?: string;
}[];
specificNullabilityImplementation?: boolean;
implicitNullDeclaration?: boolean;
@@ -12,9 +12,41 @@
export let tableInfo;
export let driver;
function getIndexTypeValue(constraintInfo, indexTypeOptions) {
if (!indexTypeOptions?.length) return null;
const indexType = constraintInfo?.indexType?.toString()?.toUpperCase();
if (indexType === 'FULLTEXT') {
return indexTypeOptions.find(option => option.indexType?.toString()?.toUpperCase() === 'FULLTEXT')?.value;
}
if (constraintInfo?.isUnique) {
return indexTypeOptions.find(option => option.isUnique)?.value || indexTypeOptions[0].value;
}
return indexTypeOptions[0].value;
}
let isUnique = constraintInfo?.isUnique;
let indexTypeValue = getIndexTypeValue(constraintInfo, driver?.dialect?.indexTypes);
function getExtractConstraintProps() {
const indexTypeOptions = driver?.dialect?.indexTypes;
if (indexTypeOptions?.length) {
const selected = indexTypeOptions.find(option => option.value === indexTypeValue) || indexTypeOptions[0];
let nextIndexType = selected?.indexType;
if (selected?.isUnique) {
nextIndexType = undefined;
} else if (!nextIndexType) {
const currentIndexType = constraintInfo?.indexType?.toString()?.toUpperCase();
if (currentIndexType && currentIndexType !== 'FULLTEXT') {
nextIndexType = constraintInfo?.indexType;
}
}
return {
isUnique: !!selected?.isUnique,
indexType: nextIndexType,
filterDefinition,
};
}
return {
isUnique,
filterDefinition,
@@ -24,6 +56,10 @@
let filterDefinition = constraintInfo?.filterDefinition;
$: isReadOnly = !setTableInfo;
$: indexTypeOptions = driver?.dialect?.indexTypes;
$: if (!indexTypeValue && indexTypeOptions?.length) {
indexTypeValue = getIndexTypeValue(constraintInfo, indexTypeOptions);
}
</script>
<ColumnsConstraintEditorModal
@@ -61,9 +97,29 @@
</svelte:fragment>
<svelte:fragment slot="constraintProps">
<div class="largeFormMarker">
<div class="row">
<CheckboxField checked={isUnique} on:change={e => (isUnique = e.target.checked)} disabled={isReadOnly} /> {_t('indexEditor.isUnique', { defaultMessage: 'Is unique index' })}
</div>
{#if indexTypeOptions?.length}
<div class="row">
<div class="label col-3">{_t('indexEditor.indexType', { defaultMessage: 'Index type' })}</div>
<div class="col-9">
<SelectField
value={indexTypeValue}
isNative
disabled={isReadOnly}
options={indexTypeOptions.map(option => ({
label: option.label,
value: option.value,
}))}
on:change={e => {
if (e.detail) indexTypeValue = e.detail;
}}
/>
</div>
</div>
{:else}
<div class="row">
<CheckboxField checked={isUnique} on:change={e => (isUnique = e.target.checked)} disabled={isReadOnly} /> {_t('indexEditor.isUnique', { defaultMessage: 'Is unique index' })}
</div>
{/if}
</div>
<div class="largeFormMarker">
@@ -154,6 +154,14 @@
});
}
function getIndexTypeLabel(row) {
const indexType = row?.indexType?.toString()?.toUpperCase();
if (indexType === 'FULLTEXT') return 'FULLTEXT';
if (row?.isUnique) return 'UNIQUE';
if (indexType) return indexType;
return 'INDEX';
}
$: columns = tableInfo?.columns;
$: foreignKeys = tableInfo?.foreignKeys;
$: dependencies = tableInfo?.dependencies;
@@ -357,10 +365,9 @@
sortable: true,
},
{
fieldName: 'unique',
header: _t('tableEditor.unique', { defaultMessage: 'Unique' }),
fieldName: 'indexType',
header: _t('tableEditor.indexType', { defaultMessage: 'Type' }),
slot: 1,
sortable: true,
},
isWritable
? {
@@ -372,11 +379,7 @@
>
<svelte:fragment slot="name" let:row><ConstraintLabel {...row} /></svelte:fragment>
<svelte:fragment slot="0" let:row>{row?.columns.map(x => x.columnName).join(', ')}</svelte:fragment>
<svelte:fragment slot="1" let:row
>{row?.isUnique
? _t('tableEditor.yes', { defaultMessage: 'YES' })
: _t('tableEditor.no', { defaultMessage: 'NO' })}</svelte:fragment
>
<svelte:fragment slot="1" let:row>{getIndexTypeLabel(row)}</svelte:fragment>
<svelte:fragment slot="2" let:row
><Link
onClick={e => {
@@ -100,6 +100,17 @@ class Dumper extends SqlDumper {
this.put('^select ^last_insert_id()');
}
indexType(ix) {
if (ix.isUnique) {
this.put(' ^unique');
return;
}
if (ix.indexType?.toLowerCase() == 'fulltext') {
this.put(' ^fulltext');
return;
}
}
callableTemplate(func) {
const parameters = (func.parameters || []).filter(x => x.parameterMode != 'RETURN');
@@ -88,6 +88,12 @@ const dialect = {
'year',
],
indexTypes: [
{ value: 'normal', label: 'Normal' },
{ value: 'unique', label: 'Unique', isUnique: true },
{ value: 'fulltext', label: 'Fulltext', indexType: 'FULLTEXT' },
],
createColumnViewExpression(columnName, dataType, source, alias) {
if (dataType && spatialTypes.includes(dataType.toUpperCase())) {
return {