Database table references exist in schema
Why it matters
A hallucinated table name is one of the clearest markers of AI-generated database code: the model invents a plausible name (userProfile, user_profiles) that differs from the actual schema (users, profiles). Prisma throws Cannot read property 'findMany' of undefined at runtime — a hard crash. Supabase returns a PostgREST 404 or an empty result set, which can silently suppress data that the application treats as authoritative. In both cases the failure mode is a runtime data-layer outage tied directly to reference-integrity drift (CWE-1188, OWASP A08: Software and Data Integrity Failures). The mismatch is undetectable at build time in Supabase projects unless type generation is wired into CI.
Severity rationale
Critical because a single unresolved table reference crashes the affected code path at runtime — Prisma hard-throws, Supabase silently returns no data — with no compile-time safety net in most stacks.
Remediation
Fix each hallucinated table name to exactly match the schema definition. Prisma model names are camelCase client accessors derived from PascalCase model names; Supabase table names are the literal CREATE TABLE identifier (typically snake_case).
// Prisma — model 'User' in schema.prisma → prisma.user (camelCase)
await prisma.userProfile.findMany() // Bad: 'userProfile' not in schema
await prisma.user.findMany() // Good
// Supabase — must match CREATE TABLE name in migrations exactly
await supabase.from('user_profiles').select('*') // Bad: table is 'profiles'
await supabase.from('profiles').select('*') // Good
For Supabase projects, run supabase gen types typescript --local > src/database.types.ts and use the generated Database type with the client to get compile-time table name checking.
Detection
-
ID:
db-models-in-schema -
Severity:
critical -
What to look for: This check validates that every database table/model reference in code matches a table defined in the schema. It covers TWO paths depending on the project's database access pattern:
Path A — ORM projects (Prisma, Drizzle, TypeORM, Sequelize, Mongoose, Kysely): Read the schema file(s) and build
KNOWN_TABLESfrom defined model/table names. Walk source files for ORM query patterns:prisma.X.findMany,prisma.X.create,prisma.X.update,prisma.X.delete,prisma.X.upsert,prisma.X.findUnique,prisma.X.findFirst,prisma.X.count,prisma.X.aggregate,db.X.findMany,db.select().from(X),db.insert(X),db.update(X),db.delete(X),repository.find()(TypeORM),Model.find()(Mongoose),Model.findOne(). Extract the model nameXfrom each call.Path B — Supabase projects (
@supabase/supabase-jsin dependencies): BuildKNOWN_TABLESfrom migration SQL files (see "How to Analyze" step 8 for parsing instructions) or from a generateddatabase.types.ts. Walk source files for.from('tablename')and.from("tablename")calls on the Supabase client. Extract the table name string literal. SKIP Supabase reserved schema tables — any.from()call where the argument starts withauth.,storage.,realtime.,extensions., orvault.(these are Supabase-managed tables not defined in user migrations).For both paths: before evaluating, extract and quote the table/model name from each call site, then verify each name exists in
KNOWN_TABLES. Count all table references inspected, total resolved, total unresolved. -
Detector snippet (Node-capable tools only): If the tool has shell + Node, extract model names from
prisma/*.prismafiles and CREATE TABLE statements insupabase/**/*.sql, then scan src for.from('...')calls and compare. Output reports refs, missing count, and the offending table names. If exit 0 with "missing=0", report pass. If "missing>0", report fail. Exit >=2 or command not found — fall back to prose reasoning below.node -e 'const fs=require("fs"),path=require("path");function w(d,o=[]){try{for(const e of fs.readdirSync(d,{withFileTypes:true})){if(e.name==="node_modules"||e.name===".next")continue;const p=path.join(d,e.name);e.isDirectory()?w(p,o):o.push(p)}}catch{}return o}let models=new Set();for(const f of w("prisma")){if(!/\.prisma$/.test(f))continue;const s=fs.readFileSync(f,"utf-8");for(const m of s.matchAll(/^model\s+(\w+)/gm))models.add(m[1].toLowerCase())}for(const f of w("supabase")){if(!/\.sql$/.test(f))continue;const s=fs.readFileSync(f,"utf-8");for(const m of s.matchAll(/create\s+table\s+(?:if\s+not\s+exists\s+)?[\u0022\u0060]?(\w+)/gi))models.add(m[1].toLowerCase())}let refs=0,missing=[];for(const f of w("src")){if(!/\.(ts|tsx|js|jsx)$/.test(f))continue;const s=fs.readFileSync(f,"utf-8");for(const m of s.matchAll(/\.from\([\u0022\u0027]([^\u0022\u0027]+)[\u0022\u0027]\)/g)){refs++;if(!models.has(m[1].toLowerCase()))missing.push(f+":"+m[1])}}console.log("refs="+refs+" missing="+missing.length+" models="+models.size);missing.slice(0,3).forEach(x=>console.log(x))' -
Pass criteria: 100% of database table references resolve to a defined table in the schema. Report: "X table references inspected (source: [Prisma schema / Supabase migrations / ...]), Y resolved, 0 unresolved."
-
Fail criteria: At least 1 database call references a table that is not defined in the schema.
-
Do NOT pass when: The table name is dynamically computed (
prisma[modelName]orsupabase.from(tableName)wheretableNameis a variable) AND no fallback static reference exists — flag as a "dynamic table access" issue with detail. -
Skip (N/A) when: No ORM is detected (none of
prisma,drizzle-orm,typeorm,sequelize,mongoose,kyselyin dependencies) AND no@supabase/supabase-jsin dependencies AND no raw SQL client (pg,mysql2,better-sqlite3) in dependencies. -
Cross-reference: For deeper schema design analysis, the Database Design & Operations audit (
database-design-operations) covers index strategy, normalization, and migration safety. -
Detail on fail:
"3 unresolved table references: prisma.userProfile.findMany() in src/app/profile/page.tsx (model 'userProfile' not in schema.prisma), supabase.from('user_profiles') in src/lib/db.ts (table 'user_profiles' not in migrations — closest match: 'profiles')" -
Remediation: Hallucinated table references cause runtime errors — Prisma throws
Cannot read property 'findMany' of undefined, Supabase returns a 404 or empty result with a misleading error. Fix each unresolved reference:// Prisma: model name doesn't match schema await prisma.userProfile.findMany() // Bad await prisma.user.findMany() // Good: matches schema.prisma // Supabase: table name doesn't match migrations await supabase.from('user_profiles').select('*') // Bad await supabase.from('profiles').select('*') // Good: matches CREATE TABLEPrisma model names are case-sensitive —
Userin the schema becomesprisma.user(camelCased). Supabase table names must exactly match theCREATE TABLEname in migrations (usually lowercase with underscores).
External references
- cwe · CWE-1188 — Insecure Default Initialization of Resource: hallucinated table names default to runtime errors or empty data
- owasp:2021 · A08 — Software and Data Integrity Failures: data access layer calls resources that were never defined
Taxons
History
- 2026-04-18·v1.0.0·Initial import from ai-slop-hallucinations·automated
- 2026-04-20·v1.1.0·Add Phase 6.1 detect-node snippet for Prisma/Supabase schema vs .from() cross-reference·by cakleinman