diff --git a/mathesar_ui/src/components/abstract-type-control/AbstractTypeControl.svelte b/mathesar_ui/src/components/abstract-type-control/AbstractTypeControl.svelte index 847a8566d8..8f9bce015f 100644 --- a/mathesar_ui/src/components/abstract-type-control/AbstractTypeControl.svelte +++ b/mathesar_ui/src/components/abstract-type-control/AbstractTypeControl.svelte @@ -4,7 +4,7 @@ import type { RequestStatus } from '@mathesar/api/rest/utils/requestUtils'; import { toast } from '@mathesar/stores/toast'; - import { objectsAreDeeplyEqual } from '@mathesar/utils/objectUtils'; + import { columnTypeOptionsAreEqual } from '@mathesar/utils/columnUtils'; import { CancelOrProceedButtonPair, createValidationContext, @@ -38,7 +38,7 @@ $: actionButtonsVisible = selectedAbstractType !== column.abstractType || selectedDbType !== column.type || - !objectsAreDeeplyEqual(savedTypeOptions, typeOptions); + !columnTypeOptionsAreEqual(savedTypeOptions, typeOptions ?? {}); let typeChangeState: RequestStatus; diff --git a/mathesar_ui/src/utils/columnUtils.ts b/mathesar_ui/src/utils/columnUtils.ts index ad2d65ab9d..a99797cea7 100644 --- a/mathesar_ui/src/utils/columnUtils.ts +++ b/mathesar_ui/src/utils/columnUtils.ts @@ -1,4 +1,4 @@ -import type { Column } from '@mathesar/api/rpc/columns'; +import type { Column, ColumnTypeOptions } from '@mathesar/api/rpc/columns'; import type { ConstraintType } from '@mathesar/api/rpc/constraints'; import { type ValidationFn, uniqueWith } from '@mathesar/components/form'; import { iconConstraint, iconTableLink } from '@mathesar/icons'; @@ -66,3 +66,32 @@ export function getColumnConstraintTypeByColumnId( ); return constraintsType; } + +export function columnTypeOptionsAreEqual( + a: ColumnTypeOptions, + b: ColumnTypeOptions, +): boolean { + type TypeOption = keyof ColumnTypeOptions; + // This weird object exists for type safety purposes. This way, if a new field + // is added to ColumnTypeOptions, we'll get a type error here if we don't + // update this object. + const fieldsObj: Record = { + precision: null, + scale: null, + length: null, + fields: null, + item_type: null, + }; + const fields = Object.keys(fieldsObj) as TypeOption[]; + + for (const field of fields) { + // The nullish coalescing here is important and kind of the main reason this + // function exists. We need to make sure that if a field is missing from one + // object while present but `null` in the other object, then the two objects + // are still considered equal as far as comparing column type options goes. + if ((a[field] ?? null) !== (b[field] ?? null)) { + return false; + } + } + return true; +} diff --git a/mathesar_ui/src/utils/objectUtils.ts b/mathesar_ui/src/utils/objectUtils.ts deleted file mode 100644 index 40dfb9a7ec..0000000000 --- a/mathesar_ui/src/utils/objectUtils.ts +++ /dev/null @@ -1,14 +0,0 @@ -function isObject(obj: unknown): obj is Record { - return obj !== null && typeof obj === 'object'; -} - -export function objectsAreDeeplyEqual(a: unknown, b: unknown): boolean { - if (!isObject(a) || !isObject(b)) return a === b; - - const aKeys = Object.keys(a); - const bKeys = Object.keys(b); - - if (aKeys.length !== bKeys.length) return false; - - return aKeys.every((key) => objectsAreDeeplyEqual(a[key], b[key])); -}