Appropriate data types for each column (no stringly-typed data)
Why it matters
Monetary values stored as FLOAT lose cents through floating-point rounding — a $29.99 price becomes $29.989999... in arithmetic, producing billing discrepancies. Timestamps stored as VARCHAR cannot be range-queried or timezone-converted by the database. Enums stored as unbounded TEXT accept any string value, so typos like 'actve' or 'CANCELED' silently enter the database and bypass all application-level validation. These are not hypothetical: AI-generated schemas routinely default to TEXT for everything and FLOAT for any numeric value. ISO 25010 functional-correctness penalizes these because the database cannot enforce domain constraints it was not given.
Severity rationale
Medium because type mismatches cause data corruption and silent validation failures, but are typically contained to specific columns rather than system-wide integrity failures.
Remediation
Fix type mismatches one column at a time using migration ALTER COLUMN ... TYPE. Monetary columns must use DECIMAL/NUMERIC or integer cents. Enum columns must use a Postgres ENUM type or a CHECK constraint.
-- Monetary: DECIMAL not FLOAT
ALTER TABLE orders
ALTER COLUMN total TYPE DECIMAL(12, 2);
-- Enum: add CHECK constraint
ALTER TABLE subscriptions
ADD CONSTRAINT chk_status
CHECK (status IN ('active', 'canceled', 'past_due', 'trialing'));
-- Timestamp: convert VARCHAR to TIMESTAMPTZ
ALTER TABLE users
ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at::TIMESTAMPTZ;
model Order {
total Decimal @db.Decimal(12, 2) // not Float
status OrderStatus // enum, not String
}
enum OrderStatus { active canceled past_due trialing }
Detection
-
ID:
appropriate-types -
Severity:
medium -
What to look for: List all columns in the schema and for each, classify the data type as "correct" or "mismatched." Common violations in AI-generated schemas: (1) Timestamps stored as
TEXT/VARCHARinstead ofTIMESTAMP WITH TIME ZONEorDateTime. (2) Booleans stored asINTEGER(0/1) orVARCHAR('true'/'false') instead ofBOOLEAN. (3) Enums stored as unboundedTEXT/VARCHARwith no check constraint or enum type, leaving the column open to any string value. (4) UUIDs stored asVARCHAR(36)orTEXTinstead ofUUIDtype (Postgres). (5) Monetary values stored asFLOAT/DOUBLE PRECISION— floating point arithmetic loses cents, useDECIMAL/NUMERIC(10,2)or store as integer cents. (6) IP addresses stored asVARCHARinstead ofINET(Postgres). (7) JSON data stored asTEXTinstead ofJSONB/JSON. -
Pass criteria: Column types match their semantic meaning — no more than 2 type mismatches allowed. Timestamps use
TIMESTAMP/DateTime/TIMESTAMPTZ. Booleans useBOOLEAN. Monetary values useDECIMAL/NUMERIC(notFLOAT). UUIDs useUUIDtype in Postgres. Enums useENUMtype, Postgresenum, or aCHECKconstraint to restrict values. JSON blobs useJSONB/JSON. -
Fail criteria: More than 2 columns use incorrect types. Money stored as
FLOAT. Timestamps stored as strings. Booleans stored as integers. Enums as unbounded text. -
Skip (N/A) when: N/A — always applicable for any project with a database.
-
Detail on fail: Specify each type mismatch. Example:
"'orders.total' stored as FLOAT — floating point imprecision causes rounding errors for monetary values."or"'users.created_at' is VARCHAR(255) — should be TIMESTAMPTZ."or"'subscriptions.status' is TEXT with no constraint — accepts any string value; should be an enum or have a CHECK constraint.". -
Remediation: Use appropriate types:
-- Monetary values — use DECIMAL, not FLOAT ALTER TABLE orders ALTER COLUMN total TYPE DECIMAL(12, 2); -- Or store as integer cents (no decimal storage needed): -- total_cents BIGINT NOT NULL (e.g., 2999 = $29.99) -- Timestamps — use TIMESTAMPTZ ALTER TABLE users ALTER COLUMN created_at TYPE TIMESTAMPTZ USING created_at::TIMESTAMPTZ; -- Booleans — use BOOLEAN ALTER TABLE users ALTER COLUMN is_active TYPE BOOLEAN USING (is_active::INTEGER)::BOOLEAN; -- Enums — add CHECK constraint or use enum type ALTER TABLE subscriptions ADD CONSTRAINT chk_status CHECK (status IN ('active', 'canceled', 'past_due', 'trialing')); -- Or create a proper enum type: CREATE TYPE subscription_status AS ENUM ('active', 'canceled', 'past_due', 'trialing'); ALTER TABLE subscriptions ALTER COLUMN status TYPE subscription_status USING status::subscription_status;// Prisma — correct types model Order { total Decimal @db.Decimal(12, 2) // not Float createdAt DateTime @default(now()) // not String isActive Boolean @default(true) // not Int status OrderStatus // enum, not String } enum OrderStatus { active canceled past_due trialing }
External references
- iso-25010:2011 · functional-suitability.functional-correctness — Functional Correctness
Taxons
History
- 2026-04-18·v1.0.0·Initial import from database-design-operations·automated