feat(types): add database type help
This commit is contained in:
30 files changed
+2006
-79
No files matched your search
@@ -11,6 +11,7 @@ import { Input } from "@/components/ui/input";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||
import { OptionHelpPanel } from "@/components/ui/option-help-panel";
|
||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
||||
import { Switch } from "@/components/ui/switch";
|
||||
import CustomContextMenu, { type ContextMenuItem } from "@/components/ui/CustomContextMenu.vue";
|
||||
@@ -33,6 +34,8 @@ import { useEditorFontFamilyStyle } from "@/composables/useEditorFontFamilyStyle
|
||||
import { useToast } from "@/composables/useToast";
|
||||
import { redisKeySearchPattern } from "@/lib/redis/redisKeyPattern";
|
||||
import { REDIS_SCAN_PAGE_SIZE_DEFAULT } from "@/lib/redis/redisKeyPattern";
|
||||
import { getRedisCreateKeyTypeHelp, redisCreateKeyTypeHelpOptionOnOpen, shouldActivateRedisCreateKeyTypeHelpOnFocus } from "@/lib/redis/redisCreateKeyTypeHelp";
|
||||
import { optionHelpPanelOffsetTop } from "@/lib/common/optionHelpPanelOffset";
|
||||
|
||||
const { t } = useI18n();
|
||||
const { toast } = useToast();
|
||||
@@ -100,6 +103,12 @@ const createKeyRawMode = ref(false);
|
||||
const createKeyEntryId = ref("*");
|
||||
const jsonModuleAvailable = ref<boolean | null>(null);
|
||||
const checkingJsonModule = ref(false);
|
||||
const activeCreateKeyTypeHelp = ref<RedisCreateKeyType>();
|
||||
const createKeyTypeKeyboardNavigation = ref(false);
|
||||
const createKeyTypeOpenedByArrow = ref(false);
|
||||
const createKeyTypeListCard = ref<HTMLElement>();
|
||||
const createKeyTypeHelpPanel = ref<{ element?: HTMLElement }>();
|
||||
const createKeyTypeHelpOffsetTop = ref(0);
|
||||
let nextEntryId = 0;
|
||||
let searchRequestId = 0;
|
||||
let redisBrowserIsActive = true;
|
||||
@@ -166,6 +175,67 @@ const createKeyTypeOptions = computed<{ value: RedisCreateKeyType; label: string
|
||||
{ value: "stream", label: "Stream" },
|
||||
{ value: "json", label: "JSON" },
|
||||
]);
|
||||
function createKeyTypeTooltip(type: RedisCreateKeyType): string | undefined {
|
||||
const help = getRedisCreateKeyTypeHelp(type);
|
||||
return help ? t(`redis.createKeyTypeHelp.${help.key}`) : undefined;
|
||||
}
|
||||
const activeCreateKeyTypeHelpContent = computed(() => (activeCreateKeyTypeHelp.value ? createKeyTypeTooltip(activeCreateKeyTypeHelp.value) : undefined));
|
||||
|
||||
function activateCreateKeyTypeHelp(type: RedisCreateKeyType) {
|
||||
activeCreateKeyTypeHelp.value = createKeyTypeTooltip(type) ? type : undefined;
|
||||
}
|
||||
|
||||
function onCreateKeyTypeSelectOpen(open: boolean) {
|
||||
if (open) {
|
||||
activeCreateKeyTypeHelp.value = redisCreateKeyTypeHelpOptionOnOpen(createKeyType.value);
|
||||
return;
|
||||
}
|
||||
activeCreateKeyTypeHelp.value = undefined;
|
||||
createKeyTypeKeyboardNavigation.value = false;
|
||||
createKeyTypeOpenedByArrow.value = false;
|
||||
}
|
||||
|
||||
function onCreateKeyTypeTriggerKeydown(event: KeyboardEvent) {
|
||||
if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
|
||||
createKeyTypeOpenedByArrow.value = true;
|
||||
createKeyTypeKeyboardNavigation.value = true;
|
||||
}
|
||||
|
||||
function onCreateKeyTypeSelectKeydown(event: KeyboardEvent) {
|
||||
if (event.key !== "ArrowDown" && event.key !== "ArrowUp") return;
|
||||
createKeyTypeKeyboardNavigation.value = true;
|
||||
}
|
||||
|
||||
function onCreateKeyTypeOptionFocus(type: RedisCreateKeyType) {
|
||||
if (!shouldActivateRedisCreateKeyTypeHelpOnFocus({ openedByArrow: createKeyTypeOpenedByArrow.value, keyboardNavigating: createKeyTypeKeyboardNavigation.value })) return;
|
||||
activateCreateKeyTypeHelp(type);
|
||||
createKeyTypeOpenedByArrow.value = false;
|
||||
createKeyTypeKeyboardNavigation.value = false;
|
||||
}
|
||||
|
||||
async function updateCreateKeyTypeHelpOffset() {
|
||||
if (!activeCreateKeyTypeHelp.value) {
|
||||
createKeyTypeHelpOffsetTop.value = 0;
|
||||
return;
|
||||
}
|
||||
await nextTick();
|
||||
const card = createKeyTypeListCard.value;
|
||||
const panel = createKeyTypeHelpPanel.value?.element;
|
||||
const option = card?.querySelector<HTMLElement>(`[data-option-help-value="${activeCreateKeyTypeHelp.value}"]`);
|
||||
if (!card || !panel || !option) {
|
||||
createKeyTypeHelpOffsetTop.value = 0;
|
||||
return;
|
||||
}
|
||||
createKeyTypeHelpOffsetTop.value = optionHelpPanelOffsetTop({
|
||||
activeItemTop: option.getBoundingClientRect().top - card.getBoundingClientRect().top,
|
||||
listCardHeight: card.clientHeight,
|
||||
panelHeight: panel.clientHeight,
|
||||
});
|
||||
}
|
||||
|
||||
watch(activeCreateKeyTypeHelp, () => {
|
||||
void updateCreateKeyTypeHelpOffset();
|
||||
});
|
||||
const visibleRows = computed(() => {
|
||||
const rows = useFlatKeySearchRows.value ? flatKeys.value.map((key) => redisKeyToFlatTreeRow(key, props.db)) : flattenVisibleRedisKeyTree(treeKeys.value, expandedGroupIds.value);
|
||||
return rows.map((row) => ({
|
||||
@@ -632,6 +702,9 @@ function resetCreateKeyForm() {
|
||||
createKeyEntryId.value = "*";
|
||||
jsonModuleAvailable.value = null;
|
||||
checkingJsonModule.value = false;
|
||||
activeCreateKeyTypeHelp.value = undefined;
|
||||
createKeyTypeKeyboardNavigation.value = false;
|
||||
createKeyTypeOpenedByArrow.value = false;
|
||||
resetEntries();
|
||||
}
|
||||
|
||||
@@ -640,6 +713,9 @@ function onCreateKeyTypeChange(type: any) {
|
||||
createKeyRawMode.value = false;
|
||||
jsonModuleAvailable.value = null;
|
||||
checkingJsonModule.value = false;
|
||||
activeCreateKeyTypeHelp.value = undefined;
|
||||
createKeyTypeKeyboardNavigation.value = false;
|
||||
createKeyTypeOpenedByArrow.value = false;
|
||||
resetEntries();
|
||||
if (createKeyType.value === "json") {
|
||||
createKeyError.value = "";
|
||||
@@ -1267,14 +1343,19 @@ defineExpose({ focusSearch });
|
||||
|
||||
<label class="grid gap-1.5 text-xs font-medium">
|
||||
<span>{{ t("redis.createKeyType") }}</span>
|
||||
<Select :model-value="createKeyType" @update:model-value="onCreateKeyTypeChange">
|
||||
<SelectTrigger class="h-8 text-xs">
|
||||
<Select :model-value="createKeyType" @update:open="onCreateKeyTypeSelectOpen" @update:model-value="onCreateKeyTypeChange">
|
||||
<SelectTrigger class="h-8 text-xs" @keydown.capture="onCreateKeyTypeTriggerKeydown">
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="option in createKeyTypeOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</SelectItem>
|
||||
<SelectContent data-naked-surface class="w-auto max-w-[calc(100vw-1rem)] border-0 bg-transparent p-0 shadow-none ring-0">
|
||||
<div class="flex min-w-0 flex-col gap-2 sm:flex-row sm:items-start" @keydown.capture="onCreateKeyTypeSelectKeydown">
|
||||
<div ref="createKeyTypeListCard" class="min-w-40 rounded-md border bg-popover p-1 shadow-md" @scroll="updateCreateKeyTypeHelpOffset">
|
||||
<SelectItem v-for="option in createKeyTypeOptions" :key="option.value" :value="option.value" :data-option-help-value="option.value" @pointerenter="activateCreateKeyTypeHelp(option.value)" @focus="onCreateKeyTypeOptionFocus(option.value)">
|
||||
{{ option.label }}
|
||||
</SelectItem>
|
||||
</div>
|
||||
<OptionHelpPanel v-if="activeCreateKeyTypeHelpContent" ref="createKeyTypeHelpPanel" :content="activeCreateKeyTypeHelpContent" :offset-top="createKeyTypeHelpOffsetTop" />
|
||||
</div>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</label>
|
||||
|
||||
@@ -27,6 +27,9 @@ import { queryTimeoutSecsForConnection } from "@/lib/sql/queryTimeout";
|
||||
import { safeLocalStorageGet, safeLocalStorageSet } from "@/lib/backend/safeStorage";
|
||||
import { type BuildTableStructureChangeSqlOptions, type EditableStructureColumn, type EditableStructureForeignKey, type EditableStructureIndex, type EditableStructureTrigger } from "@/lib/table/tableStructureEditorSql";
|
||||
import { PRESET_FIELDS_TEMPLATE_ID, createTableColumnTemplateDrafts } from "@/lib/table/tableColumnTemplates";
|
||||
import { getMysqlDataTypeHelp } from "@/lib/table/mysqlDataTypeHelp";
|
||||
import { getPostgresDataTypeHelp } from "@/lib/table/postgresDataTypeHelp";
|
||||
import { getSqliteDataTypeHelp } from "@/lib/table/sqliteDataTypeHelp";
|
||||
import { getTableMetadataCapabilities, firstStructureMetadataTab, isStructureMetadataTabSupported } from "@/lib/table/tableMetadataCapabilities";
|
||||
import { canAddTableStructureColumn, getTableStructureCapabilities, hasLocalTableColumnOrderChange, isPhysicalTableColumnOrderChange, supportsLocalTableColumnReorder } from "@/lib/table/tableStructureCapabilities";
|
||||
import { orderedColumnIndexes, uniqueDataGridColumnOrderKeys } from "@/lib/dataGrid/dataGridColumnOrder";
|
||||
@@ -43,6 +46,7 @@ import {
|
||||
createIndexDrafts,
|
||||
createTriggerDrafts,
|
||||
dataTypeLengthInputValue,
|
||||
defaultNewColumnDataType,
|
||||
generateIndexName,
|
||||
generateUniqueIndexName,
|
||||
getColumnEditorControls,
|
||||
@@ -599,6 +603,7 @@ function onIndexColResize(e: MouseEvent, col: number) {
|
||||
|
||||
const connection = computed(() => (props.connectionId ? store.getConfig(props.connectionId) : undefined));
|
||||
const databaseType = computed(() => tableStructureDatabaseTypeForConnection(connection.value));
|
||||
const usesMysql8SafeDefaults = computed(() => databaseType.value === "mysql" && connection.value?.db_type === "mysql" && connection.value.driver_profile === "mysql");
|
||||
const structureCapabilities = computed(() => getTableStructureCapabilities(databaseType.value, connection.value?.db_type));
|
||||
const tableMetadataCapabilities = computed(() => getTableMetadataCapabilities(databaseType.value));
|
||||
const structureDialect = computed(() => structureCapabilities.value.dialect);
|
||||
@@ -1008,6 +1013,32 @@ function mergeDataTypeOptions(primary: readonly string[], fallback: readonly str
|
||||
return result;
|
||||
}
|
||||
|
||||
function mysqlDataTypeTooltip(option: string): string | undefined {
|
||||
if (databaseType.value !== "mysql") return undefined;
|
||||
const product = connection.value?.driver_profile === "mariadb" ? "mariadb" : connection.value?.driver_profile === "mysql" ? "mysql" : undefined;
|
||||
const help = getMysqlDataTypeHelp(option, { product });
|
||||
return help ? [help.key, ...(help.warningKeys ?? [])].map((key) => t(`structureEditor.mysqlDataTypeHelp.${key}`)).join("\n\n") : undefined;
|
||||
}
|
||||
|
||||
function postgresDataTypeTooltip(option: string): string | undefined {
|
||||
if (databaseType.value !== "postgres") return undefined;
|
||||
const help = getPostgresDataTypeHelp(option);
|
||||
return help ? t(`structureEditor.postgresDataTypeHelp.${help.key}`) : undefined;
|
||||
}
|
||||
|
||||
function sqliteDataTypeTooltip(option: string): string | undefined {
|
||||
if (databaseType.value !== "sqlite") return undefined;
|
||||
const help = getSqliteDataTypeHelp(option);
|
||||
return help ? t(`structureEditor.sqliteDataTypeHelp.${help.key}`) : undefined;
|
||||
}
|
||||
|
||||
function dataTypeTooltip(option: string): string | undefined {
|
||||
if (databaseType.value === "mysql") return mysqlDataTypeTooltip(option);
|
||||
if (databaseType.value === "postgres") return postgresDataTypeTooltip(option);
|
||||
if (databaseType.value === "sqlite") return sqliteDataTypeTooltip(option);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async function loadDynamicDataTypeOptions() {
|
||||
const requestId = ++dataTypeOptionsRequestId;
|
||||
const connectionId = props.connectionId;
|
||||
@@ -1278,7 +1309,7 @@ async function refreshStructureAfterSave(scope: StructureRefreshScope) {
|
||||
async function addColumn() {
|
||||
if (!canAddColumn.value) return;
|
||||
activeTab.value = "columns";
|
||||
const dataType = databaseType.value === "manticoresearch" ? combineDataTypeForDatabase(databaseType.value, dataTypeOptions.value[0] ?? "text", getDefaultLengthForType(databaseType.value, dataTypeOptions.value[0] ?? "text")) : "varchar(255)";
|
||||
const dataType = defaultNewColumnDataType(databaseType.value, dataTypeOptions.value);
|
||||
const column: EditableStructureColumn = {
|
||||
id: `new:${uuid()}`,
|
||||
name: "",
|
||||
@@ -1517,7 +1548,7 @@ function updateColumnDataType(column: EditableStructureColumn, baseType: string)
|
||||
if (!column.enumValues?.length) column.enumValues = [""];
|
||||
column.dataType = mysqlEnumDataType(column.enumValues);
|
||||
} else {
|
||||
column.dataType = combineDataTypeForDatabase(databaseType.value, baseType, getDefaultLengthForType(databaseType.value, baseType));
|
||||
column.dataType = combineDataTypeForDatabase(databaseType.value, baseType, getDefaultLengthForType(databaseType.value, baseType, { omitMysqlDeprecatedDefaults: usesMysql8SafeDefaults.value }));
|
||||
}
|
||||
syncSqlServerIdentityForDataType(column);
|
||||
syncDamengIdentityForDataType(column);
|
||||
@@ -2567,6 +2598,7 @@ watch([activeTab, ddlLoading], ([tab, loading]) => {
|
||||
:empty-text="t('structureEditor.noMatchingType')"
|
||||
:loading-text="t('common.loading')"
|
||||
:allow-custom="true"
|
||||
:option-tooltip="dataTypeTooltip"
|
||||
:trigger-class="[structureMonoControlClass, 'w-full']"
|
||||
@update:model-value="(v: string) => updateColumnDataType(column, v)"
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import { OPTION_HELP_PANEL_CLASS } from "./optionHelpPanel";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
content: string;
|
||||
offsetTop?: number;
|
||||
}>(),
|
||||
{ offsetTop: 0 },
|
||||
);
|
||||
|
||||
const element = ref<HTMLElement>();
|
||||
defineExpose({ element });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside ref="element" data-slot="option-help-panel" role="note" :class="OPTION_HELP_PANEL_CLASS" :style="{ '--option-help-offset': `${offsetTop}px` }">
|
||||
{{ content }}
|
||||
</aside>
|
||||
</template>
|
||||
@@ -0,0 +1 @@
|
||||
export { default as OptionHelpPanel } from "./OptionHelpPanel.vue";
|
||||
@@ -0,0 +1,13 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { OPTION_HELP_PANEL_CLASS } from "./optionHelpPanel";
|
||||
|
||||
describe("OptionHelpPanel layout", () => {
|
||||
it("keeps the panel compact at the top and scrolls long help content", () => {
|
||||
expect(OPTION_HELP_PANEL_CLASS).toContain("self-start");
|
||||
expect(OPTION_HELP_PANEL_CLASS).toContain("max-h-64");
|
||||
expect(OPTION_HELP_PANEL_CLASS).toContain("overflow-y-auto");
|
||||
expect(OPTION_HELP_PANEL_CLASS).toContain("rounded-md");
|
||||
expect(OPTION_HELP_PANEL_CLASS).toContain("shadow-md");
|
||||
expect(OPTION_HELP_PANEL_CLASS).toContain("sm:mt-[var(--option-help-offset)]");
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
export const OPTION_HELP_PANEL_CLASS = "w-full shrink-0 self-start max-h-64 overflow-y-auto rounded-md border bg-popover px-3 py-2 text-xs leading-relaxed text-muted-foreground shadow-md sm:mt-[var(--option-help-offset)] sm:w-[min(26rem,calc(100vw-5rem))]";
|
||||
@@ -5,9 +5,12 @@ import { Check, ChevronDown, Search, X } from "@lucide/vue";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import type { ButtonVariants } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { OptionHelpPanel } from "@/components/ui/option-help-panel";
|
||||
import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover";
|
||||
import { filterDatabaseOptions } from "@/lib/database/databaseOptionSearch";
|
||||
import { cn } from "@/lib/common/utils";
|
||||
import { optionHelpPanelOffsetTop } from "@/lib/common/optionHelpPanelOffset";
|
||||
import { SEARCHABLE_SELECT_HELP_PANEL_ALIGN, searchableSelectKeyboardTooltipOption, searchableSelectSelectedOrFirstHelpOption } from "@/lib/common/searchableSelectTooltip";
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
@@ -26,6 +29,7 @@ const props = withDefaults(
|
||||
contentClass?: HTMLAttributes["class"];
|
||||
itemClass?: HTMLAttributes["class"];
|
||||
displayName?: (option: string) => string;
|
||||
optionTooltip?: (option: string) => string | undefined;
|
||||
normalizeCustom?: (value: string) => string;
|
||||
clearable?: boolean;
|
||||
}>(),
|
||||
@@ -38,6 +42,7 @@ const props = withDefaults(
|
||||
triggerVariant: "ghost",
|
||||
triggerIconClass: "h-3 w-3",
|
||||
displayName: (option: string) => option,
|
||||
optionTooltip: () => undefined,
|
||||
normalizeCustom: (value: string) => value,
|
||||
},
|
||||
);
|
||||
@@ -57,7 +62,11 @@ const open = ref(false);
|
||||
const searchText = ref("");
|
||||
const searchInput = ref<InstanceType<typeof Input>>();
|
||||
const listContainer = ref<HTMLDivElement>();
|
||||
const listCard = ref<HTMLElement>();
|
||||
const helpPanel = ref<{ element?: HTMLElement }>();
|
||||
const highlightIndex = ref(-1);
|
||||
const activeHelpOption = ref<string>();
|
||||
const helpPanelOffsetTop = ref(0);
|
||||
|
||||
const selectedLabel = computed(() => {
|
||||
if (!props.modelValue && !props.options.includes("")) return props.placeholder;
|
||||
@@ -86,6 +95,7 @@ async function scrollHighlightedOptionIntoView() {
|
||||
const buttons = container.querySelectorAll("button");
|
||||
const target = buttons[highlightIndex.value];
|
||||
target?.scrollIntoView({ block: "nearest" });
|
||||
void updateHelpPanelOffset();
|
||||
}
|
||||
|
||||
function highlightAndScrollSelectedOption() {
|
||||
@@ -93,33 +103,79 @@ function highlightAndScrollSelectedOption() {
|
||||
void scrollHighlightedOptionIntoView();
|
||||
}
|
||||
|
||||
function activateInitialHelpOption() {
|
||||
activeHelpOption.value = searchableSelectSelectedOrFirstHelpOption(filteredOptions.value, props.modelValue, props.optionTooltip);
|
||||
}
|
||||
|
||||
watch(open, async (value) => {
|
||||
emit("update:open", value);
|
||||
if (!value) {
|
||||
searchText.value = "";
|
||||
highlightIndex.value = -1;
|
||||
activeHelpOption.value = undefined;
|
||||
return;
|
||||
}
|
||||
await nextTick();
|
||||
const input = searchInput.value?.$el as HTMLInputElement | undefined;
|
||||
input?.focus();
|
||||
highlightAndScrollSelectedOption();
|
||||
activateInitialHelpOption();
|
||||
});
|
||||
|
||||
watch(searchText, () => {
|
||||
highlightIndex.value = 0;
|
||||
activeHelpOption.value = searchableSelectKeyboardTooltipOption(filteredOptions.value, 0, props.optionTooltip);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => [props.modelValue, props.options],
|
||||
() => {
|
||||
if (!open.value || searchText.value) return;
|
||||
activeHelpOption.value = undefined;
|
||||
highlightAndScrollSelectedOption();
|
||||
activateInitialHelpOption();
|
||||
},
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
watch(highlightIndex, scrollHighlightedOptionIntoView);
|
||||
watch([highlightIndex, filteredOptions], () => {
|
||||
void scrollHighlightedOptionIntoView();
|
||||
});
|
||||
|
||||
const activeHelpContent = computed(() => (activeHelpOption.value ? props.optionTooltip(activeHelpOption.value) : undefined));
|
||||
|
||||
function activateHelpForHighlightedOption() {
|
||||
activeHelpOption.value = searchableSelectKeyboardTooltipOption(filteredOptions.value, highlightIndex.value, props.optionTooltip);
|
||||
}
|
||||
|
||||
function activateHelpForOption(option: string) {
|
||||
activeHelpOption.value = props.optionTooltip(option) ? option : undefined;
|
||||
}
|
||||
|
||||
async function updateHelpPanelOffset() {
|
||||
if (!activeHelpOption.value) {
|
||||
helpPanelOffsetTop.value = 0;
|
||||
return;
|
||||
}
|
||||
await nextTick();
|
||||
const card = listCard.value;
|
||||
const panel = helpPanel.value?.element;
|
||||
const optionIndex = filteredOptions.value.indexOf(activeHelpOption.value);
|
||||
const option = optionIndex >= 0 ? listContainer.value?.querySelectorAll("button")[optionIndex] : undefined;
|
||||
if (!card || !panel || !option) {
|
||||
helpPanelOffsetTop.value = 0;
|
||||
return;
|
||||
}
|
||||
helpPanelOffsetTop.value = optionHelpPanelOffsetTop({
|
||||
activeItemTop: option.getBoundingClientRect().top - card.getBoundingClientRect().top,
|
||||
listCardHeight: card.clientHeight,
|
||||
panelHeight: panel.clientHeight,
|
||||
});
|
||||
}
|
||||
|
||||
watch([activeHelpOption, filteredOptions], () => {
|
||||
void updateHelpPanelOffset();
|
||||
});
|
||||
|
||||
function selectOption(option: string) {
|
||||
emit("update:modelValue", option);
|
||||
@@ -146,11 +202,13 @@ function handleKeydown(event: KeyboardEvent) {
|
||||
const total = optionCount();
|
||||
if (total === 0) return;
|
||||
highlightIndex.value = highlightIndex.value < total - 1 ? highlightIndex.value + 1 : 0;
|
||||
activateHelpForHighlightedOption();
|
||||
} else if (event.key === "ArrowUp") {
|
||||
event.preventDefault();
|
||||
const total = optionCount();
|
||||
if (total === 0) return;
|
||||
highlightIndex.value = highlightIndex.value > 0 ? highlightIndex.value - 1 : total - 1;
|
||||
activateHelpForHighlightedOption();
|
||||
} else if (event.key === "Enter") {
|
||||
if (highlightIndex.value < 0 || highlightIndex.value >= optionCount()) return;
|
||||
event.preventDefault();
|
||||
@@ -176,76 +234,84 @@ function handleKeydown(event: KeyboardEvent) {
|
||||
<ChevronDown v-else :class="cn('shrink-0 opacity-60', triggerIconClass)" />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent align="end" :class="cn('w-52 gap-1 p-1.5', contentClass)">
|
||||
<div class="relative rounded-sm border bg-background">
|
||||
<Search class="pointer-events-none absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground" />
|
||||
<span v-if="!searchText" class="pointer-events-none absolute left-[25px] top-1/2 -translate-y-1/2 text-sm text-muted-foreground">{{ searchPlaceholder }}</span>
|
||||
<Input ref="searchInput" :model-value="searchText" class="h-6 border-0 pl-6 pr-2 text-sm caret-foreground shadow-none focus-visible:ring-0" @update:model-value="(value) => (searchText = String(value))" @keydown="handleKeydown" />
|
||||
</div>
|
||||
<div ref="listContainer" class="max-h-64 overflow-y-auto py-1">
|
||||
<div v-if="loading" class="px-2 py-2 text-sm text-muted-foreground">
|
||||
{{ loadingText }}
|
||||
</div>
|
||||
<template v-else-if="filteredOptions.length">
|
||||
<button
|
||||
v-for="(option, index) in filteredOptions"
|
||||
:key="option"
|
||||
type="button"
|
||||
:title="optionTitle(option)"
|
||||
:class="
|
||||
cn(
|
||||
'flex h-8 w-full min-w-0 items-center gap-2 rounded-sm px-2 text-left text-sm hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none',
|
||||
props.itemClass,
|
||||
index === highlightIndex && 'bg-accent text-accent-foreground',
|
||||
)
|
||||
"
|
||||
@click="selectOption(option)"
|
||||
>
|
||||
<Check :class="cn('h-3.5 w-3.5 shrink-0', option === modelValue ? 'opacity-100' : 'opacity-0')" />
|
||||
<slot name="option-label" :option="option" :label="displayName?.(option)">
|
||||
<span class="truncate">{{ displayName?.(option) }}</span>
|
||||
</slot>
|
||||
</button>
|
||||
<button
|
||||
v-if="canSelectCustom"
|
||||
type="button"
|
||||
:title="customOptionValue"
|
||||
:class="
|
||||
cn(
|
||||
'flex h-8 w-full min-w-0 items-center gap-2 rounded-sm px-2 text-left text-sm hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none',
|
||||
props.itemClass,
|
||||
filteredOptions.length === highlightIndex && 'bg-accent text-accent-foreground',
|
||||
)
|
||||
"
|
||||
@click="selectCustomOption"
|
||||
>
|
||||
<Check class="h-3.5 w-3.5 shrink-0 opacity-0" />
|
||||
<slot name="custom-option-label" :value="customOptionValue">
|
||||
<span class="truncate">{{ customOptionValue }}</span>
|
||||
</slot>
|
||||
</button>
|
||||
</template>
|
||||
<button
|
||||
v-else-if="canSelectCustom"
|
||||
type="button"
|
||||
:title="customOptionValue"
|
||||
:class="
|
||||
cn(
|
||||
'flex h-8 w-full min-w-0 items-center gap-2 rounded-sm px-2 text-left text-sm hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none',
|
||||
props.itemClass,
|
||||
0 === highlightIndex && 'bg-accent text-accent-foreground',
|
||||
)
|
||||
"
|
||||
@click="selectCustomOption"
|
||||
>
|
||||
<Check class="h-3.5 w-3.5 shrink-0 opacity-0" />
|
||||
<slot name="custom-option-label" :value="customOptionValue">
|
||||
<span class="truncate">{{ customOptionValue }}</span>
|
||||
</slot>
|
||||
</button>
|
||||
<div v-else class="px-2 py-2 text-sm text-muted-foreground">
|
||||
{{ emptyText }}
|
||||
<PopoverContent :align="SEARCHABLE_SELECT_HELP_PANEL_ALIGN" :class="cn('w-auto max-w-[calc(100vw-1rem)] border-0 bg-transparent p-0 shadow-none ring-0', contentClass)">
|
||||
<div class="flex min-w-0 flex-col gap-2 sm:flex-row sm:items-start">
|
||||
<div ref="listCard" class="w-52 shrink-0 rounded-md border bg-popover p-1.5 shadow-md">
|
||||
<div class="relative rounded-sm border bg-background">
|
||||
<Search class="pointer-events-none absolute left-2 top-1/2 h-3 w-3 -translate-y-1/2 text-muted-foreground" />
|
||||
<span v-if="!searchText" class="pointer-events-none absolute left-[25px] top-1/2 -translate-y-1/2 text-sm text-muted-foreground">{{ searchPlaceholder }}</span>
|
||||
<Input ref="searchInput" :model-value="searchText" class="h-6 border-0 pl-6 pr-2 text-sm caret-foreground shadow-none focus-visible:ring-0" @update:model-value="(value) => (searchText = String(value))" @keydown="handleKeydown" />
|
||||
</div>
|
||||
<div ref="listContainer" class="max-h-64 overflow-y-auto py-1" @scroll="updateHelpPanelOffset">
|
||||
<div v-if="loading" class="px-2 py-2 text-sm text-muted-foreground">
|
||||
{{ loadingText }}
|
||||
</div>
|
||||
<template v-else-if="filteredOptions.length">
|
||||
<button
|
||||
v-for="(option, index) in filteredOptions"
|
||||
:key="option"
|
||||
type="button"
|
||||
:title="optionTooltip(option) ? undefined : optionTitle(option)"
|
||||
:class="
|
||||
cn(
|
||||
'flex h-8 w-full min-w-0 items-center gap-2 rounded-sm px-2 text-left text-sm hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none',
|
||||
props.itemClass,
|
||||
index === highlightIndex && 'bg-accent text-accent-foreground',
|
||||
)
|
||||
"
|
||||
@pointerenter="activateHelpForOption(option)"
|
||||
@click="selectOption(option)"
|
||||
>
|
||||
<Check :class="cn('h-3.5 w-3.5 shrink-0', option === modelValue ? 'opacity-100' : 'opacity-0')" />
|
||||
<slot name="option-label" :option="option" :label="displayName?.(option)">
|
||||
<span class="truncate">{{ displayName?.(option) }}</span>
|
||||
</slot>
|
||||
</button>
|
||||
<button
|
||||
v-if="canSelectCustom"
|
||||
type="button"
|
||||
:title="customOptionValue"
|
||||
:class="
|
||||
cn(
|
||||
'flex h-8 w-full min-w-0 items-center gap-2 rounded-sm px-2 text-left text-sm hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none',
|
||||
props.itemClass,
|
||||
filteredOptions.length === highlightIndex && 'bg-accent text-accent-foreground',
|
||||
)
|
||||
"
|
||||
@pointerenter="activateHelpForOption(customOptionValue)"
|
||||
@click="selectCustomOption"
|
||||
>
|
||||
<Check class="h-3.5 w-3.5 shrink-0 opacity-0" />
|
||||
<slot name="custom-option-label" :value="customOptionValue">
|
||||
<span class="truncate">{{ customOptionValue }}</span>
|
||||
</slot>
|
||||
</button>
|
||||
</template>
|
||||
<button
|
||||
v-else-if="canSelectCustom"
|
||||
type="button"
|
||||
:title="customOptionValue"
|
||||
:class="
|
||||
cn(
|
||||
'flex h-8 w-full min-w-0 items-center gap-2 rounded-sm px-2 text-left text-sm hover:bg-accent hover:text-accent-foreground focus-visible:bg-accent focus-visible:text-accent-foreground focus-visible:outline-none',
|
||||
props.itemClass,
|
||||
0 === highlightIndex && 'bg-accent text-accent-foreground',
|
||||
)
|
||||
"
|
||||
@pointerenter="activateHelpForOption(customOptionValue)"
|
||||
@click="selectCustomOption"
|
||||
>
|
||||
<Check class="h-3.5 w-3.5 shrink-0 opacity-0" />
|
||||
<slot name="custom-option-label" :value="customOptionValue">
|
||||
<span class="truncate">{{ customOptionValue }}</span>
|
||||
</slot>
|
||||
</button>
|
||||
<div v-else class="px-2 py-2 text-sm text-muted-foreground">
|
||||
{{ emptyText }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<OptionHelpPanel v-if="activeHelpContent" ref="helpPanel" :content="activeHelpContent" :offset-top="helpPanelOffsetTop" />
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
||||
@@ -1848,6 +1848,107 @@ export default {
|
||||
dataType: "Type",
|
||||
typePlaceholder: "Select or type...",
|
||||
noMatchingType: "No matching type",
|
||||
mysqlDataTypeHelp: {
|
||||
tinyint: "1-byte integer. Signed range: -128 to 127; unsigned range: 0 to 255.",
|
||||
smallint: "2-byte integer. Signed range: -32,768 to 32,767; unsigned range: 0 to 65,535.",
|
||||
mediumint: "3-byte integer. Signed range: -8,388,608 to 8,388,607; unsigned range: 0 to 16,777,215.",
|
||||
int: "4-byte integer. Signed range: -2,147,483,648 to 2,147,483,647; unsigned range: 0 to 4,294,967,295.",
|
||||
bigint: "8-byte integer. Signed range: -2^63 to 2^63-1; unsigned range: 0 to 2^64-1.",
|
||||
decimal: "Exact fixed-point number. MySQL supports precision up to 65 digits and scale up to 30 digits; MariaDB version limits can differ. Use it for money and other exact values.",
|
||||
float: "Single-precision approximate number. Use DECIMAL when exact decimal values are required.",
|
||||
double: "Double-precision approximate number. Use DECIMAL when exact decimal values are required.",
|
||||
real: "Alias for DOUBLE by default; REAL_AS_FLOAT SQL mode makes it an alias for FLOAT.",
|
||||
bit: "Bit field of 1 to 64 bits. The default width is 1 bit.",
|
||||
boolean: "Alias for TINYINT(1): 0 represents false and nonzero values represent true.",
|
||||
serial: "Convenience alias for BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE.",
|
||||
date: "Calendar date. Supported range: 1000-01-01 through 9999-12-31.",
|
||||
datetime: "Date and time without time-zone conversion. Supported range: 1000-01-01 00:00:00 through 9999-12-31 23:59:59; fractional-seconds precision is 0 to 6 (default 0).",
|
||||
timestamp: "Date and time converted between the session time zone and UTC. Supported range: 1970-01-01 00:00:01 UTC through 2038-01-19 03:14:07 UTC; fractional-seconds precision is 0 to 6 (default 0).",
|
||||
time: "Time of day or elapsed time. Supported range: -838:59:59 through 838:59:59; fractional-seconds precision is 0 to 6 (default 0).",
|
||||
year: "Year value. Four-digit values range from 1901 to 2155, plus 0000.",
|
||||
char: "Fixed-length character string, up to 255 characters. Stored values are right-padded with spaces.",
|
||||
varchar: "Variable-length character string. Its practical maximum is constrained by row size and character set.",
|
||||
tinytext: "Variable-length text, up to 255 bytes. Character set, row-size, and max_allowed_packet limits can impose lower practical limits.",
|
||||
text: "Variable-length text, up to 65,535 bytes. Character set, row-size, and max_allowed_packet limits can impose lower practical limits.",
|
||||
mediumtext: "Variable-length text, up to 16,777,215 bytes. Character set, row-size, and max_allowed_packet limits can impose lower practical limits.",
|
||||
longtext: "Variable-length text, up to 4,294,967,295 bytes (about 4 GiB). Character set, row-size, and max_allowed_packet limits can impose lower practical limits.",
|
||||
binary: "Fixed-length binary byte string, up to 255 bytes. Stored values are padded with 0x00.",
|
||||
varbinary: "Variable-length binary byte string. Its practical maximum is constrained by row size.",
|
||||
tinyblob: "Binary large object, up to 255 bytes. Row-size and max_allowed_packet limits can impose lower practical limits.",
|
||||
blob: "Binary large object, up to 65,535 bytes. Row-size and max_allowed_packet limits can impose lower practical limits.",
|
||||
mediumblob: "Binary large object, up to 16,777,215 bytes. Row-size and max_allowed_packet limits can impose lower practical limits.",
|
||||
longblob: "Binary large object, up to 4,294,967,295 bytes (about 4 GiB). Row-size and max_allowed_packet limits can impose lower practical limits.",
|
||||
enum: "One value selected from up to 65,535 declared literals. Changing literal order can change stored value semantics.",
|
||||
set: "Zero or more values selected from up to 64 declared members.",
|
||||
json: "Validated JSON document. Storage representation and JSON feature support depend on the connected server product and version; document size is also limited by max_allowed_packet.",
|
||||
jsonMysql: "MySQL's native binary JSON document type with JSON functions and validation. Document size is limited by max_allowed_packet.",
|
||||
jsonMariaDb: "MariaDB JSON is an alias for LONGTEXT with a JSON_VALID constraint and utf8mb4_bin collation; document size is limited by max_allowed_packet.",
|
||||
nationalCharacter: "National character string alias. In MySQL it uses the predefined national character set (utf8mb3, deprecated); explicitly choose utf8mb4 for new schemas.",
|
||||
geometry: "Spatial value of any supported geometry type. A MySQL InnoDB SPATIAL index requires a NOT NULL column with an SRID attribute.",
|
||||
point: "Spatial point with one coordinate pair. A MySQL InnoDB SPATIAL index requires a NOT NULL column with an SRID attribute.",
|
||||
linestring: "Spatial curve composed of one or more connected points. A MySQL InnoDB SPATIAL index requires a NOT NULL column with an SRID attribute.",
|
||||
polygon: "Spatial surface enclosed by one or more rings. A MySQL InnoDB SPATIAL index requires a NOT NULL column with an SRID attribute.",
|
||||
multipoint: "Collection of spatial points. A MySQL InnoDB SPATIAL index requires a NOT NULL column with an SRID attribute.",
|
||||
multilinestring: "Collection of spatial curves. A MySQL InnoDB SPATIAL index requires a NOT NULL column with an SRID attribute.",
|
||||
multipolygon: "Collection of spatial polygons. A MySQL InnoDB SPATIAL index requires a NOT NULL column with an SRID attribute.",
|
||||
geometrycollection: "Collection that can contain mixed spatial geometry types. A MySQL InnoDB SPATIAL index requires a NOT NULL column with an SRID attribute.",
|
||||
inet4: "MariaDB IPv4 address type (available from MariaDB 10.10); it stores an IPv4 address in four bytes.",
|
||||
inet6: "MariaDB-compatible address type for IPv6 and mapped IPv4 values.",
|
||||
uuid: "128-bit universally unique identifier type supplied by compatible servers.",
|
||||
vector: "Vector data type for similarity or machine-learning workloads; availability and dimensions depend on the server version.",
|
||||
xmltype: "XML data type supplied by compatible servers.",
|
||||
integerDisplayWidth: "MySQL integer display width (for example INT(11)) is deprecated and does not limit the stored range. Do not add it to new MySQL 8 schemas.",
|
||||
zerofill: "ZEROFILL is deprecated in MySQL 8. It also implicitly makes the column UNSIGNED; use explicit formatting in the application instead.",
|
||||
unsignedNumeric: "UNSIGNED for DECIMAL, FLOAT, or DOUBLE is deprecated in MySQL 8. Use a CHECK constraint when a nonnegative domain is required.",
|
||||
floatPrecision: "For FLOAT(p), p from 25 through 53 selects DOUBLE precision rather than FLOAT. Use DOUBLE explicitly when that is intended.",
|
||||
floatingPointDisplay: "FLOAT(M,D) and DOUBLE(M,D) are deprecated in MySQL 8. Use FLOAT/DOUBLE without display parameters, or DECIMAL(M,D) for exact values.",
|
||||
},
|
||||
postgresDataTypeHelp: {
|
||||
smallint: "2-byte signed integer, from -32,768 to 32,767.",
|
||||
integer: "4-byte signed integer, from -2,147,483,648 to 2,147,483,647.",
|
||||
bigint: "8-byte signed integer, from -2^63 to 2^63-1.",
|
||||
serial: "Convenience syntax that creates an integer column, sequence, NOT NULL, and nextval default. It is not unique by itself and sequence values can have gaps; prefer IDENTITY for new schemas.",
|
||||
numeric: "Exact decimal number with user-selected precision and scale. Use it for money and other values that must not use binary floating-point rounding.",
|
||||
real: "4-byte single-precision floating-point number. FLOAT(p) with p from 1 through 24 selects this type.",
|
||||
double: "8-byte double-precision floating-point number. FLOAT without precision is its alias; FLOAT(p) with p from 25 through 53 selects it.",
|
||||
money: "Locale-formatted currency amount. Its output and parsing depend on lc_monetary; NUMERIC is usually safer for portable financial data.",
|
||||
boolean: "Logical true, false, or null value. BOOL is an alias for BOOLEAN.",
|
||||
char: "Fixed-length blank-padded character string. CHARACTER is an alias; avoid it when trailing spaces are significant.",
|
||||
varchar: "Variable-length character string with an optional character limit. CHARACTER VARYING is an alias.",
|
||||
text: "Variable-length character string without a declared limit; it has the same storage behavior as VARCHAR without a limit.",
|
||||
bytea: "Variable-length binary string. Large values can be stored out of line by TOAST.",
|
||||
date: "Calendar date without a time of day or time zone.",
|
||||
time: "Time of day without a time zone, with optional fractional seconds precision.",
|
||||
timetz: "Time of day plus a UTC offset. It has limited usefulness and is generally discouraged; use timestamptz for an instant in time.",
|
||||
timestamp: "Date and time without time-zone conversion. Use it for local wall-clock values whose time zone is not part of the data.",
|
||||
timestamptz: "Instant in time stored internally in UTC and displayed in the session time zone. It does not retain the original named time zone.",
|
||||
interval: "Duration expressed in months, days, and microseconds. Calendar units are not fixed-length durations.",
|
||||
uuid: "128-bit universally unique identifier.",
|
||||
json: "JSON text preserved in the original input representation. Parsing is validated, but processing is generally slower than JSONB.",
|
||||
jsonb: "Decomposed binary JSON representation optimized for processing and indexing. It does not preserve insignificant whitespace, key order, or duplicate keys.",
|
||||
xml: "XML value with optional document or content validation depending on the XML option used.",
|
||||
bit: "Fixed- or variable-length bit string. BIT VARYING and VARBIT are variable-length aliases.",
|
||||
tsvector: "Normalized lexemes used as the searchable document side of PostgreSQL full-text search; commonly indexed with GIN or GiST.",
|
||||
tsquery: "Boolean full-text search query evaluated against a tsvector.",
|
||||
cidr: "IPv4 or IPv6 network with host bits constrained to zero.",
|
||||
inet: "IPv4 or IPv6 host address, optionally with a network mask; unlike CIDR, host bits may be nonzero.",
|
||||
macaddr: "MAC address. MACADDR stores 6-byte addresses and MACADDR8 stores 8-byte addresses.",
|
||||
geometry: "Built-in two-dimensional geometric type. PostgreSQL geometry is planar and has no SRID; use PostGIS for geographic coordinates, projections, or advanced spatial operations.",
|
||||
range: "Single continuous range with inclusive/exclusive bounds. Range values require a compatible subtype; use multirange types for multiple disjoint ranges.",
|
||||
oid: "Unsigned object identifier. It is a system identifier, not a durable application primary key; use an ordinary integer or UUID for application data.",
|
||||
array: "PostgreSQL array of any element type, including multi-dimensional arrays. Array dimensions are not enforced by the declared type and arrays are often less flexible than normalized child rows.",
|
||||
multirange: "Set of non-overlapping ranges of the matching range subtype. PostgreSQL normalizes adjacent or overlapping members according to the subtype rules.",
|
||||
jsonpath: "SQL/JSON path expression used by JSONB query and predicate functions.",
|
||||
pgLsn: "Write-ahead log location used for replication and recovery coordinates; it is not an application sequence number.",
|
||||
pgSnapshot: "Transaction visibility snapshot. Its text representation is intended for database snapshot operations, not general application data.",
|
||||
},
|
||||
sqliteDataTypeHelp: {
|
||||
integer: "SQLite uses dynamic typing and INTEGER affinity; an ordinary column does not enforce the INTEGER storage class. Values are stored as signed integers when possible; INTEGER PRIMARY KEY (exact spelling) is the special alias for the rowid.",
|
||||
real: "REAL affinity stores IEEE 754 floating-point values. It is appropriate for approximate measurements, not exact money or decimal calculations.",
|
||||
text: "TEXT affinity stores text in the database encoding. VARCHAR(n) and CHAR(n) do not enforce n in SQLite; add a CHECK constraint when a length limit matters.",
|
||||
blob: "BLOB affinity stores bytes exactly as supplied, without type conversion.",
|
||||
numeric: "NUMERIC affinity attempts to convert well-formed numeric text to INTEGER first, then REAL; it is not a separate SQLite storage class. DATE, DATETIME, and TIME declarations also have NUMERIC affinity; store ISO-8601 TEXT, Julian-day REAL, or Unix-time INTEGER consistently.",
|
||||
},
|
||||
length: "Length",
|
||||
enumValues: "Enum values",
|
||||
enumValueCount: "{count} values",
|
||||
@@ -2078,6 +2179,15 @@ export default {
|
||||
createKeyName: "Key",
|
||||
createKeyNamePlaceholder: "user:1",
|
||||
createKeyType: "Type",
|
||||
createKeyTypeHelp: {
|
||||
string: "Single binary-safe value up to 512 MB. Best for cached values, counters, flags, and simple serialized data.",
|
||||
hash: "Flat field-value map. Efficient for individual fields, but reading a large hash in full is O(N).",
|
||||
list: "Ordered sequence. Head and tail operations are efficient; random access in the middle is linear in the list length.",
|
||||
set: "Unordered collection of unique members. Single-member operations are O(1), while enumerating or combining large sets is linear.",
|
||||
zset: "Unique members ordered by floating-point scores. Writes are O(log N); members with equal scores are ordered lexicographically.",
|
||||
stream: "Append-only event log. Consumer groups need XACK and pending-entry handling; trim the stream to control retention and memory.",
|
||||
json: "Path-addressable JSON document. Requires RedisJSON/Redis Stack JSON.* support and usually has more overhead than String or Hash.",
|
||||
},
|
||||
createValue: "Value",
|
||||
createMember: "Member",
|
||||
createField: "Field",
|
||||
|
||||
@@ -1749,6 +1749,107 @@ export default withEnglishFallback({
|
||||
sortBy: "ordenar por",
|
||||
},
|
||||
structureEditor: {
|
||||
mysqlDataTypeHelp: {
|
||||
tinyint: "Entero de 1 byte: con signo -128 a 127; sin signo 0 a 255.",
|
||||
smallint: "Entero de 2 bytes: con signo -32.768 a 32.767; sin signo 0 a 65.535.",
|
||||
mediumint: "Entero de 3 bytes: con signo -8.388.608 a 8.388.607; sin signo 0 a 16.777.215.",
|
||||
int: "Entero de 4 bytes: con signo -2.147.483.648 a 2.147.483.647; sin signo 0 a 4.294.967.295.",
|
||||
bigint: "Entero de 8 bytes: con signo -2^63 a 2^63-1; sin signo 0 a 2^64-1.",
|
||||
decimal: "Número decimal exacto; MySQL admite hasta 65 dígitos y escala 30. Úselo para dinero.",
|
||||
float: "Número aproximado de precisión simple; para decimales exactos use DECIMAL.",
|
||||
double: "Número aproximado de doble precisión; para decimales exactos use DECIMAL.",
|
||||
real: "Alias de DOUBLE por defecto; REAL_AS_FLOAT lo convierte en alias de FLOAT.",
|
||||
bit: "Campo de 1 a 64 bits; el ancho predeterminado es 1.",
|
||||
boolean: "Alias de TINYINT(1): 0 es falso y distinto de cero es verdadero.",
|
||||
serial: "Alias práctico de BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE.",
|
||||
date: "Fecha de calendario, de 1000-01-01 a 9999-12-31.",
|
||||
datetime: "Fecha y hora sin conversión de zona; precisión fraccionaria 0 a 6, predeterminada 0.",
|
||||
timestamp: "Fecha y hora convertida entre zona de sesión y UTC; precisión fraccionaria 0 a 6.",
|
||||
time: "Hora o intervalo de -838:59:59 a 838:59:59; precisión fraccionaria 0 a 6.",
|
||||
year: "Año de cuatro dígitos de 1901 a 2155, además de 0000.",
|
||||
char: "Cadena de longitud fija de hasta 255 caracteres, rellenada con espacios.",
|
||||
varchar: "Cadena de longitud variable; el máximo real depende de fila y juego de caracteres.",
|
||||
tinytext: "Texto variable de hasta 255 bytes; fila, charset y max_allowed_packet pueden limitarlo.",
|
||||
text: "Texto variable de hasta 65.535 bytes; fila, charset y max_allowed_packet pueden limitarlo.",
|
||||
mediumtext: "Texto variable de hasta 16.777.215 bytes; fila, charset y max_allowed_packet pueden limitarlo.",
|
||||
longtext: "Texto variable de hasta 4.294.967.295 bytes; fila, charset y max_allowed_packet pueden limitarlo.",
|
||||
binary: "Cadena binaria fija de hasta 255 bytes, rellenada con 0x00.",
|
||||
varbinary: "Cadena binaria variable; el máximo real depende del tamaño de fila.",
|
||||
tinyblob: "Objeto binario de hasta 255 bytes; max_allowed_packet puede limitarlo.",
|
||||
blob: "Objeto binario de hasta 65.535 bytes; max_allowed_packet puede limitarlo.",
|
||||
mediumblob: "Objeto binario de hasta 16.777.215 bytes; max_allowed_packet puede limitarlo.",
|
||||
longblob: "Objeto binario de hasta 4.294.967.295 bytes; max_allowed_packet puede limitarlo.",
|
||||
enum: "Un valor entre hasta 65.535 literales; cambiar el orden puede alterar la semántica almacenada.",
|
||||
set: "Cero o más valores entre hasta 64 miembros declarados.",
|
||||
json: "Documento JSON validado; representación y funciones dependen del producto/versión; limitado por max_allowed_packet.",
|
||||
jsonMysql: "JSON binario nativo de MySQL con validación y funciones JSON; limitado por max_allowed_packet.",
|
||||
jsonMariaDb: "En MariaDB JSON es alias de LONGTEXT con JSON_VALID y colación utf8mb4_bin.",
|
||||
nationalCharacter: "Alias de cadena nacional; MySQL usa utf8mb3, obsoleto; prefiera utf8mb4.",
|
||||
geometry: "Geometría espacial; un índice SPATIAL InnoDB requiere NOT NULL y atributo SRID.",
|
||||
point: "Punto espacial; SPATIAL InnoDB requiere NOT NULL y SRID.",
|
||||
linestring: "Curva espacial; SPATIAL InnoDB requiere NOT NULL y SRID.",
|
||||
polygon: "Superficie espacial; SPATIAL InnoDB requiere NOT NULL y SRID.",
|
||||
multipoint: "Colección de puntos; SPATIAL InnoDB requiere NOT NULL y SRID.",
|
||||
multilinestring: "Colección de curvas; SPATIAL InnoDB requiere NOT NULL y SRID.",
|
||||
multipolygon: "Colección de polígonos; SPATIAL InnoDB requiere NOT NULL y SRID.",
|
||||
geometrycollection: "Colección de geometrías mixtas; SPATIAL InnoDB requiere NOT NULL y SRID.",
|
||||
inet4: "Tipo IPv4 de MariaDB desde 10.10; almacena la dirección en cuatro bytes.",
|
||||
inet6: "Tipo compatible MariaDB para IPv6 e IPv4 mapeada.",
|
||||
uuid: "Identificador único universal de 128 bits proporcionado por servidores compatibles.",
|
||||
vector: "Tipo vector para similitud o ML; disponibilidad y dimensiones dependen de versión.",
|
||||
xmltype: "Tipo XML proporcionado por servidores compatibles.",
|
||||
integerDisplayWidth: "El ancho de visualización entero, por ejemplo INT(11), está obsoleto en MySQL y no limita el rango.",
|
||||
zerofill: "ZEROFILL está obsoleto en MySQL 8 e implica UNSIGNED; formatee en la aplicación.",
|
||||
unsignedNumeric: "UNSIGNED para DECIMAL, FLOAT o DOUBLE está obsoleto en MySQL 8; use CHECK para no negativos.",
|
||||
floatPrecision: "En FLOAT(p), p de 25 a 53 selecciona DOUBLE; use DOUBLE explícitamente.",
|
||||
floatingPointDisplay: "FLOAT(M,D) y DOUBLE(M,D) están obsoletos; use FLOAT/DOUBLE sin parámetros o DECIMAL(M,D).",
|
||||
},
|
||||
postgresDataTypeHelp: {
|
||||
smallint: "Entero con signo de 2 bytes, de -32.768 a 32.767.",
|
||||
integer: "Entero con signo de 4 bytes, de -2.147.483.648 a 2.147.483.647.",
|
||||
bigint: "Entero con signo de 8 bytes, de -2^63 a 2^63-1.",
|
||||
serial: "Sintaxis práctica que crea entero, secuencia, NOT NULL y nextval; no es único por sí mismo y puede tener huecos; prefiera IDENTITY.",
|
||||
numeric: "Decimal exacto con precisión y escala; úselo para dinero y valores sin redondeo binario.",
|
||||
real: "Flotante de precisión simple de 4 bytes; FLOAT(p) de 1 a 24 lo selecciona.",
|
||||
double: "Flotante de doble precisión de 8 bytes; FLOAT sin precisión es alias y FLOAT(p) de 25 a 53 lo selecciona.",
|
||||
money: "Importe monetario dependiente de lc_monetary; NUMERIC suele ser más seguro para datos portables.",
|
||||
boolean: "Valor lógico verdadero, falso o nulo; BOOL es alias de BOOLEAN.",
|
||||
char: "Cadena fija rellenada con espacios; CHARACTER es alias.",
|
||||
varchar: "Cadena variable con límite opcional; CHARACTER VARYING es alias.",
|
||||
text: "Cadena variable sin límite declarado; se almacena como VARCHAR sin límite.",
|
||||
bytea: "Cadena binaria variable; TOAST puede almacenar valores grandes fuera de fila.",
|
||||
date: "Fecha de calendario sin hora ni zona horaria.",
|
||||
time: "Hora sin zona, con precisión fraccionaria opcional.",
|
||||
timetz: "Hora con desplazamiento UTC; uso limitado, prefiera timestamptz para un instante.",
|
||||
timestamp: "Fecha y hora sin conversión de zona para valores locales.",
|
||||
timestamptz: "Instante almacenado en UTC y mostrado en la zona de sesión; no conserva la zona original.",
|
||||
interval: "Duración en meses, días y microsegundos; las unidades de calendario no tienen duración fija.",
|
||||
uuid: "Identificador único universal de 128 bits.",
|
||||
json: "Texto JSON en representación original; se valida al analizar, pero suele ser más lento que JSONB.",
|
||||
jsonb: "JSON binario descompuesto para procesamiento e índices; no conserva espacios, orden ni claves duplicadas.",
|
||||
xml: "Valor XML con validación de documento o contenido según la opción.",
|
||||
bit: "Cadena de bits fija o variable; BIT VARYING y VARBIT son alias variables.",
|
||||
tsvector: "Lexemas normalizados para búsqueda de texto completo; suele indexarse con GIN o GiST.",
|
||||
tsquery: "Consulta booleana de texto completo evaluada contra tsvector.",
|
||||
cidr: "Red IPv4 o IPv6 con bits de host forzados a cero.",
|
||||
inet: "Host IPv4 o IPv6 con máscara opcional; a diferencia de CIDR puede tener bits de host distintos de cero.",
|
||||
macaddr: "Dirección MAC; MACADDR usa 6 bytes y MACADDR8 usa 8 bytes.",
|
||||
geometry: "Geometría plana bidimensional sin SRID; use PostGIS para coordenadas geográficas y operaciones espaciales avanzadas.",
|
||||
range: "Rango continuo con límites inclusivos/exclusivos; use multirange para rangos separados.",
|
||||
oid: "Identificador de objeto sin signo del sistema; no es una clave primaria estable de aplicación.",
|
||||
array: "Arreglo PostgreSQL de cualquier elemento, incluso multidimensional; las dimensiones declaradas no se fuerzan.",
|
||||
multirange: "Conjunto de rangos no superpuestos; PostgreSQL normaliza miembros adyacentes o solapados.",
|
||||
jsonpath: "Expresión de ruta SQL/JSON usada por consultas y predicados JSONB.",
|
||||
pgLsn: "Ubicación WAL para replicación y recuperación; no es un número de secuencia de aplicación.",
|
||||
pgSnapshot: "Instantánea de visibilidad de transacciones para operaciones de base de datos, no datos generales.",
|
||||
},
|
||||
sqliteDataTypeHelp: {
|
||||
integer: "Afinidad INTEGER; una columna normal no fuerza esa clase. INTEGER PRIMARY KEY exacto es alias de rowid.",
|
||||
real: "Afinidad REAL con IEEE 754; no apta para dinero exacto.",
|
||||
text: "Afinidad TEXT; VARCHAR(n) y CHAR(n) no fuerzan n; use CHECK si importa la longitud.",
|
||||
blob: "Afinidad BLOB: almacena bytes sin conversión.",
|
||||
numeric: "Afinidad NUMERIC convierte texto numérico primero a INTEGER y luego a REAL; DATE, DATETIME y TIME también la usan.",
|
||||
},
|
||||
title: "Editar estructura de tabla",
|
||||
createTitle: "Crear tabla",
|
||||
editTabTitle: "Editar: {tableName}",
|
||||
@@ -1941,6 +2042,15 @@ export default withEnglishFallback({
|
||||
summarySize: "tamaño",
|
||||
},
|
||||
redis: {
|
||||
createKeyTypeHelp: {
|
||||
string: "Valor único de hasta 512 MB; útil para caché y contadores.",
|
||||
hash: "Mapa plano campo-valor; leer un Hash grande completo es O(N).",
|
||||
list: "Secuencia ordenada; acceso intermedio lineal.",
|
||||
set: "Miembros únicos sin orden; enumerar conjuntos grandes es lineal.",
|
||||
zset: "Miembros únicos con puntuación flotante; escritura O(log N).",
|
||||
stream: "Registro append-only; gestione XACK, pendientes y recorte.",
|
||||
json: "JSON por ruta; requiere RedisJSON/Redis Stack y suele costar más que String o Hash.",
|
||||
},
|
||||
selectKey: "Selecciona una clave para ver su valor",
|
||||
noKeys: "No se encontraron claves",
|
||||
noKeysInScanHint: "No se encontraron claves en el rango de escaneo actual",
|
||||
|
||||
@@ -1747,6 +1747,107 @@ export default withEnglishFallback({
|
||||
sortBy: "Ordina per",
|
||||
},
|
||||
structureEditor: {
|
||||
mysqlDataTypeHelp: {
|
||||
tinyint: "Intero da 1 byte: con segno da -128 a 127, senza segno da 0 a 255.",
|
||||
smallint: "Intero da 2 byte: con segno da -32.768 a 32.767, senza segno da 0 a 65.535.",
|
||||
mediumint: "Intero da 3 byte: con segno da -8.388.608 a 8.388.607, senza segno da 0 a 16.777.215.",
|
||||
int: "Intero da 4 byte: con segno da -2.147.483.648 a 2.147.483.647, senza segno da 0 a 4.294.967.295.",
|
||||
bigint: "Intero da 8 byte: con segno da -2^63 a 2^63-1, senza segno da 0 a 2^64-1.",
|
||||
decimal: "Numero decimale esatto; MySQL supporta fino a 65 cifre e scala 30. Usarlo per denaro.",
|
||||
float: "Numero approssimato a precisione singola; per decimali esatti usare DECIMAL.",
|
||||
double: "Numero approssimato a doppia precisione; per decimali esatti usare DECIMAL.",
|
||||
real: "Alias di DOUBLE per impostazione predefinita; REAL_AS_FLOAT lo rende alias di FLOAT.",
|
||||
bit: "Campo da 1 a 64 bit; larghezza predefinita 1.",
|
||||
boolean: "Alias di TINYINT(1): 0 è falso e un valore non zero è vero.",
|
||||
serial: "Alias pratico di BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE.",
|
||||
date: "Data di calendario da 1000-01-01 a 9999-12-31.",
|
||||
datetime: "Data e ora senza conversione di fuso; precisione frazionaria da 0 a 6, predefinita 0.",
|
||||
timestamp: "Data e ora convertita tra fuso sessione e UTC; precisione frazionaria da 0 a 6.",
|
||||
time: "Ora o intervallo da -838:59:59 a 838:59:59; precisione frazionaria da 0 a 6.",
|
||||
year: "Anno a quattro cifre da 1901 a 2155, oltre a 0000.",
|
||||
char: "Stringa a lunghezza fissa fino a 255 caratteri, riempita con spazi.",
|
||||
varchar: "Stringa a lunghezza variabile; il massimo dipende da riga e set di caratteri.",
|
||||
tinytext: "Testo variabile fino a 255 byte; riga, charset e max_allowed_packet possono ridurlo.",
|
||||
text: "Testo variabile fino a 65.535 byte; riga, charset e max_allowed_packet possono ridurlo.",
|
||||
mediumtext: "Testo variabile fino a 16.777.215 byte; riga, charset e max_allowed_packet possono ridurlo.",
|
||||
longtext: "Testo variabile fino a 4.294.967.295 byte; riga, charset e max_allowed_packet possono ridurlo.",
|
||||
binary: "Stringa binaria fissa fino a 255 byte, riempita con 0x00.",
|
||||
varbinary: "Stringa binaria variabile; il massimo dipende dalla dimensione della riga.",
|
||||
tinyblob: "Oggetto binario fino a 255 byte; max_allowed_packet può limitarlo.",
|
||||
blob: "Oggetto binario fino a 65.535 byte; max_allowed_packet può limitarlo.",
|
||||
mediumblob: "Oggetto binario fino a 16.777.215 byte; max_allowed_packet può limitarlo.",
|
||||
longblob: "Oggetto binario fino a 4.294.967.295 byte; max_allowed_packet può limitarlo.",
|
||||
enum: "Un valore tra fino a 65.535 letterali; cambiare l'ordine può alterare il significato memorizzato.",
|
||||
set: "Zero o più valori tra fino a 64 membri dichiarati.",
|
||||
json: "Documento JSON validato; rappresentazione e funzioni dipendono da prodotto/versione; limitato da max_allowed_packet.",
|
||||
jsonMysql: "JSON binario nativo MySQL con validazione e funzioni JSON; limitato da max_allowed_packet.",
|
||||
jsonMariaDb: "In MariaDB JSON è alias di LONGTEXT con JSON_VALID e collazione utf8mb4_bin.",
|
||||
nationalCharacter: "Alias di stringa nazionale; MySQL usa utf8mb3, deprecato; preferire utf8mb4.",
|
||||
geometry: "Geometria spaziale; un indice SPATIAL InnoDB richiede NOT NULL e attributo SRID.",
|
||||
point: "Punto spaziale; SPATIAL InnoDB richiede NOT NULL e SRID.",
|
||||
linestring: "Curva spaziale; SPATIAL InnoDB richiede NOT NULL e SRID.",
|
||||
polygon: "Superficie spaziale; SPATIAL InnoDB richiede NOT NULL e SRID.",
|
||||
multipoint: "Raccolta di punti; SPATIAL InnoDB richiede NOT NULL e SRID.",
|
||||
multilinestring: "Raccolta di curve; SPATIAL InnoDB richiede NOT NULL e SRID.",
|
||||
multipolygon: "Raccolta di poligoni; SPATIAL InnoDB richiede NOT NULL e SRID.",
|
||||
geometrycollection: "Raccolta di geometrie miste; SPATIAL InnoDB richiede NOT NULL e SRID.",
|
||||
inet4: "Tipo IPv4 MariaDB disponibile da 10.10; memorizza l'indirizzo in quattro byte.",
|
||||
inet6: "Tipo compatibile MariaDB per IPv6 e IPv4 mappato.",
|
||||
uuid: "Identificatore universale univoco a 128 bit fornito da server compatibili.",
|
||||
vector: "Tipo vettore per similarità o ML; disponibilità e dimensioni dipendono dalla versione.",
|
||||
xmltype: "Tipo XML fornito da server compatibili.",
|
||||
integerDisplayWidth: "La larghezza di visualizzazione intera, ad esempio INT(11), è deprecata e non limita l'intervallo.",
|
||||
zerofill: "ZEROFILL è deprecato in MySQL 8 e implica UNSIGNED; formattare nell'applicazione.",
|
||||
unsignedNumeric: "UNSIGNED per DECIMAL, FLOAT o DOUBLE è deprecato in MySQL 8; usare CHECK per valori non negativi.",
|
||||
floatPrecision: "In FLOAT(p), p da 25 a 53 seleziona DOUBLE; usare DOUBLE esplicitamente.",
|
||||
floatingPointDisplay: "FLOAT(M,D) e DOUBLE(M,D) sono deprecati; usare FLOAT/DOUBLE senza parametri o DECIMAL(M,D).",
|
||||
},
|
||||
postgresDataTypeHelp: {
|
||||
smallint: "Intero con segno da 2 byte, da -32.768 a 32.767.",
|
||||
integer: "Intero con segno da 4 byte, da -2.147.483.648 a 2.147.483.647.",
|
||||
bigint: "Intero con segno da 8 byte, da -2^63 a 2^63-1.",
|
||||
serial: "Sintassi pratica che crea intero, sequenza, NOT NULL e default nextval; non è unico da solo e può avere lacune; preferire IDENTITY.",
|
||||
numeric: "Decimale esatto con precisione e scala; usarlo per denaro e valori senza arrotondamento binario.",
|
||||
real: "Floating point a precisione singola da 4 byte; FLOAT(p) da 1 a 24 lo seleziona.",
|
||||
double: "Floating point a doppia precisione da 8 byte; FLOAT senza precisione è alias e FLOAT(p) da 25 a 53 lo seleziona.",
|
||||
money: "Importo monetario dipendente da lc_monetary; NUMERIC è più sicuro per dati finanziari portabili.",
|
||||
boolean: "Valore logico vero, falso o nullo; BOOL è alias di BOOLEAN.",
|
||||
char: "Stringa fissa riempita di spazi; CHARACTER è alias.",
|
||||
varchar: "Stringa variabile con limite opzionale; CHARACTER VARYING è alias.",
|
||||
text: "Stringa variabile senza limite dichiarato; stessa memorizzazione di VARCHAR senza limite.",
|
||||
bytea: "Stringa binaria variabile; TOAST può memorizzare valori grandi fuori riga.",
|
||||
date: "Data di calendario senza ora né fuso.",
|
||||
time: "Ora senza fuso, con precisione frazionaria opzionale.",
|
||||
timetz: "Ora con offset UTC; uso limitato, preferire timestamptz per un istante.",
|
||||
timestamp: "Data e ora senza conversione di fuso per valori locali.",
|
||||
timestamptz: "Istante memorizzato in UTC e mostrato nel fuso della sessione; non conserva il fuso originale.",
|
||||
interval: "Durata in mesi, giorni e microsecondi; le unità di calendario non hanno durata fissa.",
|
||||
uuid: "Identificatore universale univoco a 128 bit.",
|
||||
json: "Testo JSON nella rappresentazione originale; validato al parsing ma generalmente più lento di JSONB.",
|
||||
jsonb: "JSON binario decomposto ottimizzato per elaborazione e indici; non conserva spazi, ordine o chiavi duplicate.",
|
||||
xml: "Valore XML con validazione di documento o contenuto secondo l'opzione.",
|
||||
bit: "Stringa di bit fissa o variabile; BIT VARYING e VARBIT sono alias variabili.",
|
||||
tsvector: "Lessemi normalizzati per ricerca full-text; spesso indicizzati con GIN o GiST.",
|
||||
tsquery: "Query full-text booleana valutata contro un tsvector.",
|
||||
cidr: "Rete IPv4 o IPv6 con bit host vincolati a zero.",
|
||||
inet: "Host IPv4 o IPv6 con maschera opzionale; diversamente da CIDR può avere bit host non zero.",
|
||||
macaddr: "Indirizzo MAC; MACADDR usa 6 byte e MACADDR8 usa 8 byte.",
|
||||
geometry: "Tipo geometrico piano bidimensionale senza SRID; usare PostGIS per coordinate geografiche e operazioni avanzate.",
|
||||
range: "Intervallo continuo con limiti inclusivi/esclusivi; usare multirange per intervalli separati.",
|
||||
oid: "Identificatore oggetto senza segno di sistema; non è una chiave primaria applicativa durevole.",
|
||||
array: "Array PostgreSQL di qualunque elemento, anche multidimensionale; le dimensioni dichiarate non sono imposte.",
|
||||
multirange: "Insieme di intervalli non sovrapposti; PostgreSQL normalizza membri adiacenti o sovrapposti.",
|
||||
jsonpath: "Espressione percorso SQL/JSON usata da query e predicati JSONB.",
|
||||
pgLsn: "Posizione WAL per replica e recupero; non è un numero di sequenza applicativo.",
|
||||
pgSnapshot: "Snapshot di visibilità delle transazioni per operazioni database, non dati applicativi generali.",
|
||||
},
|
||||
sqliteDataTypeHelp: {
|
||||
integer: "Affinità INTEGER; una colonna normale non impone questa classe. INTEGER PRIMARY KEY esatto è alias di rowid.",
|
||||
real: "Affinità REAL con IEEE 754; non adatta a denaro esatto.",
|
||||
text: "Affinità TEXT; VARCHAR(n) e CHAR(n) non impongono n; usare CHECK se la lunghezza conta.",
|
||||
blob: "Affinità BLOB: memorizza byte senza conversione.",
|
||||
numeric: "Affinità NUMERIC converte testo numerico prima in INTEGER e poi in REAL; anche DATE, DATETIME e TIME la usano.",
|
||||
},
|
||||
title: "Modifica Struttura Tabella",
|
||||
createTitle: "Crea Tabella",
|
||||
editTabTitle: "Modifica: {tableName}",
|
||||
@@ -1939,6 +2040,15 @@ export default withEnglishFallback({
|
||||
summarySize: "dimensione",
|
||||
},
|
||||
redis: {
|
||||
createKeyTypeHelp: {
|
||||
string: "Valore singolo fino a 512 MB; utile per cache e contatori.",
|
||||
hash: "Mappa campo-valore piatta; leggere un Hash grande intero è O(N).",
|
||||
list: "Sequenza ordinata; accesso intermedio lineare.",
|
||||
set: "Membri unici non ordinati; enumerare insiemi grandi è lineare.",
|
||||
zset: "Membri unici con punteggio floating point; scrittura O(log N).",
|
||||
stream: "Log append-only; gestire XACK, pendenti e trimming.",
|
||||
json: "JSON per percorso; richiede RedisJSON/Redis Stack e costa di solito più di String o Hash.",
|
||||
},
|
||||
selectKey: "Seleziona una chiave per visualizzarne il valore",
|
||||
noKeys: "Nessuna chiave trovata",
|
||||
noKeysInScanHint: "Nessuna chiave trovata nell'intervallo di scansione corrente",
|
||||
|
||||
@@ -1782,6 +1782,107 @@ export default withEnglishFallback({
|
||||
sortBy: "並べ替え",
|
||||
},
|
||||
structureEditor: {
|
||||
mysqlDataTypeHelp: {
|
||||
tinyint: "1 バイト整数。符号付きは -128~127、符号なしは 0~255 です。",
|
||||
smallint: "2 バイト整数。符号付きは -32,768~32,767、符号なしは 0~65,535 です。",
|
||||
mediumint: "3 バイト整数。符号付きは -8,388,608~8,388,607、符号なしは 0~16,777,215 です。",
|
||||
int: "4 バイト整数。符号付きは -2,147,483,648~2,147,483,647、符号なしは 0~4,294,967,295 です。",
|
||||
bigint: "8 バイト整数。符号付きは -2^63~2^63-1、符号なしは 0~2^64-1 です。",
|
||||
decimal: "精度と小数部桁数を指定できる正確な 10 進数です。MySQL では最大 65 桁、小数部は最大 30 桁で、金額に適します。",
|
||||
float: "単精度の近似数値です。正確な小数値には DECIMAL を使用してください。",
|
||||
double: "倍精度の近似数値です。正確な小数値には DECIMAL を使用してください。",
|
||||
real: "既定では DOUBLE の別名です。REAL_AS_FLOAT を有効にすると FLOAT の別名になります。",
|
||||
bit: "1~64 ビットのビット列です。既定の幅は 1 です。",
|
||||
boolean: "TINYINT(1) の別名です。0 は偽、0 以外は真として扱われます。",
|
||||
serial: "BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE の簡略別名です。",
|
||||
date: "1000-01-01~9999-12-31 の暦日です。",
|
||||
datetime: "タイムゾーン変換を行わない日時です。小数秒精度は 0~6、既定値は 0 です。",
|
||||
timestamp: "セッションのタイムゾーンと UTC の間で変換される日時です。小数秒精度は 0~6 です。",
|
||||
time: "-838:59:59~838:59:59 の時刻または時間間隔です。小数秒精度は 0~6 です。",
|
||||
year: "1901~2155 と 0000 を格納する 4 桁の年です。",
|
||||
char: "最大 255 文字の固定長文字列です。末尾は空白で埋められます。",
|
||||
varchar: "可変長文字列です。実際の最大長は行サイズと文字セットに依存します。",
|
||||
tinytext: "最大 255 バイトの可変長テキストです。行サイズ、文字セット、max_allowed_packet により制限される場合があります。",
|
||||
text: "最大 65,535 バイトの可変長テキストです。行サイズ、文字セット、max_allowed_packet により制限される場合があります。",
|
||||
mediumtext: "最大 16,777,215 バイトの可変長テキストです。行サイズ、文字セット、max_allowed_packet により制限される場合があります。",
|
||||
longtext: "最大 4,294,967,295 バイトの可変長テキストです。行サイズ、文字セット、max_allowed_packet により制限される場合があります。",
|
||||
binary: "最大 255 バイトの固定長バイナリ文字列です。末尾は 0x00 で埋められます。",
|
||||
varbinary: "可変長バイナリ文字列です。実際の最大長は行サイズに依存します。",
|
||||
tinyblob: "最大 255 バイトのバイナリオブジェクトです。max_allowed_packet により制限される場合があります。",
|
||||
blob: "最大 65,535 バイトのバイナリオブジェクトです。max_allowed_packet により制限される場合があります。",
|
||||
mediumblob: "最大 16,777,215 バイトのバイナリオブジェクトです。max_allowed_packet により制限される場合があります。",
|
||||
longblob: "最大 4,294,967,295 バイトのバイナリオブジェクトです。max_allowed_packet により制限される場合があります。",
|
||||
enum: "最大 65,535 個のリテラルから 1 つを選ぶ値です。定義順を変えると保存済み値の意味が変わることがあります。",
|
||||
set: "宣言した最大 64 個のメンバーから、0 個以上を選ぶ値です。",
|
||||
json: "検証済み JSON ドキュメントです。内部表現と関数は製品・バージョンに依存し、max_allowed_packet の制限を受けます。",
|
||||
jsonMysql: "MySQL のネイティブなバイナリ JSON です。検証と JSON 関数を備え、max_allowed_packet の制限を受けます。",
|
||||
jsonMariaDb: "MariaDB では JSON は JSON_VALID と utf8mb4_bin 照合順序を持つ LONGTEXT の別名です。",
|
||||
nationalCharacter: "国別文字列型の別名です。MySQL では非推奨の utf8mb3 を使用するため、utf8mb4 を推奨します。",
|
||||
geometry: "空間ジオメトリです。InnoDB の SPATIAL インデックスには NOT NULL と SRID 属性が必要です。",
|
||||
point: "空間上の点です。InnoDB の SPATIAL インデックスには NOT NULL と SRID が必要です。",
|
||||
linestring: "空間上の曲線です。InnoDB の SPATIAL インデックスには NOT NULL と SRID が必要です。",
|
||||
polygon: "空間上の面です。InnoDB の SPATIAL インデックスには NOT NULL と SRID が必要です。",
|
||||
multipoint: "点のコレクションです。InnoDB の SPATIAL インデックスには NOT NULL と SRID が必要です。",
|
||||
multilinestring: "曲線のコレクションです。InnoDB の SPATIAL インデックスには NOT NULL と SRID が必要です。",
|
||||
multipolygon: "ポリゴンのコレクションです。InnoDB の SPATIAL インデックスには NOT NULL と SRID が必要です。",
|
||||
geometrycollection: "異なる種類のジオメトリのコレクションです。InnoDB の SPATIAL インデックスには NOT NULL と SRID が必要です。",
|
||||
inet4: "MariaDB 10.10 以降で利用できる IPv4 型です。アドレスを 4 バイトで保存します。",
|
||||
inet6: "IPv6 および IPv4 マップドアドレス向けの MariaDB 互換型です。",
|
||||
uuid: "対応サーバーが提供する 128 ビットの UUID です。",
|
||||
vector: "類似検索や機械学習向けのベクトル型です。利用可否と次元数はバージョンに依存します。",
|
||||
xmltype: "対応サーバーが提供する XML 型です。",
|
||||
integerDisplayWidth: "INT(11) のような整数の表示幅は MySQL で非推奨であり、値の範囲を制限しません。",
|
||||
zerofill: "ZEROFILL は MySQL 8 で非推奨で、UNSIGNED を暗黙に指定します。表示はアプリケーション側で整形してください。",
|
||||
unsignedNumeric: "DECIMAL、FLOAT、DOUBLE の UNSIGNED は MySQL 8 で非推奨です。非負値の制約には CHECK を使用してください。",
|
||||
floatPrecision: "FLOAT(p) では p が 25~53 の場合に DOUBLE が選ばれます。DOUBLE を明示的に使用してください。",
|
||||
floatingPointDisplay: "FLOAT(M,D) と DOUBLE(M,D) は非推奨です。引数なしの FLOAT/DOUBLE または DECIMAL(M,D) を使用してください。",
|
||||
},
|
||||
postgresDataTypeHelp: {
|
||||
smallint: "2 バイトの符号付き整数で、範囲は -32,768~32,767 です。",
|
||||
integer: "4 バイトの符号付き整数で、範囲は -2,147,483,648~2,147,483,647 です。",
|
||||
bigint: "8 バイトの符号付き整数で、範囲は -2^63~2^63-1 です。",
|
||||
serial: "整数型、シーケンス、NOT NULL、nextval の既定値を作る簡略構文です。単独では一意でなく欠番も発生するため、IDENTITY を推奨します。",
|
||||
numeric: "精度と小数部桁数を持つ正確な 10 進数です。金額やバイナリ丸めを避ける値に適します。",
|
||||
real: "4 バイトの単精度浮動小数点数です。FLOAT(p) の p が 1~24 の場合に選ばれます。",
|
||||
double: "8 バイトの倍精度浮動小数点数です。精度指定なしの FLOAT は別名で、FLOAT(p) の p が 25~53 の場合に選ばれます。",
|
||||
money: "lc_monetary に依存する通貨額です。移植性のある金額データには NUMERIC の方が安全です。",
|
||||
boolean: "真、偽、NULL を取る論理値です。BOOL は BOOLEAN の別名です。",
|
||||
char: "空白で埋める固定長文字列です。CHARACTER は別名です。",
|
||||
varchar: "任意の長さ上限を持てる可変長文字列です。CHARACTER VARYING は別名です。",
|
||||
text: "宣言上の長さ上限がない可変長文字列です。長さ指定なしの VARCHAR と同じ形式で保存されます。",
|
||||
bytea: "可変長バイナリ文字列です。TOAST により大きな値を行外に保存できます。",
|
||||
date: "時刻やタイムゾーンを含まない暦日です。",
|
||||
time: "タイムゾーンを含まない時刻で、小数秒精度は任意です。",
|
||||
timetz: "UTC オフセットを持つ時刻です。用途は限定的で、ある時点を表すには timestamptz を推奨します。",
|
||||
timestamp: "ローカルな日時に用いる、タイムゾーン変換を行わない日時です。",
|
||||
timestamptz: "UTC で保存しセッションのタイムゾーンで表示する時点です。元のタイムゾーン名は保持しません。",
|
||||
interval: "月、日、マイクロ秒で表す期間です。暦上の単位には固定の長さがありません。",
|
||||
uuid: "128 ビットの UUID です。",
|
||||
json: "元の表現を保持する JSON テキストです。解析時に検証されますが、通常は JSONB より低速です。",
|
||||
jsonb: "処理とインデックス用に分解されたバイナリ JSON です。空白、キーの順序、重複キーは保持しません。",
|
||||
xml: "オプションに応じて文書またはコンテンツを検証する XML 値です。",
|
||||
bit: "固定長または可変長のビット列です。BIT VARYING と VARBIT は可変長の別名です。",
|
||||
tsvector: "全文検索用に正規化された語彙素です。通常は GIN または GiST でインデックスします。",
|
||||
tsquery: "tsvector に対して評価する全文検索の論理クエリです。",
|
||||
cidr: "ホスト部ビットを 0 に固定した IPv4 または IPv6 ネットワークです。",
|
||||
inet: "任意のマスクを持てる IPv4 または IPv6 ホストです。CIDR と異なり、ホスト部ビットは 0 以外にできます。",
|
||||
macaddr: "MAC アドレスです。MACADDR は 6 バイト、MACADDR8 は 8 バイトを使用します。",
|
||||
geometry: "SRID を持たない 2 次元平面ジオメトリです。地理座標や高度な空間演算には PostGIS を使用してください。",
|
||||
range: "包含・非包含の境界を持つ連続範囲です。分離した範囲には multirange を使用してください。",
|
||||
oid: "システムの符号なしオブジェクト識別子です。安定したアプリケーションの主キーではありません。",
|
||||
array: "任意の要素型の PostgreSQL 配列で、多次元にもできます。宣言した次元数は強制されません。",
|
||||
multirange: "重複しない範囲の集合です。PostgreSQL は隣接または重複する要素を正規化します。",
|
||||
jsonpath: "JSONB のクエリや述語に使う SQL/JSON パス式です。",
|
||||
pgLsn: "レプリケーションと復旧に使う WAL の位置です。アプリケーション用の連番ではありません。",
|
||||
pgSnapshot: "一般的なアプリケーションデータではなく、データベース操作で使うトランザクション可視性のスナップショットです。",
|
||||
},
|
||||
sqliteDataTypeHelp: {
|
||||
integer: "INTEGER アフィニティです。通常の列はこのストレージクラスを強制しません。正確に INTEGER PRIMARY KEY と宣言すると rowid の別名になります。",
|
||||
real: "IEEE 754 を用いる REAL アフィニティです。正確な金額には適しません。",
|
||||
text: "TEXT アフィニティです。VARCHAR(n) と CHAR(n) は n を強制しないため、長さが重要なら CHECK を使用してください。",
|
||||
blob: "BLOB アフィニティです。バイト列を変換せずに保存します。",
|
||||
numeric: "NUMERIC アフィニティは数値テキストをまず INTEGER、次に REAL に変換します。DATE、DATETIME、TIME もこのアフィニティを持ちます。",
|
||||
},
|
||||
title: "テーブル構造を編集",
|
||||
createTitle: "テーブルを作成",
|
||||
editTabTitle: "編集: {tableName}",
|
||||
@@ -1940,6 +2041,15 @@ export default withEnglishFallback({
|
||||
base64Readonly: "Base64値はこのバージョンでは読み取り専用です。",
|
||||
},
|
||||
redis: {
|
||||
createKeyTypeHelp: {
|
||||
string: "最大 512 MB の単一値。キャッシュやカウンター向けです。",
|
||||
hash: "フラットなフィールド値マップ。大きな Hash 全読込は O(N) です。",
|
||||
list: "順序付き列。途中へのランダムアクセスは線形です。",
|
||||
set: "順不同の一意メンバー集合。大集合の列挙は線形です。",
|
||||
zset: "浮動小数スコア付き一意メンバー。書込みは O(log N) です。",
|
||||
stream: "追記専用ログ。XACK、pending、トリミングを管理します。",
|
||||
json: "パス指定 JSON。RedisJSON/Redis Stack が必要で、String/Hash より負荷が高めです。",
|
||||
},
|
||||
selectKey: "キーを選択して値を表示",
|
||||
noKeys: "キーが見つかりません",
|
||||
pattern: "パターン(例: user:*)",
|
||||
|
||||
@@ -1749,6 +1749,107 @@ export default withEnglishFallback({
|
||||
sortBy: "Ordenar por",
|
||||
},
|
||||
structureEditor: {
|
||||
mysqlDataTypeHelp: {
|
||||
tinyint: "Inteiro de 1 byte: com sinal, de -128 a 127; sem sinal, de 0 a 255.",
|
||||
smallint: "Inteiro de 2 bytes: com sinal, de -32.768 a 32.767; sem sinal, de 0 a 65.535.",
|
||||
mediumint: "Inteiro de 3 bytes: com sinal, de -8.388.608 a 8.388.607; sem sinal, de 0 a 16.777.215.",
|
||||
int: "Inteiro de 4 bytes: com sinal, de -2.147.483.648 a 2.147.483.647; sem sinal, de 0 a 4.294.967.295.",
|
||||
bigint: "Inteiro de 8 bytes: com sinal, de -2^63 a 2^63-1; sem sinal, de 0 a 2^64-1.",
|
||||
decimal: "Número decimal exato; o MySQL aceita até 65 dígitos e escala 30. Use para valores monetários.",
|
||||
float: "Número aproximado de precisão simples; para decimais exatos, use DECIMAL.",
|
||||
double: "Número aproximado de dupla precisão; para decimais exatos, use DECIMAL.",
|
||||
real: "Alias de DOUBLE por padrão; REAL_AS_FLOAT faz dele um alias de FLOAT.",
|
||||
bit: "Campo de 1 a 64 bits; a largura padrão é 1.",
|
||||
boolean: "Alias de TINYINT(1): 0 é falso e qualquer valor diferente de zero é verdadeiro.",
|
||||
serial: "Alias prático de BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE.",
|
||||
date: "Data de calendário de 1000-01-01 a 9999-12-31.",
|
||||
datetime: "Data e hora sem conversão de fuso; precisão fracionária de 0 a 6, padrão 0.",
|
||||
timestamp: "Data e hora convertida entre o fuso da sessão e UTC; precisão fracionária de 0 a 6.",
|
||||
time: "Hora ou intervalo de -838:59:59 a 838:59:59; precisão fracionária de 0 a 6.",
|
||||
year: "Ano de quatro dígitos de 1901 a 2155, além de 0000.",
|
||||
char: "String de tamanho fixo de até 255 caracteres, preenchida com espaços.",
|
||||
varchar: "String de tamanho variável; o máximo efetivo depende da linha e do conjunto de caracteres.",
|
||||
tinytext: "Texto variável de até 255 bytes; linha, charset e max_allowed_packet podem reduzi-lo.",
|
||||
text: "Texto variável de até 65.535 bytes; linha, charset e max_allowed_packet podem reduzi-lo.",
|
||||
mediumtext: "Texto variável de até 16.777.215 bytes; linha, charset e max_allowed_packet podem reduzi-lo.",
|
||||
longtext: "Texto variável de até 4.294.967.295 bytes; linha, charset e max_allowed_packet podem reduzi-lo.",
|
||||
binary: "String binária fixa de até 255 bytes, preenchida com 0x00.",
|
||||
varbinary: "String binária de tamanho variável; o máximo efetivo depende do tamanho da linha.",
|
||||
tinyblob: "Objeto binário de até 255 bytes; max_allowed_packet pode limitá-lo.",
|
||||
blob: "Objeto binário de até 65.535 bytes; max_allowed_packet pode limitá-lo.",
|
||||
mediumblob: "Objeto binário de até 16.777.215 bytes; max_allowed_packet pode limitá-lo.",
|
||||
longblob: "Objeto binário de até 4.294.967.295 bytes; max_allowed_packet pode limitá-lo.",
|
||||
enum: "Um valor entre até 65.535 literais; alterar a ordem pode mudar o significado armazenado.",
|
||||
set: "Zero ou mais valores entre até 64 membros declarados.",
|
||||
json: "Documento JSON validado; a representação e as funções dependem do produto e da versão; limitado por max_allowed_packet.",
|
||||
jsonMysql: "JSON binário nativo do MySQL, com validação e funções JSON; limitado por max_allowed_packet.",
|
||||
jsonMariaDb: "No MariaDB, JSON é um alias de LONGTEXT com JSON_VALID e collation utf8mb4_bin.",
|
||||
nationalCharacter: "Alias de string nacional; o MySQL usa utf8mb3, que está obsoleto; prefira utf8mb4.",
|
||||
geometry: "Geometria espacial; um índice SPATIAL do InnoDB requer NOT NULL e atributo SRID.",
|
||||
point: "Ponto espacial; SPATIAL do InnoDB requer NOT NULL e SRID.",
|
||||
linestring: "Curva espacial; SPATIAL do InnoDB requer NOT NULL e SRID.",
|
||||
polygon: "Superfície espacial; SPATIAL do InnoDB requer NOT NULL e SRID.",
|
||||
multipoint: "Coleção de pontos; SPATIAL do InnoDB requer NOT NULL e SRID.",
|
||||
multilinestring: "Coleção de curvas; SPATIAL do InnoDB requer NOT NULL e SRID.",
|
||||
multipolygon: "Coleção de polígonos; SPATIAL do InnoDB requer NOT NULL e SRID.",
|
||||
geometrycollection: "Coleção de geometrias mistas; SPATIAL do InnoDB requer NOT NULL e SRID.",
|
||||
inet4: "Tipo IPv4 do MariaDB disponível desde a versão 10.10; armazena o endereço em quatro bytes.",
|
||||
inet6: "Tipo compatível com MariaDB para IPv6 e IPv4 mapeado.",
|
||||
uuid: "Identificador universalmente único de 128 bits fornecido por servidores compatíveis.",
|
||||
vector: "Tipo de vetor para similaridade ou ML; disponibilidade e dimensões dependem da versão.",
|
||||
xmltype: "Tipo XML fornecido por servidores compatíveis.",
|
||||
integerDisplayWidth: "A largura de exibição de inteiros, como INT(11), está obsoleta no MySQL e não limita o intervalo.",
|
||||
zerofill: "ZEROFILL está obsoleto no MySQL 8 e implica UNSIGNED; formate na aplicação.",
|
||||
unsignedNumeric: "UNSIGNED para DECIMAL, FLOAT ou DOUBLE está obsoleto no MySQL 8; use CHECK para valores não negativos.",
|
||||
floatPrecision: "Em FLOAT(p), p de 25 a 53 seleciona DOUBLE; use DOUBLE explicitamente.",
|
||||
floatingPointDisplay: "FLOAT(M,D) e DOUBLE(M,D) estão obsoletos; use FLOAT/DOUBLE sem parâmetros ou DECIMAL(M,D).",
|
||||
},
|
||||
postgresDataTypeHelp: {
|
||||
smallint: "Inteiro com sinal de 2 bytes, de -32.768 a 32.767.",
|
||||
integer: "Inteiro com sinal de 4 bytes, de -2.147.483.648 a 2.147.483.647.",
|
||||
bigint: "Inteiro com sinal de 8 bytes, de -2^63 a 2^63-1.",
|
||||
serial: "Sintaxe prática que cria inteiro, sequência, NOT NULL e nextval; não é único por si só e pode ter lacunas; prefira IDENTITY.",
|
||||
numeric: "Decimal exato com precisão e escala; use para valores monetários e valores sem arredondamento binário.",
|
||||
real: "Ponto flutuante de precisão simples de 4 bytes; FLOAT(p) de 1 a 24 o seleciona.",
|
||||
double: "Ponto flutuante de dupla precisão de 8 bytes; FLOAT sem precisão é um alias e FLOAT(p) de 25 a 53 o seleciona.",
|
||||
money: "Valor monetário dependente de lc_monetary; NUMERIC costuma ser mais seguro para dados portáveis.",
|
||||
boolean: "Valor lógico verdadeiro, falso ou nulo; BOOL é um alias de BOOLEAN.",
|
||||
char: "String fixa preenchida com espaços; CHARACTER é um alias.",
|
||||
varchar: "String de tamanho variável com limite opcional; CHARACTER VARYING é um alias.",
|
||||
text: "String de tamanho variável sem limite declarado; é armazenada como VARCHAR sem limite.",
|
||||
bytea: "String binária de tamanho variável; TOAST pode armazenar valores grandes fora da linha.",
|
||||
date: "Data de calendário sem hora nem fuso horário.",
|
||||
time: "Hora sem fuso horário, com precisão fracionária opcional.",
|
||||
timetz: "Hora com deslocamento UTC; tem uso limitado, prefira timestamptz para representar um instante.",
|
||||
timestamp: "Data e hora sem conversão de fuso para valores locais.",
|
||||
timestamptz: "Instante armazenado em UTC e exibido no fuso da sessão; não preserva o fuso original.",
|
||||
interval: "Duração em meses, dias e microssegundos; unidades de calendário não têm duração fixa.",
|
||||
uuid: "Identificador universalmente único de 128 bits.",
|
||||
json: "Texto JSON na representação original; é validado ao ser analisado, mas costuma ser mais lento que JSONB.",
|
||||
jsonb: "JSON binário decomposto para processamento e índices; não preserva espaços, ordem nem chaves duplicadas.",
|
||||
xml: "Valor XML com validação de documento ou conteúdo conforme a opção.",
|
||||
bit: "String de bits fixa ou variável; BIT VARYING e VARBIT são aliases de tamanho variável.",
|
||||
tsvector: "Lexemas normalizados para busca de texto completo; normalmente indexados com GIN ou GiST.",
|
||||
tsquery: "Consulta booleana de texto completo avaliada contra tsvector.",
|
||||
cidr: "Rede IPv4 ou IPv6 com os bits de host forçados a zero.",
|
||||
inet: "Host IPv4 ou IPv6 com máscara opcional; ao contrário de CIDR, pode ter bits de host diferentes de zero.",
|
||||
macaddr: "Endereço MAC; MACADDR usa 6 bytes e MACADDR8 usa 8 bytes.",
|
||||
geometry: "Geometria plana bidimensional sem SRID; use PostGIS para coordenadas geográficas e operações espaciais avançadas.",
|
||||
range: "Intervalo contínuo com limites inclusivos ou exclusivos; use multirange para intervalos separados.",
|
||||
oid: "Identificador de objeto sem sinal do sistema; não é uma chave primária estável para aplicações.",
|
||||
array: "Array PostgreSQL de qualquer tipo de elemento, inclusive multidimensional; as dimensões declaradas não são impostas.",
|
||||
multirange: "Conjunto de intervalos não sobrepostos; o PostgreSQL normaliza membros adjacentes ou sobrepostos.",
|
||||
jsonpath: "Expressão de caminho SQL/JSON usada em consultas e predicados JSONB.",
|
||||
pgLsn: "Posição do WAL para replicação e recuperação; não é um número de sequência da aplicação.",
|
||||
pgSnapshot: "Instantâneo de visibilidade de transações para operações de banco de dados, não para dados gerais.",
|
||||
},
|
||||
sqliteDataTypeHelp: {
|
||||
integer: "Afinidade INTEGER; uma coluna comum não impõe essa classe de armazenamento. INTEGER PRIMARY KEY exato é um alias de rowid.",
|
||||
real: "Afinidade REAL com IEEE 754; não é adequada para valores monetários exatos.",
|
||||
text: "Afinidade TEXT; VARCHAR(n) e CHAR(n) não impõem n; use CHECK se o tamanho for importante.",
|
||||
blob: "Afinidade BLOB: armazena bytes sem conversão.",
|
||||
numeric: "A afinidade NUMERIC converte texto numérico primeiro em INTEGER e depois em REAL; DATE, DATETIME e TIME também a usam.",
|
||||
},
|
||||
title: "Editar estrutura da tabela",
|
||||
createTitle: "Criar tabela",
|
||||
editTabTitle: "Editar: {tableName}",
|
||||
@@ -1941,6 +2042,15 @@ export default withEnglishFallback({
|
||||
summarySize: "tamanho",
|
||||
},
|
||||
redis: {
|
||||
createKeyTypeHelp: {
|
||||
string: "Valor único de até 512 MB; útil para cache e contadores.",
|
||||
hash: "Mapa plano campo-valor; ler um Hash grande inteiro é O(N).",
|
||||
list: "Sequência ordenada; acesso no meio é linear.",
|
||||
set: "Membros únicos sem ordem; enumerar conjuntos grandes é linear.",
|
||||
zset: "Membros únicos com pontuação de ponto flutuante; escrita O(log N).",
|
||||
stream: "Log somente de acréscimo; gerencie XACK, pendências e corte.",
|
||||
json: "JSON por caminho; requer RedisJSON/Redis Stack e costuma custar mais que String ou Hash.",
|
||||
},
|
||||
selectKey: "Selecione uma chave para ver seu valor",
|
||||
noKeys: "Nenhuma chave encontrada",
|
||||
noKeysInScanHint: "Nenhuma chave encontrada no intervalo atual de verificação",
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import en from "./en";
|
||||
import esMessages from "./es";
|
||||
import itMessages from "./it";
|
||||
import jaMessages from "./ja";
|
||||
import ptBRMessages from "./pt-BR";
|
||||
import zhTWMessages from "./zh-TW";
|
||||
|
||||
type TypeHelpLocale = { structureEditor: Record<string, Record<string, string>>; redis: Record<string, Record<string, string>> };
|
||||
|
||||
const english = en as unknown as TypeHelpLocale;
|
||||
const es = esMessages as unknown as TypeHelpLocale;
|
||||
const itLocale = itMessages as unknown as TypeHelpLocale;
|
||||
const ja = jaMessages as unknown as TypeHelpLocale;
|
||||
const ptBR = ptBRMessages as unknown as TypeHelpLocale;
|
||||
const zhTW = zhTWMessages as unknown as TypeHelpLocale;
|
||||
const locales = { es, it: itLocale, ja, "pt-BR": ptBR, "zh-TW": zhTW };
|
||||
const directories = [
|
||||
["structureEditor", "mysqlDataTypeHelp"],
|
||||
["structureEditor", "postgresDataTypeHelp"],
|
||||
["structureEditor", "sqliteDataTypeHelp"],
|
||||
["redis", "createKeyTypeHelp"],
|
||||
] as const;
|
||||
|
||||
describe("localized type help", () => {
|
||||
it("provides every English help key directly in each supported locale", () => {
|
||||
for (const [localeName, locale] of Object.entries(locales)) {
|
||||
for (const [section, directory] of directories) {
|
||||
const localized = (locale as unknown as Record<string, Record<string, Record<string, unknown>>>)[section]?.[directory] ?? {};
|
||||
for (const key of Object.keys(english[section]![directory]!)) {
|
||||
expect(localized[key], `${localeName}.${section}.${directory}.${key}`).toEqual(expect.any(String));
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
it("does not silently use the English copy for representative entries", () => {
|
||||
for (const locale of Object.values(locales)) {
|
||||
expect(locale.structureEditor.mysqlDataTypeHelp.int).not.toBe(english.structureEditor.mysqlDataTypeHelp.int);
|
||||
expect(locale.structureEditor.postgresDataTypeHelp.jsonb).not.toBe(english.structureEditor.postgresDataTypeHelp.jsonb);
|
||||
expect(locale.structureEditor.sqliteDataTypeHelp.integer).not.toBe(english.structureEditor.sqliteDataTypeHelp.integer);
|
||||
expect(locale.redis.createKeyTypeHelp.string).not.toBe(english.redis.createKeyTypeHelp.string);
|
||||
}
|
||||
});
|
||||
|
||||
it("keeps concrete database facts in the Spanish translations", () => {
|
||||
expect(es.structureEditor.mysqlDataTypeHelp.int).toContain("4 bytes");
|
||||
expect(es.structureEditor.mysqlDataTypeHelp.jsonMysql).toContain("max_allowed_packet");
|
||||
expect(es.structureEditor.mysqlDataTypeHelp.geometry).toContain("SRID");
|
||||
expect(es.structureEditor.postgresDataTypeHelp.jsonb).toContain("índices");
|
||||
expect(es.structureEditor.sqliteDataTypeHelp.integer).toContain("rowid");
|
||||
expect(es.redis.createKeyTypeHelp.stream).toContain("XACK");
|
||||
});
|
||||
|
||||
it("keeps concrete database facts in the Italian translations", () => {
|
||||
expect(itLocale.structureEditor.mysqlDataTypeHelp.int).toContain("4 byte");
|
||||
expect(itLocale.structureEditor.mysqlDataTypeHelp.jsonMysql).toContain("max_allowed_packet");
|
||||
expect(itLocale.structureEditor.mysqlDataTypeHelp.geometry).toContain("SRID");
|
||||
expect(itLocale.structureEditor.postgresDataTypeHelp.jsonb).toContain("indici");
|
||||
expect(itLocale.structureEditor.sqliteDataTypeHelp.integer).toContain("rowid");
|
||||
expect(itLocale.redis.createKeyTypeHelp.stream).toContain("XACK");
|
||||
});
|
||||
|
||||
it("keeps concrete database facts in the Japanese translations", () => {
|
||||
expect(ja.structureEditor.mysqlDataTypeHelp.int).toContain("4 バイト");
|
||||
expect(ja.structureEditor.mysqlDataTypeHelp.jsonMariaDb).toContain("LONGTEXT");
|
||||
expect(ja.structureEditor.mysqlDataTypeHelp.geometry).toContain("SRID");
|
||||
expect(ja.structureEditor.mysqlDataTypeHelp.integerDisplayWidth).toContain("非推奨");
|
||||
expect(ja.structureEditor.postgresDataTypeHelp.jsonb).toContain("重複キー");
|
||||
expect(ja.structureEditor.postgresDataTypeHelp.pgLsn).toContain("WAL");
|
||||
expect(ja.structureEditor.sqliteDataTypeHelp.integer).toContain("rowid");
|
||||
expect(ja.structureEditor.sqliteDataTypeHelp.text).toContain("CHECK");
|
||||
expect(ja.redis.createKeyTypeHelp.stream).toContain("XACK");
|
||||
});
|
||||
|
||||
it("keeps concrete database facts in the Brazilian Portuguese translations", () => {
|
||||
for (const directory of [ptBR.structureEditor.mysqlDataTypeHelp, ptBR.structureEditor.postgresDataTypeHelp, ptBR.structureEditor.sqliteDataTypeHelp]) {
|
||||
expect(Object.values(directory)).not.toContain(expect.stringContaining("{type}"));
|
||||
}
|
||||
expect(ptBR.structureEditor.mysqlDataTypeHelp.int).toContain("4 bytes");
|
||||
expect(ptBR.structureEditor.mysqlDataTypeHelp.jsonMysql).toContain("max_allowed_packet");
|
||||
expect(ptBR.structureEditor.mysqlDataTypeHelp.jsonMariaDb).toContain("LONGTEXT");
|
||||
expect(ptBR.structureEditor.mysqlDataTypeHelp.geometry).toContain("SRID");
|
||||
expect(ptBR.structureEditor.mysqlDataTypeHelp.integerDisplayWidth).toContain("obsoleta");
|
||||
expect(ptBR.structureEditor.postgresDataTypeHelp.jsonb).toContain("chaves duplicadas");
|
||||
expect(ptBR.structureEditor.postgresDataTypeHelp.bytea).toContain("TOAST");
|
||||
expect(ptBR.structureEditor.postgresDataTypeHelp.pgLsn).toContain("WAL");
|
||||
expect(ptBR.structureEditor.sqliteDataTypeHelp.integer).toContain("rowid");
|
||||
expect(ptBR.structureEditor.sqliteDataTypeHelp.text).toContain("CHECK");
|
||||
expect(ptBR.redis.createKeyTypeHelp.stream).toContain("XACK");
|
||||
});
|
||||
|
||||
it("keeps concrete database facts in the Traditional Chinese translations", () => {
|
||||
for (const directory of [zhTW.structureEditor.mysqlDataTypeHelp, zhTW.structureEditor.postgresDataTypeHelp, zhTW.structureEditor.sqliteDataTypeHelp]) {
|
||||
expect(Object.values(directory)).not.toContain(expect.stringContaining("{type}"));
|
||||
}
|
||||
expect(zhTW.structureEditor.mysqlDataTypeHelp.int).toContain("4 位元組");
|
||||
expect(zhTW.structureEditor.mysqlDataTypeHelp.jsonMysql).toContain("max_allowed_packet");
|
||||
expect(zhTW.structureEditor.mysqlDataTypeHelp.jsonMariaDb).toContain("LONGTEXT");
|
||||
expect(zhTW.structureEditor.mysqlDataTypeHelp.geometry).toContain("SRID");
|
||||
expect(zhTW.structureEditor.mysqlDataTypeHelp.integerDisplayWidth).toContain("淘汰");
|
||||
expect(zhTW.structureEditor.postgresDataTypeHelp.jsonb).toContain("重複鍵");
|
||||
expect(zhTW.structureEditor.postgresDataTypeHelp.bytea).toContain("TOAST");
|
||||
expect(zhTW.structureEditor.postgresDataTypeHelp.pgLsn).toContain("WAL");
|
||||
expect(zhTW.structureEditor.sqliteDataTypeHelp.integer).toContain("rowid");
|
||||
expect(zhTW.structureEditor.sqliteDataTypeHelp.text).toContain("CHECK");
|
||||
expect(zhTW.redis.createKeyTypeHelp.stream).toContain("XACK");
|
||||
});
|
||||
});
|
||||
@@ -1853,6 +1853,107 @@ export default withEnglishFallback({
|
||||
enumValuePlaceholder: "枚举值(允许空字符串)",
|
||||
typePlaceholder: "选择或输入...",
|
||||
noMatchingType: "无匹配类型",
|
||||
mysqlDataTypeHelp: {
|
||||
tinyint: "1 字节整数。有符号范围:-128 到 127;无符号范围:0 到 255。",
|
||||
smallint: "2 字节整数。有符号范围:-32768 到 32767;无符号范围:0 到 65535。",
|
||||
mediumint: "3 字节整数。有符号范围:-8388608 到 8388607;无符号范围:0 到 16777215。",
|
||||
int: "4 字节整数。有符号范围:-2147483648 到 2147483647;无符号范围:0 到 4294967295。",
|
||||
bigint: "8 字节整数。有符号范围:-2^63 到 2^63-1;无符号范围:0 到 2^64-1。",
|
||||
decimal: "精确的定点数。MySQL 总精度最多 65 位、小数位最多 30 位;MariaDB 的版本限制可能不同。金额等需要精确计算的值应使用此类型。",
|
||||
float: "单精度近似数值。需要精确小数值时请使用 DECIMAL。",
|
||||
double: "双精度近似数值。需要精确小数值时请使用 DECIMAL。",
|
||||
real: "默认是 DOUBLE 的别名;启用 REAL_AS_FLOAT SQL 模式后是 FLOAT 的别名。",
|
||||
bit: "位字段,长度为 1 到 64 位,默认长度为 1 位。",
|
||||
boolean: "TINYINT(1) 的别名:0 表示假,非零表示真。",
|
||||
serial: "BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE 的便捷别名。",
|
||||
date: "日历日期,支持范围为 1000-01-01 到 9999-12-31。",
|
||||
datetime: "不进行时区转换的日期和时间,支持范围为 1000-01-01 00:00:00 到 9999-12-31 23:59:59;小数秒精度为 0 到 6,默认 0。",
|
||||
timestamp: "会在会话时区和 UTC 间转换的日期时间,支持范围为 1970-01-01 00:00:01 UTC 到 2038-01-19 03:14:07 UTC;小数秒精度为 0 到 6,默认 0。",
|
||||
time: "时间或时间间隔,支持范围为 -838:59:59 到 838:59:59;小数秒精度为 0 到 6,默认 0。",
|
||||
year: "年份值。四位年份范围为 1901 到 2155,另支持 0000。",
|
||||
char: "定长字符串,最多 255 个字符;存储时会在右侧补空格。",
|
||||
varchar: "变长字符串;实际最大长度受行大小和字符集约束。",
|
||||
tinytext: "变长文本,最多 255 字节;字符集、行大小和 max_allowed_packet 可能使实际限制更小。",
|
||||
text: "变长文本,最多 65535 字节;字符集、行大小和 max_allowed_packet 可能使实际限制更小。",
|
||||
mediumtext: "变长文本,最多 16777215 字节;字符集、行大小和 max_allowed_packet 可能使实际限制更小。",
|
||||
longtext: "变长文本,最多 4294967295 字节(约 4 GiB);字符集、行大小和 max_allowed_packet 可能使实际限制更小。",
|
||||
binary: "定长二进制字节串,最多 255 字节;存储时以 0x00 补齐。",
|
||||
varbinary: "变长二进制字节串;实际最大长度受行大小约束。",
|
||||
tinyblob: "二进制大对象,最多 255 字节;行大小和 max_allowed_packet 可能使实际限制更小。",
|
||||
blob: "二进制大对象,最多 65535 字节;行大小和 max_allowed_packet 可能使实际限制更小。",
|
||||
mediumblob: "二进制大对象,最多 16777215 字节;行大小和 max_allowed_packet 可能使实际限制更小。",
|
||||
longblob: "二进制大对象,最多 4294967295 字节(约 4 GiB);行大小和 max_allowed_packet 可能使实际限制更小。",
|
||||
enum: "从最多 65535 个声明字面量中选择一个值;调整字面量顺序可能改变已存储值的语义。",
|
||||
set: "从最多 64 个声明成员中选择零个或多个值。",
|
||||
json: "经过校验的 JSON 文档;存储形式和 JSON 功能取决于所连接的服务端产品及版本,文档大小还受 max_allowed_packet 限制。",
|
||||
jsonMysql: "MySQL 原生二进制 JSON 文档类型,支持 JSON 函数和有效性校验;文档大小受 max_allowed_packet 限制。",
|
||||
jsonMariaDb: "MariaDB 的 JSON 是带 JSON_VALID 约束和 utf8mb4_bin 排序规则的 LONGTEXT 别名;文档大小受 max_allowed_packet 限制。",
|
||||
nationalCharacter: "国家字符集字符串别名。在 MySQL 中会使用预定义国家字符集 utf8mb3(已弃用);新建表应显式选择 utf8mb4。",
|
||||
geometry: "可存储任意受支持空间几何类型的值。MySQL InnoDB 的 SPATIAL 索引要求列为 NOT NULL 且指定 SRID 属性。",
|
||||
point: "由一对坐标构成的空间点。MySQL InnoDB 的 SPATIAL 索引要求列为 NOT NULL 且指定 SRID 属性。",
|
||||
linestring: "由一个或多个相连点构成的空间曲线。MySQL InnoDB 的 SPATIAL 索引要求列为 NOT NULL 且指定 SRID 属性。",
|
||||
polygon: "由一个或多个环围成的空间区域。MySQL InnoDB 的 SPATIAL 索引要求列为 NOT NULL 且指定 SRID 属性。",
|
||||
multipoint: "空间点的集合。MySQL InnoDB 的 SPATIAL 索引要求列为 NOT NULL 且指定 SRID 属性。",
|
||||
multilinestring: "空间曲线的集合。MySQL InnoDB 的 SPATIAL 索引要求列为 NOT NULL 且指定 SRID 属性。",
|
||||
multipolygon: "空间多边形的集合。MySQL InnoDB 的 SPATIAL 索引要求列为 NOT NULL 且指定 SRID 属性。",
|
||||
geometrycollection: "可包含混合空间几何类型的集合。MySQL InnoDB 的 SPATIAL 索引要求列为 NOT NULL 且指定 SRID 属性。",
|
||||
inet4: "MariaDB 的 IPv4 地址类型(MariaDB 10.10 起提供),以 4 字节存储 IPv4 地址。",
|
||||
inet6: "MariaDB 兼容的地址类型,用于 IPv6 和映射后的 IPv4 值。",
|
||||
uuid: "兼容服务提供的 128 位通用唯一标识符类型。",
|
||||
vector: "用于相似度检索或机器学习负载的向量类型;是否可用及维度取决于服务端版本。",
|
||||
xmltype: "兼容服务提供的 XML 数据类型。",
|
||||
integerDisplayWidth: "MySQL 整数显示宽度(如 INT(11))已弃用,且不会限制实际存储范围;MySQL 8 新表不应再添加此参数。",
|
||||
zerofill: "ZEROFILL 已在 MySQL 8 中弃用,并会隐式使字段成为 UNSIGNED;应改在应用层处理补零展示。",
|
||||
unsignedNumeric: "DECIMAL、FLOAT 或 DOUBLE 的 UNSIGNED 已在 MySQL 8 中弃用;需要非负取值范围时请使用 CHECK 约束。",
|
||||
floatPrecision: "FLOAT(p) 中 p 为 25 到 53 时实际选择 DOUBLE 精度,而不是 FLOAT;需要双精度时应显式使用 DOUBLE。",
|
||||
floatingPointDisplay: "FLOAT(M,D) 和 DOUBLE(M,D) 已在 MySQL 8 中弃用;请使用无显示参数的 FLOAT/DOUBLE,或对精确数值使用 DECIMAL(M,D)。",
|
||||
},
|
||||
postgresDataTypeHelp: {
|
||||
smallint: "2 字节有符号整数,范围为 -32768 到 32767。",
|
||||
integer: "4 字节有符号整数,范围为 -2147483648 到 2147483647。",
|
||||
bigint: "8 字节有符号整数,范围为 -2^63 到 2^63-1。",
|
||||
serial: "便利语法:创建整数列、序列、NOT NULL 和 nextval 默认值。它本身不保证唯一且序列值可能断号;新建表建议使用 IDENTITY。",
|
||||
numeric: "可指定精度和小数位的精确十进制数。金额等不能接受二进制浮点舍入的值应使用此类型。",
|
||||
real: "4 字节单精度浮点数。FLOAT(p) 的 p 为 1 到 24 时选择此类型。",
|
||||
double: "8 字节双精度浮点数。未指定精度的 FLOAT 是它的别名;FLOAT(p) 的 p 为 25 到 53 时选择它。",
|
||||
money: "受区域格式影响的货币金额,输出和解析依赖 lc_monetary;需要可移植财务数据时通常应使用 NUMERIC。",
|
||||
boolean: "逻辑真、假或空值;BOOL 是 BOOLEAN 的别名。",
|
||||
char: "定长、以空格补齐的字符串。CHARACTER 是别名;若尾随空格有业务含义应谨慎使用。",
|
||||
varchar: "可选长度限制的变长字符串;CHARACTER VARYING 是其别名。",
|
||||
text: "无声明长度限制的变长字符串;存储行为与无长度限制的 VARCHAR 相同。",
|
||||
bytea: "变长二进制字符串;大值可由 TOAST 移到行外存储。",
|
||||
date: "不含时间和时区的日历日期。",
|
||||
time: "不含时区的时间,可指定小数秒精度。",
|
||||
timetz: "带 UTC 偏移的时间;用途有限且通常不推荐。表示时间点应使用 timestamptz。",
|
||||
timestamp: "不进行时区转换的日期时间。适用于时区不属于数据本身的本地钟表时间。",
|
||||
timestamptz: "时间点内部以 UTC 存储,并按会话时区显示;不会保留原始命名时区。",
|
||||
interval: "以月、日和微秒表示的时间间隔;日历单位不是固定时长。",
|
||||
uuid: "128 位通用唯一标识符。",
|
||||
json: "保留原始输入表示的 JSON 文本,解析会校验有效性,但处理通常比 JSONB 慢。",
|
||||
jsonb: "适合处理和索引的分解二进制 JSON;不会保留无意义空白、键顺序或重复键。",
|
||||
xml: "XML 值;是否按文档或内容校验取决于所用 XML 选项。",
|
||||
bit: "定长或变长位串。BIT VARYING 和 VARBIT 是变长形式的别名。",
|
||||
tsvector: "PostgreSQL 全文检索中用于被检索文档的标准化词位;通常使用 GIN 或 GiST 索引。",
|
||||
tsquery: "针对 tsvector 求值的布尔全文检索查询。",
|
||||
cidr: "IPv4 或 IPv6 网络,主机位必须全为零。",
|
||||
inet: "IPv4 或 IPv6 主机地址,可带网络掩码;与 CIDR 不同,主机位可以非零。",
|
||||
macaddr: "MAC 地址。MACADDR 存储 6 字节地址,MACADDR8 存储 8 字节地址。",
|
||||
geometry: "内置二维平面几何类型,无 SRID。地理坐标、投影或高级空间操作应使用 PostGIS。",
|
||||
range: "带开闭边界的单一连续范围,值必须使用兼容子类型;多个不连续范围应使用 multirange。",
|
||||
oid: "无符号对象标识符,是系统标识而非稳定的业务主键;业务数据应使用普通整数或 UUID。",
|
||||
array: "任意元素类型的 PostgreSQL 数组,支持多维。声明类型不会强制数组维度;相较规范化子表,数组通常灵活性更低。",
|
||||
multirange: "同一范围子类型的一组不重叠范围。PostgreSQL 会按子类型规则规范化相邻或重叠成员。",
|
||||
jsonpath: "供 JSONB 查询和谓词函数使用的 SQL/JSON 路径表达式。",
|
||||
pgLsn: "用于复制和恢复坐标的 WAL 位置,不是业务序列号。",
|
||||
pgSnapshot: "事务可见性快照,其文本表示用于数据库快照操作,不适合作为一般业务数据。",
|
||||
},
|
||||
sqliteDataTypeHelp: {
|
||||
integer: "SQLite 使用动态类型和 INTEGER 亲和性;普通列并不强制 INTEGER 存储类。可转换时按有符号整数存储;精确拼写的 INTEGER PRIMARY KEY 是 rowid 的特殊别名。",
|
||||
real: "REAL 亲和性存储 IEEE 754 浮点数。适合近似测量值,不适合精确金额或十进制计算。",
|
||||
text: "TEXT 亲和性按数据库编码存储文本。SQLite 中 VARCHAR(n) 和 CHAR(n) 不会强制 n;若长度重要,请增加 CHECK 约束。",
|
||||
blob: "BLOB 亲和性原样存储字节,不进行类型转换。",
|
||||
numeric: "NUMERIC 亲和性会尝试先将格式正确的数字文本转换为 INTEGER,再转换为 REAL;它不是独立的 SQLite 存储类。DATE、DATETIME 和 TIME 声明同样具有 NUMERIC 亲和性;应统一使用 ISO-8601 TEXT、儒略日 REAL 或 Unix 时间 INTEGER。",
|
||||
},
|
||||
nullable: "可为空",
|
||||
primaryKey: "主键",
|
||||
defaultValue: "默认值",
|
||||
@@ -2077,6 +2178,15 @@ export default withEnglishFallback({
|
||||
createKeyName: "Key",
|
||||
createKeyNamePlaceholder: "user:1",
|
||||
createKeyType: "类型",
|
||||
createKeyTypeHelp: {
|
||||
string: "单个二进制安全值,最大 512 MB。适合缓存值、计数器、标记及简单序列化数据。",
|
||||
hash: "扁平的字段-值映射。单字段操作高效,但完整读取大 Hash 是 O(N)。",
|
||||
list: "有序序列。头尾操作高效;中间随机访问的复杂度随列表长度线性增长。",
|
||||
set: "无序且成员唯一的集合。单成员操作是 O(1),枚举或运算大集合则为线性复杂度。",
|
||||
zset: "按浮点分值排序的唯一成员集合。写入为 O(log N);同分成员按字典序排序。",
|
||||
stream: "追加式事件日志。消费组需要处理 XACK 与待处理消息;应裁剪 Stream 以控制保留量和内存。",
|
||||
json: "可按路径读写的 JSON 文档。需要 RedisJSON/Redis Stack 的 JSON.* 支持,通常比 String 或 Hash 开销更高。",
|
||||
},
|
||||
createValue: "值",
|
||||
createMember: "成员",
|
||||
createField: "字段",
|
||||
|
||||
@@ -1657,6 +1657,107 @@ export default withEnglishFallback({
|
||||
sortBy: "排序方式",
|
||||
},
|
||||
structureEditor: {
|
||||
mysqlDataTypeHelp: {
|
||||
tinyint: "1 位元組整數:有號範圍 -128 至 127;無號範圍 0 至 255。",
|
||||
smallint: "2 位元組整數:有號範圍 -32,768 至 32,767;無號範圍 0 至 65,535。",
|
||||
mediumint: "3 位元組整數:有號範圍 -8,388,608 至 8,388,607;無號範圍 0 至 16,777,215。",
|
||||
int: "4 位元組整數:有號範圍 -2,147,483,648 至 2,147,483,647;無號範圍 0 至 4,294,967,295。",
|
||||
bigint: "8 位元組整數:有號範圍 -2^63 至 2^63-1;無號範圍 0 至 2^64-1。",
|
||||
decimal: "精確定點十進位數。MySQL 最多支援 65 位精度與 30 位小數位;MariaDB 的版本限制可能不同。適用於金額及其他精確值。",
|
||||
float: "單精度近似數值;需要精確十進位值時請使用 DECIMAL。",
|
||||
double: "雙精度近似數值;需要精確十進位值時請使用 DECIMAL。",
|
||||
real: "預設為 DOUBLE 的別名;REAL_AS_FLOAT SQL 模式會使其成為 FLOAT 的別名。",
|
||||
bit: "1 至 64 位元的位元欄位;預設寬度為 1 位元。",
|
||||
boolean: "TINYINT(1) 的別名:0 表示 false,非 0 值表示 true。",
|
||||
serial: "BIGINT UNSIGNED NOT NULL AUTO_INCREMENT UNIQUE 的便利別名。",
|
||||
date: "日曆日期;支援範圍為 1000-01-01 至 9999-12-31。",
|
||||
datetime: "不進行時區轉換的日期與時間;支援範圍為 1000-01-01 00:00:00 至 9999-12-31 23:59:59,小數秒精度為 0 至 6(預設 0)。",
|
||||
timestamp: "在工作階段時區與 UTC 間轉換的日期與時間;支援範圍為 1970-01-01 00:00:01 UTC 至 2038-01-19 03:14:07 UTC,小數秒精度為 0 至 6(預設 0)。",
|
||||
time: "一天中的時間或已耗時間;支援範圍為 -838:59:59 至 838:59:59,小數秒精度為 0 至 6(預設 0)。",
|
||||
year: "年份值;四位數範圍為 1901 至 2155,另包含 0000。",
|
||||
char: "固定長度字串,最多 255 個字元;儲存值會在右側以空白補齊。",
|
||||
varchar: "可變長度字串;實際最大長度受列大小與字元集限制。",
|
||||
tinytext: "可變長度文字,最多 255 位元組;字元集、列大小與 max_allowed_packet 可能使實際上限更低。",
|
||||
text: "可變長度文字,最多 65,535 位元組;字元集、列大小與 max_allowed_packet 可能使實際上限更低。",
|
||||
mediumtext: "可變長度文字,最多 16,777,215 位元組;字元集、列大小與 max_allowed_packet 可能使實際上限更低。",
|
||||
longtext: "可變長度文字,最多 4,294,967,295 位元組(約 4 GiB);字元集、列大小與 max_allowed_packet 可能使實際上限更低。",
|
||||
binary: "固定長度二進位位元組字串,最多 255 位元組;儲存值會以 0x00 補齊。",
|
||||
varbinary: "可變長度二進位位元組字串;實際最大長度受列大小限制。",
|
||||
tinyblob: "二進位大型物件,最多 255 位元組;列大小與 max_allowed_packet 可能使實際上限更低。",
|
||||
blob: "二進位大型物件,最多 65,535 位元組;列大小與 max_allowed_packet 可能使實際上限更低。",
|
||||
mediumblob: "二進位大型物件,最多 16,777,215 位元組;列大小與 max_allowed_packet 可能使實際上限更低。",
|
||||
longblob: "二進位大型物件,最多 4,294,967,295 位元組(約 4 GiB);列大小與 max_allowed_packet 可能使實際上限更低。",
|
||||
enum: "從最多 65,535 個已宣告常值中選擇一個值;改變常值順序可能改變已儲存值的語意。",
|
||||
set: "從最多 64 個已宣告成員中選擇零個或多個值。",
|
||||
json: "經驗證的 JSON 文件;儲存表示與 JSON 功能支援取決於所連線伺服器的產品及版本;文件大小亦受 max_allowed_packet 限制。",
|
||||
jsonMysql: "MySQL 原生的二進位 JSON 文件類型,具備 JSON 函式與驗證;文件大小受 max_allowed_packet 限制。",
|
||||
jsonMariaDb: "MariaDB 的 JSON 是帶有 JSON_VALID 約束與 utf8mb4_bin 定序的 LONGTEXT 別名;文件大小受 max_allowed_packet 限制。",
|
||||
nationalCharacter: "國家字元字串別名;MySQL 使用預先定義的國家字元集(已淘汰的 utf8mb3),新綱要應明確選用 utf8mb4。",
|
||||
geometry: "任何受支援幾何類型的空間值;MySQL InnoDB SPATIAL 索引要求欄位為 NOT NULL 並具有 SRID 屬性。",
|
||||
point: "含一組座標的空間點;MySQL InnoDB SPATIAL 索引要求欄位為 NOT NULL 並具有 SRID 屬性。",
|
||||
linestring: "由一或多個相連點組成的空間曲線;MySQL InnoDB SPATIAL 索引要求欄位為 NOT NULL 並具有 SRID 屬性。",
|
||||
polygon: "由一或多個環封閉的空間面;MySQL InnoDB SPATIAL 索引要求欄位為 NOT NULL 並具有 SRID 屬性。",
|
||||
multipoint: "空間點集合;MySQL InnoDB SPATIAL 索引要求欄位為 NOT NULL 並具有 SRID 屬性。",
|
||||
multilinestring: "空間曲線集合;MySQL InnoDB SPATIAL 索引要求欄位為 NOT NULL 並具有 SRID 屬性。",
|
||||
multipolygon: "空間多邊形集合;MySQL InnoDB SPATIAL 索引要求欄位為 NOT NULL 並具有 SRID 屬性。",
|
||||
geometrycollection: "可包含混合空間幾何類型的集合;MySQL InnoDB SPATIAL 索引要求欄位為 NOT NULL 並具有 SRID 屬性。",
|
||||
inet4: "MariaDB IPv4 位址類型(MariaDB 10.10 起可用);以四個位元組儲存 IPv4 位址。",
|
||||
inet6: "MariaDB 相容的 IPv6 與對應 IPv4 值位址類型。",
|
||||
uuid: "相容伺服器提供的 128 位元通用唯一識別碼類型。",
|
||||
vector: "用於相似度或機器學習工作負載的向量資料類型;可用性與維度取決於伺服器版本。",
|
||||
xmltype: "相容伺服器提供的 XML 資料類型。",
|
||||
integerDisplayWidth: "MySQL 整數顯示寬度(例如 INT(11))已淘汰,且不會限制儲存範圍;不要在新的 MySQL 8 綱要中使用。",
|
||||
zerofill: "ZEROFILL 在 MySQL 8 已淘汰,且會隱含使欄位成為 UNSIGNED;請改在應用程式中明確格式化。",
|
||||
unsignedNumeric: "DECIMAL、FLOAT 或 DOUBLE 的 UNSIGNED 在 MySQL 8 已淘汰;需要非負值範圍時請使用 CHECK 約束。",
|
||||
floatPrecision: "對於 FLOAT(p),p 為 25 至 53 時會選擇 DOUBLE 精度而非 FLOAT;若需要此行為,請明確使用 DOUBLE。",
|
||||
floatingPointDisplay: "FLOAT(M,D) 與 DOUBLE(M,D) 在 MySQL 8 已淘汰;請使用不含顯示參數的 FLOAT/DOUBLE,或以 DECIMAL(M,D) 表示精確值。",
|
||||
},
|
||||
postgresDataTypeHelp: {
|
||||
smallint: "2 位元組有號整數,範圍為 -32,768 至 32,767。",
|
||||
integer: "4 位元組有號整數,範圍為 -2,147,483,648 至 2,147,483,647。",
|
||||
bigint: "8 位元組有號整數,範圍為 -2^63 至 2^63-1。",
|
||||
serial: "便利語法,會建立整數欄位、序列、NOT NULL 與 nextval 預設值;本身不保證唯一且序列值可能有缺口,新綱要建議使用 IDENTITY。",
|
||||
numeric: "可自訂精度與小數位的精確十進位數;適合金額及不能使用二進位浮點捨入的值。",
|
||||
real: "4 位元組單精度浮點數;FLOAT(p) 的 p 為 1 至 24 時會選擇此類型。",
|
||||
double: "8 位元組雙精度浮點數;未指定精度的 FLOAT 是其別名,FLOAT(p) 的 p 為 25 至 53 時會選擇此類型。",
|
||||
money: "依地區格式化的貨幣金額;輸出與解析取決於 lc_monetary,NUMERIC 通常更適合可攜的財務資料。",
|
||||
boolean: "邏輯 true、false 或 null 值;BOOL 是 BOOLEAN 的別名。",
|
||||
char: "以空白補齊的固定長度字串;CHARACTER 是別名,尾端空白有意義時應避免使用。",
|
||||
varchar: "可選擇字元長度上限的可變長度字串;CHARACTER VARYING 是別名。",
|
||||
text: "未宣告長度上限的可變長度字串;儲存行為與不設長度上限的 VARCHAR 相同。",
|
||||
bytea: "可變長度二進位字串;大型值可由 TOAST 儲存在列外。",
|
||||
date: "不含一天中時間或時區的日曆日期。",
|
||||
time: "不含時區的一天中時間,可選擇小數秒精度。",
|
||||
timetz: "一天中時間加上 UTC 位移;實用性有限且通常不建議使用,表示某個時間點應使用 timestamptz。",
|
||||
timestamp: "不進行時區轉換的日期與時間;適合時區不屬於資料一部分的本地時鐘值。",
|
||||
timestamptz: "內部以 UTC 儲存、依工作階段時區顯示的時間點;不保留原始具名時區。",
|
||||
interval: "以月、日和微秒表示的持續時間;日曆單位不是固定長度的持續時間。",
|
||||
uuid: "128 位元通用唯一識別碼。",
|
||||
json: "保留原始輸入表示的 JSON 文字;會驗證解析,但處理速度通常慢於 JSONB。",
|
||||
jsonb: "為處理與索引最佳化的分解式二進位 JSON 表示;不保留無意義空白、鍵順序或重複鍵。",
|
||||
xml: "XML 值;是否驗證文件或內容取決於所使用的 XML 選項。",
|
||||
bit: "固定或可變長度位元字串;BIT VARYING 與 VARBIT 是可變長度別名。",
|
||||
tsvector: "PostgreSQL 全文搜尋中作為可搜尋文件端的正規化詞素;通常以 GIN 或 GiST 建立索引。",
|
||||
tsquery: "針對 tsvector 評估的布林全文搜尋查詢。",
|
||||
cidr: "主機位元被限制為零的 IPv4 或 IPv6 網路。",
|
||||
inet: "可選擇網路遮罩的 IPv4 或 IPv6 主機位址;不同於 CIDR,主機位元可以非零。",
|
||||
macaddr: "MAC 位址;MACADDR 儲存 6 位元組位址,MACADDR8 儲存 8 位元組位址。",
|
||||
geometry: "內建二維幾何類型;PostgreSQL 的 geometry 為平面且沒有 SRID,地理座標、投影或進階空間操作請使用 PostGIS。",
|
||||
range: "具有含括/不含括邊界的單一連續範圍;範圍值需要相容子類型,多個不連續範圍請使用 multirange 類型。",
|
||||
oid: "無號物件識別碼;它是系統識別碼而非持久的應用程式主鍵,應用資料請使用一般整數或 UUID。",
|
||||
array: "任何元素類型的 PostgreSQL 陣列,包含多維陣列;宣告類型不會強制陣列維度,且陣列通常不如正規化子列彈性。",
|
||||
multirange: "相符範圍子類型的不重疊範圍集合;PostgreSQL 會依子類型規則正規化相鄰或重疊成員。",
|
||||
jsonpath: "供 JSONB 查詢與述詞函式使用的 SQL/JSON 路徑運算式。",
|
||||
pgLsn: "用於複寫與復原座標的預寫式日誌(WAL)位置;不是應用程式序號。",
|
||||
pgSnapshot: "交易可見性快照;其文字表示供資料庫快照操作使用,並非一般應用程式資料。",
|
||||
},
|
||||
sqliteDataTypeHelp: {
|
||||
integer: "SQLite 使用動態型別與 INTEGER 親和性;一般欄位不會強制 INTEGER 儲存類別。值在可能時會儲存為有號整數;精確拼寫的 INTEGER PRIMARY KEY 是 rowid 的特殊別名。",
|
||||
real: "REAL 親和性儲存 IEEE 754 浮點值;適合近似量測,不適合精確金額或十進位計算。",
|
||||
text: "TEXT 親和性會以資料庫編碼儲存文字。SQLite 中 VARCHAR(n) 與 CHAR(n) 不會強制 n;長度上限重要時請加上 CHECK 約束。",
|
||||
blob: "BLOB 親和性會完全依原樣儲存位元組,不進行型別轉換。",
|
||||
numeric: "NUMERIC 親和性會先嘗試將格式正確的數值文字轉為 INTEGER,再轉為 REAL;它不是獨立的 SQLite 儲存類別。DATE、DATETIME 與 TIME 宣告也具有 NUMERIC 親和性;應一致地儲存 ISO-8601 TEXT、儒略日 REAL 或 Unix 時間 INTEGER。",
|
||||
},
|
||||
title: "編輯資料表結構",
|
||||
createTitle: "建立資料表",
|
||||
editTabTitle: "編輯: {tableName}",
|
||||
@@ -1796,6 +1897,15 @@ export default withEnglishFallback({
|
||||
noJoinSql: "沒有可複製的關係 SQL",
|
||||
},
|
||||
redis: {
|
||||
createKeyTypeHelp: {
|
||||
string: "最大 512 MB 的單一值;適合快取與計數器。",
|
||||
hash: "扁平欄位值映射;完整讀取大型 Hash 為 O(N)。",
|
||||
list: "有序序列;中間隨機存取為線性。",
|
||||
set: "無序唯一成員集合;列舉大型集合為線性。",
|
||||
zset: "含浮點分數的唯一成員;寫入為 O(log N)。",
|
||||
stream: "追加式日誌;應處理 XACK、待處理項目與裁剪。",
|
||||
json: "可依路徑存取 JSON;需要 RedisJSON/Redis Stack,通常比 String 或 Hash 開銷高。",
|
||||
},
|
||||
selectKey: "選擇一個 key 檢視值",
|
||||
noKeys: "未找到 key",
|
||||
noKeysInScanHint: "目前掃描範圍內未找到匹配的 key",
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { optionHelpPanelOffsetTop } from "@/lib/common/optionHelpPanelOffset";
|
||||
|
||||
describe("optionHelpPanelOffsetTop", () => {
|
||||
it("aligns a help panel with the active option when there is room", () => {
|
||||
expect(optionHelpPanelOffsetTop({ activeItemTop: 48, listCardHeight: 260, panelHeight: 80 })).toBe(48);
|
||||
});
|
||||
|
||||
it("does not produce a negative offset", () => {
|
||||
expect(optionHelpPanelOffsetTop({ activeItemTop: -12, listCardHeight: 260, panelHeight: 80 })).toBe(0);
|
||||
});
|
||||
|
||||
it("clamps the panel upward at the list card bottom", () => {
|
||||
expect(optionHelpPanelOffsetTop({ activeItemTop: 210, listCardHeight: 260, panelHeight: 80 })).toBe(180);
|
||||
});
|
||||
|
||||
it("pins a panel taller than the list card to the top", () => {
|
||||
expect(optionHelpPanelOffsetTop({ activeItemTop: 48, listCardHeight: 80, panelHeight: 120 })).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { SEARCHABLE_SELECT_HELP_PANEL_ALIGN, searchableSelectKeyboardTooltipOption, searchableSelectSelectedOrFirstHelpOption } from "@/lib/common/searchableSelectTooltip";
|
||||
|
||||
describe("searchableSelectKeyboardTooltipOption", () => {
|
||||
it("anchors the help panel to the trigger's left edge", () => {
|
||||
expect(SEARCHABLE_SELECT_HELP_PANEL_ALIGN).toBe("start");
|
||||
});
|
||||
|
||||
it("activates the highlighted option help during Arrow-key navigation", () => {
|
||||
const optionTooltip = (option: string) => (option === "json" ? "Validated JSON document" : undefined);
|
||||
|
||||
// ArrowDown moves highlight from int (index 0) to json (index 1) while the search input retains focus.
|
||||
expect(searchableSelectKeyboardTooltipOption(["int", "json"], 0, optionTooltip)).toBeUndefined();
|
||||
expect(searchableSelectKeyboardTooltipOption(["int", "json"], 1, optionTooltip)).toBe("json");
|
||||
});
|
||||
|
||||
it("does not activate help for the initial unhighlighted list state", () => {
|
||||
const optionTooltip = () => "Type help";
|
||||
|
||||
expect(searchableSelectKeyboardTooltipOption(["int"], -1, optionTooltip)).toBeUndefined();
|
||||
});
|
||||
|
||||
it("activates selected help on open and the first matching option after search", () => {
|
||||
const optionTooltip = (option: string) => (option === "json" || option === "int" ? `${option} help` : undefined);
|
||||
|
||||
expect(searchableSelectSelectedOrFirstHelpOption(["int", "json"], "json", optionTooltip)).toBe("json");
|
||||
expect(searchableSelectSelectedOrFirstHelpOption(["int", "json"], "unknown", optionTooltip)).toBe("int");
|
||||
expect(searchableSelectSelectedOrFirstHelpOption(["custom"], "unknown", optionTooltip)).toBeUndefined();
|
||||
expect(searchableSelectSelectedOrFirstHelpOption(["dynamic_unknown", "json"], "dynamic_unknown", optionTooltip)).toBe("json");
|
||||
});
|
||||
|
||||
it("does not activate a tooltip when the optional callback is absent or returns no content", () => {
|
||||
expect(searchableSelectKeyboardTooltipOption(["int", "json"], 1)).toBeUndefined();
|
||||
expect(searchableSelectKeyboardTooltipOption(["int", "json"], 0, () => undefined)).toBeUndefined();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { REDIS_CREATE_KEY_TYPE_VALUES, getRedisCreateKeyTypeHelp, redisCreateKeyTypeHelpOptionOnOpen, shouldActivateRedisCreateKeyTypeHelpOnFocus } from "@/lib/redis/redisCreateKeyTypeHelp";
|
||||
import en from "@/i18n/locales/en";
|
||||
import zhCN from "@/i18n/locales/zh-CN";
|
||||
|
||||
function redisCreateTypeHelpMessages(locale: unknown): Record<string, unknown> {
|
||||
const messages = locale as { redis?: { createKeyTypeHelp?: Record<string, unknown> } };
|
||||
return messages.redis?.createKeyTypeHelp ?? {};
|
||||
}
|
||||
|
||||
describe("getRedisCreateKeyTypeHelp", () => {
|
||||
it("covers every key type offered by the Create key dialog", () => {
|
||||
for (const type of REDIS_CREATE_KEY_TYPE_VALUES) {
|
||||
expect(getRedisCreateKeyTypeHelp(type)).toEqual({ key: type });
|
||||
}
|
||||
});
|
||||
|
||||
it("normalizes casing and leaves unsupported types undocumented", () => {
|
||||
expect(getRedisCreateKeyTypeHelp(" ZSET ")).toEqual({ key: "zset" });
|
||||
expect(getRedisCreateKeyTypeHelp("bitmap")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("activates help only for focus reached through Arrow navigation", () => {
|
||||
expect(shouldActivateRedisCreateKeyTypeHelpOnFocus({ openedByArrow: true, keyboardNavigating: true })).toBe(true);
|
||||
expect(shouldActivateRedisCreateKeyTypeHelpOnFocus({ openedByArrow: false, keyboardNavigating: true })).toBe(true);
|
||||
expect(shouldActivateRedisCreateKeyTypeHelpOnFocus({ openedByArrow: false, keyboardNavigating: false })).toBe(false);
|
||||
});
|
||||
|
||||
it("shows the selected offered type's help when the Select opens", () => {
|
||||
expect(redisCreateKeyTypeHelpOptionOnOpen("string")).toBe("string");
|
||||
expect(redisCreateKeyTypeHelpOptionOnOpen(" JSON ")).toBe("json");
|
||||
expect(redisCreateKeyTypeHelpOptionOnOpen("bitmap")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("has English and Simplified Chinese copy for every offered type", () => {
|
||||
const english = redisCreateTypeHelpMessages(en);
|
||||
const chinese = redisCreateTypeHelpMessages(zhCN);
|
||||
|
||||
for (const type of REDIS_CREATE_KEY_TYPE_VALUES) {
|
||||
expect(english[type], `English: ${type}`).toEqual(expect.any(String));
|
||||
expect(chinese[type], `Simplified Chinese: ${type}`).toEqual(expect.any(String));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,96 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DATA_TYPE_OPTIONS } from "@/lib/table/tableStructureEditorState";
|
||||
import { getMysqlDataTypeHelp } from "@/lib/table/mysqlDataTypeHelp";
|
||||
import en from "@/i18n/locales/en";
|
||||
import zhCN from "@/i18n/locales/zh-CN";
|
||||
|
||||
function mysqlHelpMessages(locale: unknown): Record<string, unknown> {
|
||||
const messages = locale as { structureEditor?: { mysqlDataTypeHelp?: Record<string, unknown> } };
|
||||
return messages.structureEditor?.mysqlDataTypeHelp ?? {};
|
||||
}
|
||||
|
||||
describe("getMysqlDataTypeHelp", () => {
|
||||
it("normalizes MySQL attributes, aliases, parameters, casing, and whitespace", () => {
|
||||
expect(getMysqlDataTypeHelp(" INT(11) UNSIGNED ")).toEqual({ key: "int", warningKeys: ["integerDisplayWidth"] });
|
||||
expect(getMysqlDataTypeHelp("integer zerofill")).toEqual({ key: "int", warningKeys: ["zerofill"] });
|
||||
expect(getMysqlDataTypeHelp("NUMERIC(10, 2)")).toEqual({ key: "decimal" });
|
||||
expect(getMysqlDataTypeHelp(" double precision ")).toEqual({ key: "double" });
|
||||
expect(getMysqlDataTypeHelp("BOOL")).toEqual({ key: "boolean" });
|
||||
expect(getMysqlDataTypeHelp("DEC(20, 6)")).toEqual({ key: "decimal" });
|
||||
expect(getMysqlDataTypeHelp("CHARACTER VARYING(120)")).toEqual({ key: "varchar" });
|
||||
expect(getMysqlDataTypeHelp("CHARACTER(12)")).toEqual({ key: "char" });
|
||||
expect(getMysqlDataTypeHelp("CHAR VARYING(120)")).toEqual({ key: "varchar" });
|
||||
expect(getMysqlDataTypeHelp("CHAR BYTE")).toEqual({ key: "char" });
|
||||
});
|
||||
|
||||
it("covers phpMyAdmin's additional MySQL type aliases for dynamic type lists", () => {
|
||||
expect(getMysqlDataTypeHelp("FIXED")?.key).toBe("decimal");
|
||||
expect(getMysqlDataTypeHelp("FLOAT4")?.key).toBe("float");
|
||||
expect(getMysqlDataTypeHelp("FLOAT8")?.key).toBe("double");
|
||||
expect(getMysqlDataTypeHelp("INT1")?.key).toBe("tinyint");
|
||||
expect(getMysqlDataTypeHelp("INT2")?.key).toBe("smallint");
|
||||
expect(getMysqlDataTypeHelp("INT3")?.key).toBe("mediumint");
|
||||
expect(getMysqlDataTypeHelp("INT4")?.key).toBe("int");
|
||||
expect(getMysqlDataTypeHelp("INT8")?.key).toBe("bigint");
|
||||
expect(getMysqlDataTypeHelp("MIDDLEINT")?.key).toBe("mediumint");
|
||||
expect(getMysqlDataTypeHelp("LONG")?.key).toBe("mediumtext");
|
||||
expect(getMysqlDataTypeHelp("LONG VARCHAR")?.key).toBe("mediumtext");
|
||||
expect(getMysqlDataTypeHelp("LONG VARBINARY")?.key).toBe("mediumblob");
|
||||
expect(getMysqlDataTypeHelp("GEOMCOLLECTION")?.key).toBe("geometrycollection");
|
||||
expect(getMysqlDataTypeHelp("NCHAR(30)")?.key).toBe("nationalCharacter");
|
||||
expect(getMysqlDataTypeHelp("NVARCHAR(30)")?.key).toBe("nationalCharacter");
|
||||
expect(getMysqlDataTypeHelp("NATIONAL CHARACTER VARYING(30)")?.key).toBe("nationalCharacter");
|
||||
});
|
||||
|
||||
it("documents numeric, text, binary, enum, temporal, JSON, and spatial types", () => {
|
||||
expect(getMysqlDataTypeHelp("bigint unsigned")?.key).toBe("bigint");
|
||||
expect(getMysqlDataTypeHelp("varchar")?.key).toBe("varchar");
|
||||
expect(getMysqlDataTypeHelp("mediumblob")?.key).toBe("mediumblob");
|
||||
expect(getMysqlDataTypeHelp("enum")?.key).toBe("enum");
|
||||
expect(getMysqlDataTypeHelp("timestamp")?.key).toBe("timestamp");
|
||||
expect(getMysqlDataTypeHelp("json")?.key).toBe("json");
|
||||
expect(getMysqlDataTypeHelp("geometrycollection")?.key).toBe("geometrycollection");
|
||||
});
|
||||
|
||||
it("uses product-specific JSON help when the connection profile identifies the server", () => {
|
||||
expect(getMysqlDataTypeHelp("json")).toEqual({ key: "json" });
|
||||
expect(getMysqlDataTypeHelp("json", { product: "mysql" })).toEqual({ key: "jsonMysql" });
|
||||
expect(getMysqlDataTypeHelp("json", { product: "mariadb" })).toEqual({ key: "jsonMariaDb" });
|
||||
});
|
||||
|
||||
it("warns about MySQL 8 numeric syntax based on the exact raw type", () => {
|
||||
expect(getMysqlDataTypeHelp("INT(11) ZEROFILL")).toEqual({ key: "int", warningKeys: ["integerDisplayWidth", "zerofill"] });
|
||||
expect(getMysqlDataTypeHelp("DECIMAL(10,2) UNSIGNED")).toEqual({ key: "decimal", warningKeys: ["unsignedNumeric"] });
|
||||
expect(getMysqlDataTypeHelp("FLOAT(30)")).toEqual({ key: "float", warningKeys: ["floatPrecision"] });
|
||||
expect(getMysqlDataTypeHelp("DOUBLE(10,2) UNSIGNED")).toEqual({ key: "double", warningKeys: ["unsignedNumeric", "floatingPointDisplay"] });
|
||||
});
|
||||
|
||||
it("also recognizes compatible-server extensions and leaves unknown custom types alone", () => {
|
||||
expect(getMysqlDataTypeHelp("inet4")?.key).toBe("inet4");
|
||||
expect(getMysqlDataTypeHelp("inet6")?.key).toBe("inet6");
|
||||
expect(getMysqlDataTypeHelp("uuid")?.key).toBe("uuid");
|
||||
expect(getMysqlDataTypeHelp("vector(1536)")?.key).toBe("vector");
|
||||
expect(getMysqlDataTypeHelp("xmltype")?.key).toBe("xmltype");
|
||||
expect(getMysqlDataTypeHelp("my_private_type")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("covers every built-in MySQL type option", () => {
|
||||
const undocumented = DATA_TYPE_OPTIONS.mysql.filter((type) => !getMysqlDataTypeHelp(type));
|
||||
expect(undocumented).toEqual([]);
|
||||
});
|
||||
|
||||
it("has an English and Simplified Chinese message for every supported help key", () => {
|
||||
const options = [...DATA_TYPE_OPTIONS.mysql, "inet4", "inet6", "uuid", "vector", "xmltype", "character", "char varying", "char byte", "geomcollection", "nchar", "nvarchar", "national character varying", "int(11) zerofill", "decimal(10,2) unsigned", "float(30)", "double(10,2)"];
|
||||
const english = mysqlHelpMessages(en);
|
||||
const chinese = mysqlHelpMessages(zhCN);
|
||||
|
||||
for (const option of options) {
|
||||
const key = getMysqlDataTypeHelp(option)?.key;
|
||||
expect(key, option).toBeTruthy();
|
||||
for (const helpKey of [key!, ...(getMysqlDataTypeHelp(option)?.warningKeys ?? [])]) {
|
||||
expect(english[helpKey], `English: ${option}`).toEqual(expect.any(String));
|
||||
expect(chinese[helpKey], `Simplified Chinese: ${option}`).toEqual(expect.any(String));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,64 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DATA_TYPE_OPTIONS } from "@/lib/table/tableStructureEditorState";
|
||||
import { getPostgresDataTypeHelp } from "@/lib/table/postgresDataTypeHelp";
|
||||
import en from "@/i18n/locales/en";
|
||||
import zhCN from "@/i18n/locales/zh-CN";
|
||||
|
||||
function postgresHelpMessages(locale: unknown): Record<string, unknown> {
|
||||
const messages = locale as { structureEditor?: { postgresDataTypeHelp?: Record<string, unknown> } };
|
||||
return messages.structureEditor?.postgresDataTypeHelp ?? {};
|
||||
}
|
||||
|
||||
describe("getPostgresDataTypeHelp", () => {
|
||||
it("covers every built-in PostgreSQL type option", () => {
|
||||
expect(DATA_TYPE_OPTIONS.postgres.filter((type) => !getPostgresDataTypeHelp(type))).toEqual([]);
|
||||
});
|
||||
|
||||
it("normalizes aliases and float precision parameters", () => {
|
||||
expect(getPostgresDataTypeHelp("int2")).toEqual({ key: "smallint" });
|
||||
expect(getPostgresDataTypeHelp("INT4")).toEqual({ key: "integer" });
|
||||
expect(getPostgresDataTypeHelp("int8")).toEqual({ key: "bigint" });
|
||||
expect(getPostgresDataTypeHelp("decimal(10, 2)")).toEqual({ key: "numeric" });
|
||||
expect(getPostgresDataTypeHelp("float")).toEqual({ key: "double" });
|
||||
expect(getPostgresDataTypeHelp("float(24)")).toEqual({ key: "real" });
|
||||
expect(getPostgresDataTypeHelp("float(25)")).toEqual({ key: "double" });
|
||||
expect(getPostgresDataTypeHelp("float(0)")).toBeUndefined();
|
||||
expect(getPostgresDataTypeHelp("float(54)")).toBeUndefined();
|
||||
expect(getPostgresDataTypeHelp("float(1.5)")).toBeUndefined();
|
||||
expect(getPostgresDataTypeHelp("float(precision)")).toBeUndefined();
|
||||
expect(getPostgresDataTypeHelp("character varying(255)")).toEqual({ key: "varchar" });
|
||||
});
|
||||
|
||||
it("documents serial, temporal aliases, dynamic built-ins, and arrays", () => {
|
||||
expect(getPostgresDataTypeHelp("bigserial")).toEqual({ key: "serial" });
|
||||
expect(getPostgresDataTypeHelp("timestamp with time zone")).toEqual({ key: "timestamptz" });
|
||||
expect(getPostgresDataTypeHelp("int4multirange")).toEqual({ key: "multirange" });
|
||||
expect(getPostgresDataTypeHelp("jsonpath")).toEqual({ key: "jsonpath" });
|
||||
expect(getPostgresDataTypeHelp("pg_lsn")).toEqual({ key: "pgLsn" });
|
||||
expect(getPostgresDataTypeHelp("pg_snapshot")).toEqual({ key: "pgSnapshot" });
|
||||
expect(getPostgresDataTypeHelp("uuid[][]")).toEqual({ key: "array" });
|
||||
expect(getPostgresDataTypeHelp("my_schema.order_status[]")).toEqual({ key: "array" });
|
||||
});
|
||||
|
||||
it("leaves extensions and named user types undocumented", () => {
|
||||
expect(getPostgresDataTypeHelp("vector(1536)")).toBeUndefined();
|
||||
expect(getPostgresDataTypeHelp("geometry(Point, 4326)")).toBeUndefined();
|
||||
expect(getPostgresDataTypeHelp("citext")).toBeUndefined();
|
||||
expect(getPostgresDataTypeHelp("hstore")).toBeUndefined();
|
||||
expect(getPostgresDataTypeHelp("order_status")).toBeUndefined();
|
||||
expect(getPostgresDataTypeHelp("order_status_multirange")).toBeUndefined();
|
||||
});
|
||||
|
||||
it("has English and Simplified Chinese copy for every supported static key", () => {
|
||||
const english = postgresHelpMessages(en);
|
||||
const chinese = postgresHelpMessages(zhCN);
|
||||
const dynamic = ["int4multirange", "jsonpath", "pg_lsn", "pg_snapshot", "any_type[]"];
|
||||
|
||||
for (const option of [...DATA_TYPE_OPTIONS.postgres, ...dynamic]) {
|
||||
const key = getPostgresDataTypeHelp(option)?.key;
|
||||
expect(key, option).toBeTruthy();
|
||||
expect(english[key!], `English: ${option}`).toEqual(expect.any(String));
|
||||
expect(chinese[key!], `Simplified Chinese: ${option}`).toEqual(expect.any(String));
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { DATA_TYPE_OPTIONS } from "@/lib/table/tableStructureEditorState";
|
||||
import { getSqliteDataTypeHelp } from "@/lib/table/sqliteDataTypeHelp";
|
||||
import en from "@/i18n/locales/en";
|
||||
import zhCN from "@/i18n/locales/zh-CN";
|
||||
|
||||
function sqliteHelpMessages(locale: unknown): Record<string, unknown> {
|
||||
const messages = locale as { structureEditor?: { sqliteDataTypeHelp?: Record<string, unknown> } };
|
||||
return messages.structureEditor?.sqliteDataTypeHelp ?? {};
|
||||
}
|
||||
|
||||
describe("getSqliteDataTypeHelp", () => {
|
||||
it("covers every built-in SQLite type option", () => {
|
||||
expect(DATA_TYPE_OPTIONS.sqlite.filter((type) => !getSqliteDataTypeHelp(type))).toEqual([]);
|
||||
});
|
||||
|
||||
it("normalizes whitespace, casing, and declared type parameters", () => {
|
||||
expect(getSqliteDataTypeHelp(" INTEGER ")).toEqual({ key: "integer" });
|
||||
expect(getSqliteDataTypeHelp("VARCHAR(255)")).toEqual({ key: "text" });
|
||||
expect(getSqliteDataTypeHelp(" decimal(10, 2) ")).toEqual({ key: "numeric" });
|
||||
});
|
||||
|
||||
it("applies SQLite's ordered declared-type affinity rules", () => {
|
||||
expect(getSqliteDataTypeHelp("char(12)")).toEqual({ key: "text" });
|
||||
expect(getSqliteDataTypeHelp("boolean")).toEqual({ key: "numeric" });
|
||||
expect(getSqliteDataTypeHelp("unsigned big int")).toEqual({ key: "integer" });
|
||||
expect(getSqliteDataTypeHelp("double precision")).toEqual({ key: "real" });
|
||||
expect(getSqliteDataTypeHelp("date")).toEqual({ key: "numeric" });
|
||||
expect(getSqliteDataTypeHelp("datetime")).toEqual({ key: "numeric" });
|
||||
expect(getSqliteDataTypeHelp("time")).toEqual({ key: "numeric" });
|
||||
expect(getSqliteDataTypeHelp("VARCHAR2")).toEqual({ key: "text" });
|
||||
expect(getSqliteDataTypeHelp("CHARACTER")).toEqual({ key: "text" });
|
||||
expect(getSqliteDataTypeHelp("INT64")).toEqual({ key: "integer" });
|
||||
expect(getSqliteDataTypeHelp("FLOATING POINT")).toEqual({ key: "integer" });
|
||||
expect(getSqliteDataTypeHelp("STRING")).toEqual({ key: "numeric" });
|
||||
expect(getSqliteDataTypeHelp("application_status")).toEqual({ key: "numeric" });
|
||||
expect(getSqliteDataTypeHelp("")).toEqual({ key: "blob" });
|
||||
});
|
||||
|
||||
it("has English and Simplified Chinese copy for static and common help keys", () => {
|
||||
const english = sqliteHelpMessages(en);
|
||||
const chinese = sqliteHelpMessages(zhCN);
|
||||
const types = [...DATA_TYPE_OPTIONS.sqlite, "varchar", "boolean", "datetime"];
|
||||
|
||||
for (const type of types) {
|
||||
const key = getSqliteDataTypeHelp(type)?.key;
|
||||
expect(key, type).toBeTruthy();
|
||||
expect(english[key!], `English: ${type}`).toEqual(expect.any(String));
|
||||
expect(chinese[key!], `Simplified Chinese: ${type}`).toEqual(expect.any(String));
|
||||
}
|
||||
});
|
||||
|
||||
it("calls out SQLite affinity and its important storage exceptions", () => {
|
||||
const english = sqliteHelpMessages(en);
|
||||
|
||||
expect(english.integer).toContain("rowid");
|
||||
expect(english.integer).toContain("does not enforce");
|
||||
expect(english.real).toContain("IEEE 754");
|
||||
expect(english.text).toContain("do not enforce n");
|
||||
expect(english.blob).toContain("without type conversion");
|
||||
expect(english.numeric).toContain("not a separate SQLite storage class");
|
||||
});
|
||||
});
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
combineDataTypeForDatabase,
|
||||
createColumnDrafts,
|
||||
dataTypeLengthInputValue,
|
||||
DATA_TYPE_OPTIONS,
|
||||
defaultNewColumnDataType,
|
||||
getDefaultLengthForType,
|
||||
hasExistingColumnTypeChange,
|
||||
isDataTypeLengthDisabled,
|
||||
@@ -109,6 +111,30 @@ describe("tableStructureEditorState", () => {
|
||||
expect(getDefaultLengthForType("mysql", "integer")).toBe("11");
|
||||
});
|
||||
|
||||
it("uses MySQL 8-safe defaults only when the native MySQL profile is known", () => {
|
||||
const mysql8Defaults = { omitMysqlDeprecatedDefaults: true };
|
||||
|
||||
expect(getDefaultLengthForType("mysql", "int", mysql8Defaults)).toBe("");
|
||||
expect(getDefaultLengthForType("mysql", "bigint unsigned", mysql8Defaults)).toBe("");
|
||||
expect(getDefaultLengthForType("mysql", "float", mysql8Defaults)).toBe("");
|
||||
expect(getDefaultLengthForType("mysql", "double", mysql8Defaults)).toBe("");
|
||||
expect(combineDataTypeForDatabase("mysql", "int", getDefaultLengthForType("mysql", "int", mysql8Defaults))).toBe("int");
|
||||
expect(combineDataTypeForDatabase("mysql", "float", getDefaultLengthForType("mysql", "float", mysql8Defaults))).toBe("float");
|
||||
expect(getDefaultLengthForType("mysql", "decimal", mysql8Defaults)).toBe("10,0");
|
||||
|
||||
// A compatibility profile cannot be version-identified, so its existing behavior is retained.
|
||||
expect(getDefaultLengthForType("mysql", "int")).toBe("11");
|
||||
expect(getDefaultLengthForType("mysql", "float")).toBe("10,2");
|
||||
});
|
||||
|
||||
it("uses TEXT for a new native SQLite column without changing compatible defaults", () => {
|
||||
expect(DATA_TYPE_OPTIONS.sqlite).toContain("text");
|
||||
expect(defaultNewColumnDataType("sqlite")).toBe("text");
|
||||
expect(defaultNewColumnDataType("rqlite")).toBe("varchar(255)");
|
||||
expect(defaultNewColumnDataType("turso")).toBe("varchar(255)");
|
||||
expect(defaultNewColumnDataType("mysql")).toBe("varchar(255)");
|
||||
});
|
||||
|
||||
it("requires a SQLite rebuild only for a retained existing column type change", () => {
|
||||
const [column] = createColumnDrafts(
|
||||
[
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
export interface OptionHelpPanelOffsetInput {
|
||||
activeItemTop: number;
|
||||
listCardHeight: number;
|
||||
panelHeight: number;
|
||||
}
|
||||
|
||||
/** Aligns a help card to its active row without allowing it past the list card. */
|
||||
export function optionHelpPanelOffsetTop({ activeItemTop, listCardHeight, panelHeight }: OptionHelpPanelOffsetInput): number {
|
||||
if (![activeItemTop, listCardHeight, panelHeight].every(Number.isFinite)) return 0;
|
||||
const desiredTop = Math.max(0, activeItemTop);
|
||||
const maxTop = Math.max(0, listCardHeight - Math.max(0, panelHeight));
|
||||
return Math.min(desiredTop, maxTop);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export type SearchableSelectOptionTooltip = (option: string) => string | undefined;
|
||||
|
||||
/** Keep the option list aligned with its trigger while help expands rightward. */
|
||||
export const SEARCHABLE_SELECT_HELP_PANEL_ALIGN = "start";
|
||||
|
||||
/**
|
||||
* Returns the option whose help panel is activated by keyboard navigation
|
||||
* while focus remains in the searchable input. Empty-string options are valid.
|
||||
*/
|
||||
export function searchableSelectKeyboardTooltipOption(options: readonly string[], highlightIndex: number, optionTooltip?: SearchableSelectOptionTooltip): string | undefined {
|
||||
const option = options[highlightIndex];
|
||||
return option === undefined || !optionTooltip?.(option) ? undefined : option;
|
||||
}
|
||||
|
||||
/** Returns the selected option's help, or the first available option's help. */
|
||||
export function searchableSelectSelectedOrFirstHelpOption(options: readonly string[], selectedOption: string, optionTooltip?: SearchableSelectOptionTooltip): string | undefined {
|
||||
if (options.includes(selectedOption) && optionTooltip?.(selectedOption)) return selectedOption;
|
||||
return options.find((option) => !!optionTooltip?.(option));
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
export const REDIS_CREATE_KEY_TYPE_VALUES = ["string", "hash", "list", "set", "zset", "stream", "json"] as const;
|
||||
|
||||
export type RedisCreateKeyTypeValue = (typeof REDIS_CREATE_KEY_TYPE_VALUES)[number];
|
||||
export type RedisCreateKeyTypeHelpKey = RedisCreateKeyTypeValue;
|
||||
|
||||
export interface RedisCreateKeyTypeHelp {
|
||||
key: RedisCreateKeyTypeHelpKey;
|
||||
}
|
||||
|
||||
export interface RedisCreateKeyTypeHelpFocusState {
|
||||
openedByArrow: boolean;
|
||||
keyboardNavigating: boolean;
|
||||
}
|
||||
|
||||
const REDIS_CREATE_KEY_TYPE_HELP: Readonly<Record<RedisCreateKeyTypeValue, RedisCreateKeyTypeHelpKey>> = {
|
||||
string: "string",
|
||||
hash: "hash",
|
||||
list: "list",
|
||||
set: "set",
|
||||
zset: "zset",
|
||||
stream: "stream",
|
||||
json: "json",
|
||||
};
|
||||
|
||||
/** Returns help for a Redis data type offered by the Create key dialog. */
|
||||
export function getRedisCreateKeyTypeHelp(rawType: string): RedisCreateKeyTypeHelp | undefined {
|
||||
const type = rawType.trim().toLowerCase() as RedisCreateKeyTypeValue;
|
||||
const key = REDIS_CREATE_KEY_TYPE_HELP[type];
|
||||
return key ? { key } : undefined;
|
||||
}
|
||||
|
||||
/** Returns the selected offered type whose help is shown when the Select opens. */
|
||||
export function redisCreateKeyTypeHelpOptionOnOpen(rawType: string): RedisCreateKeyTypeValue | undefined {
|
||||
const type = rawType.trim().toLowerCase() as RedisCreateKeyTypeValue;
|
||||
return REDIS_CREATE_KEY_TYPE_HELP[type] ? type : undefined;
|
||||
}
|
||||
|
||||
/** Determines whether a SelectItem focus event was caused by Arrow navigation. */
|
||||
export function shouldActivateRedisCreateKeyTypeHelpOnFocus(state: RedisCreateKeyTypeHelpFocusState): boolean {
|
||||
return state.openedByArrow || state.keyboardNavigating;
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
export type MysqlDataTypeHelpKey =
|
||||
| "tinyint"
|
||||
| "smallint"
|
||||
| "mediumint"
|
||||
| "int"
|
||||
| "bigint"
|
||||
| "decimal"
|
||||
| "float"
|
||||
| "double"
|
||||
| "real"
|
||||
| "bit"
|
||||
| "boolean"
|
||||
| "serial"
|
||||
| "date"
|
||||
| "datetime"
|
||||
| "timestamp"
|
||||
| "time"
|
||||
| "year"
|
||||
| "char"
|
||||
| "varchar"
|
||||
| "tinytext"
|
||||
| "text"
|
||||
| "mediumtext"
|
||||
| "longtext"
|
||||
| "binary"
|
||||
| "varbinary"
|
||||
| "tinyblob"
|
||||
| "blob"
|
||||
| "mediumblob"
|
||||
| "longblob"
|
||||
| "enum"
|
||||
| "set"
|
||||
| "json"
|
||||
| "jsonMysql"
|
||||
| "jsonMariaDb"
|
||||
| "nationalCharacter"
|
||||
| "geometry"
|
||||
| "point"
|
||||
| "linestring"
|
||||
| "polygon"
|
||||
| "multipoint"
|
||||
| "multilinestring"
|
||||
| "multipolygon"
|
||||
| "geometrycollection"
|
||||
| "inet4"
|
||||
| "inet6"
|
||||
| "uuid"
|
||||
| "vector"
|
||||
| "xmltype"
|
||||
| "integerDisplayWidth"
|
||||
| "zerofill"
|
||||
| "unsignedNumeric"
|
||||
| "floatPrecision"
|
||||
| "floatingPointDisplay";
|
||||
|
||||
export interface MysqlDataTypeHelp {
|
||||
key: MysqlDataTypeHelpKey;
|
||||
warningKeys?: MysqlDataTypeHelpKey[];
|
||||
}
|
||||
|
||||
export interface MysqlDataTypeHelpOptions {
|
||||
/** Set only when the selected connection profile identifies the server product. */
|
||||
product?: "mysql" | "mariadb";
|
||||
}
|
||||
|
||||
const MYSQL_TYPE_HELP_KEYS: Readonly<Record<string, MysqlDataTypeHelpKey>> = {
|
||||
tinyint: "tinyint",
|
||||
smallint: "smallint",
|
||||
mediumint: "mediumint",
|
||||
int: "int",
|
||||
bigint: "bigint",
|
||||
decimal: "decimal",
|
||||
float: "float",
|
||||
double: "double",
|
||||
real: "real",
|
||||
bit: "bit",
|
||||
boolean: "boolean",
|
||||
nationalCharacter: "nationalCharacter",
|
||||
serial: "serial",
|
||||
date: "date",
|
||||
datetime: "datetime",
|
||||
timestamp: "timestamp",
|
||||
time: "time",
|
||||
year: "year",
|
||||
char: "char",
|
||||
varchar: "varchar",
|
||||
tinytext: "tinytext",
|
||||
text: "text",
|
||||
mediumtext: "mediumtext",
|
||||
longtext: "longtext",
|
||||
binary: "binary",
|
||||
varbinary: "varbinary",
|
||||
tinyblob: "tinyblob",
|
||||
blob: "blob",
|
||||
mediumblob: "mediumblob",
|
||||
longblob: "longblob",
|
||||
enum: "enum",
|
||||
set: "set",
|
||||
json: "json",
|
||||
geometry: "geometry",
|
||||
point: "point",
|
||||
linestring: "linestring",
|
||||
polygon: "polygon",
|
||||
multipoint: "multipoint",
|
||||
multilinestring: "multilinestring",
|
||||
multipolygon: "multipolygon",
|
||||
geometrycollection: "geometrycollection",
|
||||
inet4: "inet4",
|
||||
inet6: "inet6",
|
||||
uuid: "uuid",
|
||||
vector: "vector",
|
||||
xmltype: "xmltype",
|
||||
};
|
||||
|
||||
const MYSQL_TYPE_ALIASES: Readonly<Record<string, string>> = {
|
||||
integer: "int",
|
||||
numeric: "decimal",
|
||||
dec: "decimal",
|
||||
fixed: "decimal",
|
||||
bool: "boolean",
|
||||
"character varying": "varchar",
|
||||
character: "char",
|
||||
"char varying": "varchar",
|
||||
"char byte": "char",
|
||||
nchar: "nationalCharacter",
|
||||
nvarchar: "nationalCharacter",
|
||||
"nchar varying": "nationalCharacter",
|
||||
"national char": "nationalCharacter",
|
||||
"national character": "nationalCharacter",
|
||||
"national varchar": "nationalCharacter",
|
||||
"national char varying": "nationalCharacter",
|
||||
"national character varying": "nationalCharacter",
|
||||
"double precision": "double",
|
||||
float4: "float",
|
||||
float8: "double",
|
||||
int1: "tinyint",
|
||||
int2: "smallint",
|
||||
int3: "mediumint",
|
||||
int4: "int",
|
||||
int8: "bigint",
|
||||
middleint: "mediumint",
|
||||
long: "mediumtext",
|
||||
"long varchar": "mediumtext",
|
||||
"long varbinary": "mediumblob",
|
||||
geomcollection: "geometrycollection",
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns help for a MySQL-family type name without making a database request.
|
||||
* The caller is responsible for limiting this to connections that use MySQL
|
||||
* semantics, since several compatible databases accept similarly named types
|
||||
* with different limits.
|
||||
*/
|
||||
export function getMysqlDataTypeHelp(rawType: string, options: MysqlDataTypeHelpOptions = {}): MysqlDataTypeHelp | undefined {
|
||||
const normalized = normalizeMysqlType(rawType);
|
||||
const resolvedType = MYSQL_TYPE_ALIASES[normalized] ?? normalized;
|
||||
const baseKey = MYSQL_TYPE_HELP_KEYS[resolvedType];
|
||||
if (!baseKey) return undefined;
|
||||
|
||||
const key = resolvedType === "json" ? jsonHelpKey(options.product) : baseKey;
|
||||
const warningKeys = mysqlDataTypeWarningKeys(rawType, resolvedType);
|
||||
return warningKeys.length > 0 ? { key, warningKeys } : { key };
|
||||
}
|
||||
|
||||
function jsonHelpKey(product: MysqlDataTypeHelpOptions["product"]): MysqlDataTypeHelpKey {
|
||||
if (product === "mysql") return "jsonMysql";
|
||||
if (product === "mariadb") return "jsonMariaDb";
|
||||
return "json";
|
||||
}
|
||||
|
||||
function mysqlDataTypeWarningKeys(rawType: string, resolvedType: string): MysqlDataTypeHelpKey[] {
|
||||
const lowered = rawType.trim().toLowerCase().replace(/\s+/g, " ");
|
||||
const warnings: MysqlDataTypeHelpKey[] = [];
|
||||
const isInteger = ["tinyint", "smallint", "mediumint", "int", "bigint"].includes(resolvedType);
|
||||
const isNumeric = isInteger || ["decimal", "float", "double", "real"].includes(resolvedType);
|
||||
|
||||
if (isInteger && /\(\s*\d+\s*\)/.test(lowered)) warnings.push("integerDisplayWidth");
|
||||
if (isNumeric && /\bzerofill\b/.test(lowered)) warnings.push("zerofill");
|
||||
if (["decimal", "float", "double", "real"].includes(resolvedType) && /\bunsigned\b/.test(lowered)) warnings.push("unsignedNumeric");
|
||||
|
||||
const parameters = lowered.match(/\(([^)]*)\)/)?.[1]?.trim() ?? "";
|
||||
if (resolvedType === "float" && /^\d+$/.test(parameters)) {
|
||||
const precision = Number(parameters);
|
||||
if (precision >= 25 && precision <= 53) warnings.push("floatPrecision");
|
||||
}
|
||||
if (["float", "double"].includes(resolvedType) && /^\d+\s*,\s*\d+$/.test(parameters)) warnings.push("floatingPointDisplay");
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
function normalizeMysqlType(rawType: string): string {
|
||||
return rawType
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\([^)]*\)/g, " ")
|
||||
.replace(/\b(?:unsigned|signed|zerofill)\b/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
export type PostgresDataTypeHelpKey =
|
||||
| "smallint"
|
||||
| "integer"
|
||||
| "bigint"
|
||||
| "serial"
|
||||
| "numeric"
|
||||
| "real"
|
||||
| "double"
|
||||
| "money"
|
||||
| "boolean"
|
||||
| "char"
|
||||
| "varchar"
|
||||
| "text"
|
||||
| "bytea"
|
||||
| "date"
|
||||
| "time"
|
||||
| "timetz"
|
||||
| "timestamp"
|
||||
| "timestamptz"
|
||||
| "interval"
|
||||
| "uuid"
|
||||
| "json"
|
||||
| "jsonb"
|
||||
| "xml"
|
||||
| "bit"
|
||||
| "tsvector"
|
||||
| "tsquery"
|
||||
| "cidr"
|
||||
| "inet"
|
||||
| "macaddr"
|
||||
| "geometry"
|
||||
| "range"
|
||||
| "oid"
|
||||
| "array"
|
||||
| "multirange"
|
||||
| "jsonpath"
|
||||
| "pgLsn"
|
||||
| "pgSnapshot";
|
||||
|
||||
export interface PostgresDataTypeHelp {
|
||||
key: PostgresDataTypeHelpKey;
|
||||
}
|
||||
|
||||
const POSTGRES_TYPE_HELP_KEYS: Readonly<Record<string, PostgresDataTypeHelpKey>> = {
|
||||
smallint: "smallint",
|
||||
integer: "integer",
|
||||
bigint: "bigint",
|
||||
smallserial: "serial",
|
||||
serial: "serial",
|
||||
bigserial: "serial",
|
||||
numeric: "numeric",
|
||||
real: "real",
|
||||
"double precision": "double",
|
||||
money: "money",
|
||||
boolean: "boolean",
|
||||
char: "char",
|
||||
varchar: "varchar",
|
||||
text: "text",
|
||||
bytea: "bytea",
|
||||
date: "date",
|
||||
time: "time",
|
||||
timetz: "timetz",
|
||||
timestamp: "timestamp",
|
||||
timestamptz: "timestamptz",
|
||||
interval: "interval",
|
||||
uuid: "uuid",
|
||||
json: "json",
|
||||
jsonb: "jsonb",
|
||||
xml: "xml",
|
||||
bit: "bit",
|
||||
"bit varying": "bit",
|
||||
tsvector: "tsvector",
|
||||
tsquery: "tsquery",
|
||||
cidr: "cidr",
|
||||
inet: "inet",
|
||||
macaddr: "macaddr",
|
||||
macaddr8: "macaddr",
|
||||
point: "geometry",
|
||||
line: "geometry",
|
||||
lseg: "geometry",
|
||||
box: "geometry",
|
||||
path: "geometry",
|
||||
polygon: "geometry",
|
||||
circle: "geometry",
|
||||
int4range: "range",
|
||||
int8range: "range",
|
||||
numrange: "range",
|
||||
tsrange: "range",
|
||||
tstzrange: "range",
|
||||
daterange: "range",
|
||||
oid: "oid",
|
||||
jsonpath: "jsonpath",
|
||||
pg_lsn: "pgLsn",
|
||||
pg_snapshot: "pgSnapshot",
|
||||
};
|
||||
|
||||
const POSTGRES_TYPE_ALIASES: Readonly<Record<string, string>> = {
|
||||
int2: "smallint",
|
||||
int: "integer",
|
||||
int4: "integer",
|
||||
int8: "bigint",
|
||||
decimal: "numeric",
|
||||
float4: "real",
|
||||
float8: "double precision",
|
||||
bool: "boolean",
|
||||
character: "char",
|
||||
"character varying": "varchar",
|
||||
"time without time zone": "time",
|
||||
"time with time zone": "timetz",
|
||||
"timestamp without time zone": "timestamp",
|
||||
"timestamp with time zone": "timestamptz",
|
||||
varbit: "bit varying",
|
||||
};
|
||||
|
||||
const POSTGRES_MULTIRANGE_TYPES = new Set(["int4multirange", "int8multirange", "nummultirange", "tsmultirange", "tstzmultirange", "datemultirange"]);
|
||||
|
||||
/**
|
||||
* Returns static PostgreSQL type help without querying the server. Extension
|
||||
* types and user-defined types intentionally remain undocumented.
|
||||
*/
|
||||
export function getPostgresDataTypeHelp(rawType: string): PostgresDataTypeHelp | undefined {
|
||||
const normalized = normalizePostgresType(rawType);
|
||||
if (isPostgresArrayType(normalized)) return { key: "array" };
|
||||
|
||||
const baseType = normalized
|
||||
.replace(/\([^)]*\)/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
if (baseType === "float") {
|
||||
const key = floatHelpKey(normalized);
|
||||
return key ? { key } : undefined;
|
||||
}
|
||||
if (POSTGRES_MULTIRANGE_TYPES.has(baseType)) return { key: "multirange" };
|
||||
|
||||
const canonicalType = POSTGRES_TYPE_ALIASES[baseType] ?? baseType;
|
||||
const key = POSTGRES_TYPE_HELP_KEYS[canonicalType];
|
||||
return key ? { key } : undefined;
|
||||
}
|
||||
|
||||
function normalizePostgresType(rawType: string): string {
|
||||
return rawType.trim().toLowerCase().replace(/\s+/g, " ");
|
||||
}
|
||||
|
||||
function isPostgresArrayType(normalizedType: string): boolean {
|
||||
return /(?:\[\s*\]\s*)+$/.test(normalizedType);
|
||||
}
|
||||
|
||||
function floatHelpKey(normalizedType: string): "real" | "double" | undefined {
|
||||
if (normalizedType === "float") return "double";
|
||||
const precision = normalizedType.match(/^float\(\s*(\d+)\s*\)$/)?.[1];
|
||||
if (!precision) return undefined;
|
||||
const value = Number(precision);
|
||||
if (value >= 1 && value <= 24) return "real";
|
||||
return value >= 25 && value <= 53 ? "double" : undefined;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export type SqliteDataTypeHelpKey = "integer" | "real" | "text" | "blob" | "numeric";
|
||||
|
||||
export interface SqliteDataTypeHelp {
|
||||
key: SqliteDataTypeHelpKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps declared types using SQLite's ordered affinity rules. The order matters:
|
||||
* for example, FLOATING POINT has INTEGER affinity because it contains "INT".
|
||||
*/
|
||||
export function getSqliteDataTypeHelp(rawType: string): SqliteDataTypeHelp | undefined {
|
||||
const normalized = rawType
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\([^)]*\)/g, " ")
|
||||
.replace(/\s+/g, " ")
|
||||
.trim();
|
||||
if (!normalized) return { key: "blob" };
|
||||
if (normalized.includes("int")) return { key: "integer" };
|
||||
if (normalized.includes("char") || normalized.includes("clob") || normalized.includes("text")) return { key: "text" };
|
||||
if (normalized.includes("blob")) return { key: "blob" };
|
||||
if (normalized.includes("real") || normalized.includes("floa") || normalized.includes("doub")) return { key: "real" };
|
||||
return { key: "numeric" };
|
||||
}
|
||||
@@ -965,8 +965,17 @@ function isValidTemporalPrecision(dbType: DatabaseType | undefined, params: stri
|
||||
return Number.isInteger(value) && value >= 0 && value <= max && String(value) === params;
|
||||
}
|
||||
|
||||
export function getDefaultLengthForType(_dbType: DatabaseType | undefined, baseType: string): string {
|
||||
export interface DataTypeDefaultOptions {
|
||||
/**
|
||||
* Native MySQL profiles use MySQL 8-safe defaults. Compatibility profiles
|
||||
* retain their existing DDL defaults because their server/version is unknown.
|
||||
*/
|
||||
omitMysqlDeprecatedDefaults?: boolean;
|
||||
}
|
||||
|
||||
export function getDefaultLengthForType(_dbType: DatabaseType | undefined, baseType: string, options: DataTypeDefaultOptions = {}): string {
|
||||
const key = baseType.trim().toLowerCase();
|
||||
if (_dbType === "mysql" && options.omitMysqlDeprecatedDefaults && isMysqlDeprecatedDefaultParameterType(key)) return "";
|
||||
if (_dbType === "sqlite" || _dbType === "rqlite" || _dbType === "turso") {
|
||||
return "";
|
||||
} else if (_dbType === "questdb") {
|
||||
@@ -978,6 +987,20 @@ export function getDefaultLengthForType(_dbType: DatabaseType | undefined, baseT
|
||||
}
|
||||
}
|
||||
|
||||
/** Default data type for a newly added structure-editor column. */
|
||||
export function defaultNewColumnDataType(dbType: DatabaseType | undefined, dataTypeOptions: readonly string[] = []): string {
|
||||
if (dbType === "manticoresearch") {
|
||||
const baseType = dataTypeOptions[0] ?? "text";
|
||||
return combineDataTypeForDatabase(dbType, baseType, getDefaultLengthForType(dbType, baseType));
|
||||
}
|
||||
return dbType === "sqlite" ? "text" : "varchar(255)";
|
||||
}
|
||||
|
||||
function isMysqlDeprecatedDefaultParameterType(baseType: string): boolean {
|
||||
const typeName = baseType.split(/\s+/)[0];
|
||||
return ["tinyint", "smallint", "mediumint", "int", "integer", "bigint", "float", "double", "real"].includes(typeName ?? "");
|
||||
}
|
||||
|
||||
export function isDataTypeLengthDisabled(_dbType: DatabaseType | undefined, baseType: string): boolean {
|
||||
const key = baseType.trim().toLowerCase();
|
||||
if (_dbType === "questdb") {
|
||||
|
||||
@@ -1952,6 +1952,16 @@ body.dbx-table-reference-dragging * {
|
||||
box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45) !important;
|
||||
}
|
||||
|
||||
/* A few compound pickers render their own separate surface cards. */
|
||||
[data-slot="select-content"][data-naked-surface],
|
||||
.dark [data-slot="select-content"][data-naked-surface] {
|
||||
background: transparent !important;
|
||||
border: 0 !important;
|
||||
box-shadow: none !important;
|
||||
backdrop-filter: none !important;
|
||||
-webkit-backdrop-filter: none !important;
|
||||
}
|
||||
|
||||
[data-slot="context-menu-item"]:hover,
|
||||
[data-slot="context-menu-item"]:focus,
|
||||
[data-slot="context-menu-item"][data-highlighted],
|
||||
|
||||
Reference in new issue
Block a user