Database Naming Standards
Overview
Core principle: Enforce comprehensive naming conventions for all database objects ensuring consistency, maintainability, and efficient discovery across the Construct AI database schema.
Critical standards: All database objects follow standardized patterns enforced by automated validation tools through the Supabase Table Creation Agent.
When to Use This Skill
Trigger Conditions:
- Before creating any new database tables, views, or indexes
- When designing database schemas for new features
- Before implementing database migrations
- When reviewing database schema changes
- During database refactoring or optimization
- When onboarding new team members to database development
- When troubleshooting database object discovery issues
Mandatory Application:
- Required for all new database object creation
- Must be enforced by Supabase Table Creation Agent
- Required for database schema reviews
- Must be validated before production deployment
- Required for database naming consistency audits
Step-by-Step Procedure
Step 1: Understand Naming Convention Categories
Master the core naming patterns and their applications:
// Core naming convention categories
const namingConventions = {
tableNaming: {
disciplineTables: {
pattern: 'a_{discipline_code}_{entity}_{descriptor}',
examples: [
'a_01900_procurement_orders',
'a_00850_civileng_specifications',
'a_02400_safety_incidents'
],
validation: /^a_\d{5}_[a-z]+_[a-z_]+$/
},
coreTables: {
pattern: 'a_{entity}_{descriptor}',
examples: [
'a_users',
'a_projects',
'a_tasks'
],
validation: /^a_[a-z]+(_[a-z]+)*$/
},
relationshipTables: {
pattern: 'a_{entity1}_{entity2}_{relation}',
examples: [
'a_users_projects_assignments',
'a_projects_contracts_links'
],
validation: /^a_[a-z]+_[a-z]+_[a-z]+$/
},
vectorTables: {
pattern: 'a_{discipline_code}_{entity}_vector',
examples: [
'a_01900_procurement_vector',
'a_00850_civileng_vector'
],
validation: /^a_\d{5}_[a-z]+_vector$/
},
temporaryTables: {
pattern: 't_{purpose}_{descriptor}',
examples: [
't_session_cache',
't_etl_processing_queue'
],
validation: /^t_[a-z]+(_[a-z]+)*$/
}
},
columnNaming: {
primaryKeys: {
pattern: '{singular_table_entity}_id',
examples: [
'user_id',
'project_id',
'procurement_order_id'
]
},
foreignKeys: {
pattern: '{referenced_table_entity}_id',
examples: [
'user_id', // REFERENCES users.user_id
'created_by_user_id',
'organization_id'
]
},
dataColumns: {
pattern: '{descriptor}_{data_type} or {entity}_{descriptor}',
examples: [
'first_name',
'email_address',
'is_active',
'created_at'
]
},
statusColumns: {
pattern: '{entity}_status or {entity}_type',
examples: [
'user_status',
'contract_type',
'organization_role'
]
}
},
indexNaming: {
primaryKeys: 'Generated by PostgreSQL as {table_name}_pkey',
uniqueIndexes: 'idx_{table_name}_{column1}_{column2}_unique',
performanceIndexes: 'idx_{table_name}_{column1}_{column2}',
foreignKeyIndexes: 'idx_{table_name}_{referenced_table}_{column}'
},
constraintNaming: {
uniqueConstraints: 'uk_{table_name}_{column1}_{column2}',
checkConstraints: 'chk_{table_name}_{condition}_name',
foreignKeyConstraints: 'fk_{table_name}_{referenced_table}_{column}'
}
};
// Discipline code reference
const disciplineCodes = {
'01900': 'Procurement',
'02400': 'Safety',
'01100': 'Ethics',
'00850': 'Civil Engineering',
'02050': 'IT',
'01300': 'Governance',
'01200': 'Finance',
'01500': 'HR',
'01750': 'Legal',
'01800': 'Operations'
};
Convention Categories:
- Table naming patterns (discipline, core, relationship, vector, temporary)
- Column naming standards (primary keys, foreign keys, data columns, status columns)
- Index naming conventions (primary, unique, performance, foreign key)
- Constraint naming patterns (unique, check, foreign key)
Step 2: Validate Table Name Compliance
Ensure new table names follow established patterns:
// Table name validation function
function validateTableName(tableName, context) {
const validation = {
isValid: false,
pattern: null,
suggestions: [],
disciplineCode: null,
errors: []
};
// Check for required prefix
if (!tableName.startsWith('a_') && !tableName.startsWith('t_') && !tableName.startsWith('v_')) {
validation.errors.push('Table name must start with a_, t_, or v_ prefix');
validation.suggestions.push(`Add appropriate prefix: a_${tableName}, t_${tableName}, or v_${tableName}`);
return validation;
}
// Validate discipline-specific tables (a_XXXXX_*)
if (tableName.startsWith('a_') && /^\d{5}/.test(tableName.substring(2))) {
const disciplineMatch = tableName.match(/^a_(\d{5})_([a-z]+)_([a-z_]+)$/);
if (disciplineMatch) {
const [, code, entity, descriptor] = disciplineMatch;
if (isValidDisciplineCode(code)) {
validation.isValid = true;
validation.pattern = 'discipline';
validation.disciplineCode = code;
} else {
validation.errors.push(`Invalid discipline code: ${code}`);
validation.suggestions.push('Use valid 5-digit discipline code from reference table');
}
} else {
validation.errors.push('Discipline table pattern: a_XXXXX_entity_descriptor');
validation.suggestions.push(`Correct format: a_XXXXX_${tableName.substring(tableName.indexOf('_', 3) + 1)}`);
}
}
// Validate core tables (a_*)
else if (tableName.startsWith('a_') && !/^\d{5}/.test(tableName.substring(2))) {
if (/^a_[a-z]+(_[a-z]+)*$/.test(tableName)) {
validation.isValid = true;
validation.pattern = 'core';
} else {
validation.errors.push('Core table pattern: a_entity_descriptor (snake_case only)');
validation.suggestions.push(`Use snake_case: ${tableName.replace(/([A-Z])/g, '_$1').toLowerCase()}`);
}
}
// Validate temporary tables (t_*)
else if (tableName.startsWith('t_')) {
if (/^t_[a-z]+(_[a-z]+)*$/.test(tableName)) {
validation.isValid = true;
validation.pattern = 'temporary';
} else {
validation.errors.push('Temporary table pattern: t_purpose_descriptor');
validation.suggestions.push(`Use snake_case: ${tableName.replace(/([A-Z])/g, '_$1').toLowerCase()}`);
}
}
// Check for camelCase violations
if (tableName.match(/[A-Z][a-z]/)) {
validation.errors.push('Table names must use snake_case, not camelCase');
validation.suggestions.push(`Convert to snake_case: ${tableName.replace(/([A-Z])/g, '_$1').toLowerCase()}`);
}
return validation;
}
// Validate discipline code
function isValidDisciplineCode(code) {
const validCodes = [
'00250', '00300', '00400', '00425', '00430', '00435',
'00825', '00835', '00850', '00855', '00860', '00870',
'00871', '00872', '00877', '00880', '00882', '00883',
'00884', '00885', '00886', '00888', '00889', '00890',
'00895', '00900', '01000', '01100', '01200', '01300',
'01400', '01500', '01600', '01700', '01750', '01800',
'01850', '01900', '02000', '02025', '02035', '02050',
'02075', '02200', '02250', '02400', '02500', '03000'
];
return validCodes.includes(code);
}
// Generate table name suggestions
function generateTableNameSuggestions(entity, descriptor, disciplineCode = null) {
const suggestions = [];
if (disciplineCode) {
suggestions.push(`a_${disciplineCode}_${entity}_${descriptor}`);
suggestions.push(`a_${disciplineCode}_${entity}${descriptor ? `_${descriptor}` : ''}`);
} else {
suggestions.push(`a_${entity}_${descriptor}`);
suggestions.push(`a_${entity}${descriptor ? `_${descriptor}` : ''}`);
}
return suggestions;
}
Table Validation:
- Prefix validation (a_, t_, v_)
- Discipline code verification for domain-specific tables
- Pattern matching for different table types
- Snake_case enforcement (no camelCase)
- Automatic suggestion generation
Step 3: Validate Column Name Standards
Ensure column names follow proper naming conventions:
// Column name validation function
function validateColumnName(columnName, tableName, context) {
const validation = {
isValid: false,
type: null,
suggestions: [],
errors: []
};
// Primary key validation
if (columnName.endsWith('_id') && context.isPrimaryKey) {
const expectedName = getSingularEntityName(tableName) + '_id';
if (columnName === expectedName) {
validation.isValid = true;
validation.type = 'primary_key';
} else {
validation.errors.push(`Primary key should be: ${expectedName}`);
validation.suggestions.push(expectedName);
}
}
// Foreign key validation
else if (columnName.endsWith('_id') && context.isForeignKey) {
const referencedTable = context.referencedTable;
const expectedName = getSingularEntityName(referencedTable) + '_id';
if (columnName === expectedName) {
validation.isValid = true;
validation.type = 'foreign_key';
} else {
validation.errors.push(`Foreign key should reference: ${expectedName}`);
validation.suggestions.push(expectedName);
}
}
// Status/type column validation
else if (columnName.includes('_status') || columnName.includes('_type')) {
const entity = columnName.replace(/_(status|type)$/, '');
if (isValidEntityName(entity)) {
validation.isValid = true;
validation.type = columnName.includes('_status') ? 'status_column' : 'type_column';
} else {
validation.errors.push('Status/type columns should follow: entity_status or entity_type');
validation.suggestions.push(`${entity}_status`, `${entity}_type`);
}
}
// Standard column validation
else {
if (/^[a-z]+(_[a-z]+)*$/.test(columnName)) {
validation.isValid = true;
validation.type = 'data_column';
} else {
validation.errors.push('Column names must use snake_case');
validation.suggestions.push(columnName.replace(/([A-Z])/g, '_$1').toLowerCase());
}
}
// Audit column validation
if (context.isAuditColumn) {
const auditColumns = [
'created_by_user_id', 'created_at',
'updated_by_user_id', 'updated_at',
'deleted_by_user_id', 'deleted_at'
];
if (!auditColumns.includes(columnName)) {
validation.errors.push('Audit columns must follow standard naming');
validation.suggestions.push(...auditColumns);
}
}
return validation;
}
// Get singular entity name from table name
function getSingularEntityName(tableName) {
// Remove prefix
let entity = tableName.replace(/^(a|t|v)_/, '');
// Remove discipline code if present
entity = entity.replace(/^\d{5}_/, '');
// Convert to singular (basic plural removal)
if (entity.endsWith('ies')) {
entity = entity.slice(0, -3) + 'y';
} else if (entity.endsWith('s') && !entity.endsWith('ss')) {
entity = entity.slice(0, -1);
}
return entity;
}
// Validate entity name
function isValidEntityName(entity) {
return /^[a-z]+(_[a-z]+)*$/.test(entity) && !entity.includes('table') && !entity.includes('data');
}
Column Validation:
- Primary key naming standards
- Foreign key reference patterns
- Status and type column conventions
- Snake_case enforcement
- Audit column standards
Step 4: Validate Index and Constraint Names
Ensure indexes and constraints follow naming standards:
// Index and constraint validation
function validateIndexAndConstraints(schema) {
const validation = {
indexes: [],
constraints: [],
errors: [],
suggestions: []
};
// Validate indexes
for (const index of schema.indexes) {
const indexValidation = validateIndexName(index.name, index.table, index.columns);
validation.indexes.push(indexValidation);
if (!indexValidation.isValid) {
validation.errors.push(...indexValidation.errors);
validation.suggestions.push(...indexValidation.suggestions);
}
}
// Validate constraints
for (const constraint of schema.constraints) {
const constraintValidation = validateConstraintName(constraint.name, constraint.type, constraint.table);
validation.constraints.push(constraintValidation);
if (!constraintValidation.isValid) {
validation.errors.push(...constraintValidation.errors);
validation.suggestions.push(...constraintValidation.suggestions);
}
}
return validation;
}
// Validate index name
function validateIndexName(indexName, tableName, columns) {
const validation = {
isValid: false,
type: null,
suggestions: [],
errors: []
};
// Primary key index (auto-generated)
if (indexName === `${tableName}_pkey`) {
validation.isValid = true;
validation.type = 'primary_key';
}
// Unique index
else if (indexName.includes('_unique')) {
const expectedPattern = `idx_${tableName}_${columns.join('_')}_unique`;
if (indexName === expectedPattern) {
validation.isValid = true;
validation.type = 'unique_index';
} else {
validation.errors.push(`Unique index should be: ${expectedPattern}`);
validation.suggestions.push(expectedPattern);
}
}
// Performance index
else if (indexName.startsWith(`idx_${tableName}_`)) {
const columnPart = indexName.replace(`idx_${tableName}_`, '');
const expectedColumns = columnPart.split('_');
if (arraysEqual(expectedColumns, columns)) {
validation.isValid = true;
validation.type = 'performance_index';
} else {
const expectedName = `idx_${tableName}_${columns.join('_')}`;
validation.errors.push(`Performance index should be: ${expectedName}`);
validation.suggestions.push(expectedName);
}
}
// Foreign key index
else if (indexName.includes('_id')) {
const column = columns[0];
if (column && column.endsWith('_id')) {
const referencedEntity = column.replace('_id', '');
const expectedName = `idx_${tableName}_${referencedEntity}_id`;
if (indexName === expectedName) {
validation.isValid = true;
validation.type = 'foreign_key_index';
} else {
validation.errors.push(`Foreign key index should be: ${expectedName}`);
validation.suggestions.push(expectedName);
}
}
}
else {
validation.errors.push('Index name does not follow naming conventions');
validation.suggestions.push(`idx_${tableName}_${columns.join('_')}`);
}
return validation;
}
// Validate constraint name
function validateConstraintName(constraintName, type, tableName) {
const validation = {
isValid: false,
suggestions: [],
errors: []
};
switch (type) {
case 'UNIQUE':
if (constraintName.startsWith('uk_')) {
validation.isValid = true;
} else {
validation.errors.push('Unique constraints should start with uk_');
validation.suggestions.push(`uk_${constraintName}`);
}
break;
case 'CHECK':
if (constraintName.startsWith('chk_')) {
validation.isValid = true;
} else {
validation.errors.push('Check constraints should start with chk_');
validation.suggestions.push(`chk_${constraintName}`);
}
break;
case 'FOREIGN KEY':
if (constraintName.startsWith('fk_')) {
validation.isValid = true;
} else {
validation.errors.push('Foreign key constraints should start with fk_');
validation.suggestions.push(`fk_${constraintName}`);
}
break;
default:
validation.errors.push(`Unknown constraint type: ${type}`);
}
return validation;
}
// Utility function to compare arrays
function arraysEqual(a, b) {
if (a.length !== b.length) return false;
return a.every((val, index) => val === b[index]);
}
Index and Constraint Validation:
- Primary key index patterns
- Unique index naming standards
- Performance index conventions
- Foreign key index patterns
- Constraint naming by type (unique, check, foreign key)
Step 5: Automated Validation with Supabase Agent
Use the Supabase Table Creation Agent for comprehensive validation:
// Supabase Table Creation Agent integration
class DatabaseNamingValidator {
constructor() {
this.disciplineCodes = this.loadDisciplineCodes();
this.validationRules = this.loadValidationRules();
}
// Complete schema validation
async validateSchema(schema) {
const validationReport = {
tables: [],
columns: [],
indexes: [],
constraints: [],
overallCompliance: 0,
criticalIssues: [],
recommendations: []
};
// Validate all tables
for (const table of schema.tables) {
const tableValidation = await this.validateTable(table);
validationReport.tables.push(tableValidation);
if (!tableValidation.isValid) {
validationReport.criticalIssues.push({
type: 'table_naming',
table: table.name,
issues: tableValidation.errors
});
}
}
// Validate all columns
for (const column of schema.columns) {
const columnValidation = await this.validateColumn(column);
validationReport.columns.push(columnValidation);
}
// Validate indexes and constraints
const indexConstraintValidation = await this.validateIndexesAndConstraints(schema);
validationReport.indexes = indexConstraintValidation.indexes;
validationReport.constraints = indexConstraintValidation.constraints;
// Calculate overall compliance
validationReport.overallCompliance = this.calculateCompliance(validationReport);
validationReport.recommendations = this.generateRecommendations(validationReport);
return validationReport;
}
// Validate individual table
async validateTable(table) {
const validation = {
name: table.name,
isValid: true,
errors: [],
warnings: [],
suggestions: []
};
// Basic naming validation
const nameValidation = validateTableName(table.name, { discipline: table.discipline });
if (!nameValidation.isValid) {
validation.isValid = false;
validation.errors.push(...nameValidation.errors);
validation.suggestions.push(...nameValidation.suggestions);
}
// Check for required audit columns
const hasAuditColumns = this.checkAuditColumns(table.columns);
if (!hasAuditColumns) {
validation.warnings.push('Missing standard audit columns (created_by, updated_by, etc.)');
}
// Check for primary key
const hasPrimaryKey = table.columns.some(col => col.isPrimaryKey);
if (!hasPrimaryKey) {
validation.errors.push('Table must have a primary key');
validation.isValid = false;
}
return validation;
}
// Check for required audit columns
checkAuditColumns(columns) {
const requiredAuditColumns = [
'created_by_user_id',
'created_at',
'updated_by_user_id',
'updated_at'
];
return requiredAuditColumns.every(auditCol =>
columns.some(col => col.name === auditCol)
);
}
// Calculate overall compliance percentage
calculateCompliance(validationReport) {
const totalItems =
validationReport.tables.length +
validationReport.columns.length +
validationReport.indexes.length +
validationReport.constraints.length;
const validItems =
validationReport.tables.filter(t => t.isValid).length +
validationReport.columns.filter(c => c.isValid).length +
validationReport.indexes.filter(i => i.isValid).length +
validationReport.constraints.filter(c => c.isValid).length;
return totalItems > 0 ? (validItems / totalItems) * 100 : 0;
}
// Generate recommendations
generateRecommendations(validationReport) {
const recommendations = [];
if (validationReport.overallCompliance < 80) {
recommendations.push('Critical: Overall compliance below 80%. Address naming issues before deployment.');
}
if (validationReport.criticalIssues.length > 0) {
recommendations.push(`Address ${validationReport.criticalIssues.length} critical naming issues.`);
}
if (validationReport.tables.some(t => !t.isValid)) {
recommendations.push('Review table naming conventions and correct invalid names.');
}
recommendations.push('Use Supabase Table Creation Agent for all new table creation to ensure compliance.');
return recommendations;
}
}
// Agent usage example
async function createTableWithValidation(tableSchema) {
const agent = new SupabaseTableCreationAgent();
const validator = new DatabaseNamingValidator();
// First validate the schema
const validation = await validator.validateSchema({ tables: [tableSchema] });
if (validation.overallCompliance < 100) {
throw new Error(`Schema validation failed: ${validation.criticalIssues.length} critical issues found`);
}
// If validation passes, create the table
return await agent.createTable(tableSchema);
}
Agent Integration:
- Complete schema validation before creation
- Automated compliance checking
- Critical issue identification
- Recommendation generation
- Integration with table creation workflow
Step 6: Generate Compliance Report and Recommendations
Create comprehensive compliance assessment and improvement plan:
// Generate compliance report
function generateComplianceReport(validationResults, schema) {
const report = {
executiveSummary: {
overallCompliance: validationResults.overallCompliance,
totalIssues: validationResults.criticalIssues.length,
complianceLevel: getComplianceLevel(validationResults.overallCompliance),
deploymentReadiness: validationResults.overallCompliance >= 95 ? 'READY' : 'REQUIRES_FIXES'
},
detailedFindings: {
tableCompliance: generateTableComplianceSummary(validationResults.tables),
columnCompliance: generateColumnComplianceSummary(validationResults.columns),
indexCompliance: generateIndexComplianceSummary(validationResults.indexes),
constraintCompliance: generateConstraintComplianceSummary(validationResults.constraints)
},
criticalIssues: validationResults.criticalIssues,
improvementPlan: {
immediateActions: [],
shortTermGoals: [],
longTermObjectives: [],
preventiveMeasures: []
},
successMetrics: {
targetCompliance: 98,
currentCompliance: validationResults.overallCompliance,
issuesResolved: 0,
tablesCreatedViaAgent: 0
}
};
// Generate improvement plan
report.improvementPlan = generateImprovementPlan(validationResults);
// Add success metrics
report.successMetrics = calculateSuccessMetrics(validationResults, schema);
return report;
}
// Get compliance level description
function getComplianceLevel(percentage) {
if (percentage >= 98) return 'EXCELLENT';
if (percentage >= 95) return 'GOOD';
if (percentage >= 90) return 'FAIR';
if (percentage >= 80) return 'POOR';
return 'CRITICAL';
}
// Generate improvement plan
function generateImprovementPlan(validationResults) {
const plan = {
immediateActions: [],
shortTermGoals: [],
longTermObjectives: [],
preventiveMeasures: []
};
// Immediate actions for critical issues
if (validationResults.criticalIssues.length > 0) {
plan.immediateActions.push(
`Fix ${validationResults.criticalIssues.length} critical naming issues`,
'Review schema changes with database team',
'Update documentation for corrected naming patterns'
);
}
// Short-term goals
plan.shortTermGoals = [
'Achieve 95% naming compliance across all database objects',
'Implement automated validation in CI/CD pipeline',
'Train team members on naming standards',
'Create naming standards quick reference guide'
];
// Long-term objectives
plan.longTermObjectives = [
'Maintain 98%+ naming compliance for all new database objects',
'Automate schema documentation generation',
'Implement intelligent naming suggestions in development tools',
'Establish database naming standards certification program'
];
// Preventive measures
plan.preventiveMeasures = [
'Use Supabase Table Creation Agent for all table creation',
'Implement pre-commit naming validation hooks',
'Regular compliance audits and reporting',
'Continuous improvement of naming standards based on usage patterns'
];
return plan;
}
// Calculate success metrics
function calculateSuccessMetrics(validationResults, schema) {
return {
targetCompliance: 98.0,
currentCompliance: validationResults.overallCompliance,
issuesResolved: validationResults.criticalIssues.length,
tablesCreatedViaAgent: schema.tables.filter(t => t.created_via_agent).length,
complianceTrend: calculateComplianceTrend(validationResults),
topIssueCategories: identifyTopIssueCategories(validationResults)
};
}
// Generate detailed compliance summaries
function generateTableComplianceSummary(tables) {
const summary = {
totalTables: tables.length,
compliantTables: tables.filter(t => t.isValid).length,
nonCompliantTables: tables.filter(t => !t.isValid).length,
topIssues: [],
recommendations: []
};
// Calculate percentages
summary.compliancePercentage = (summary.compliantTables / summary.totalTables) * 100;
// Identify top issues
const issueCounts = {};
tables.forEach(table => {
table.errors.forEach(error => {
issueCounts[error] = (issueCounts[error] || 0) + 1;
});
});
summary.topIssues = Object.entries(issueCounts)
.sort(([,a], [,b]) => b - a)
.slice(0, 5)
.map(([issue, count]) => ({ issue, count }));
// Generate recommendations
if (summary.compliancePercentage < 90) {
summary.recommendations.push('Critical: Table naming compliance below 90%');
}
summary.recommendations.push('Use Supabase Table Creation Agent for all new tables');
summary.recommendations.push('Review existing tables for naming consistency opportunities');
return summary;
}
Compliance Reporting:
- Executive summary with compliance levels
- Detailed findings by object type
- Critical issues identification
- Improvement plan generation
- Success metrics calculation
Success Criteria
- Database object names validated against standards
- Table names follow appropriate patterns (discipline, core, relationship, vector, temporary)
- Column names use proper conventions (primary keys, foreign keys, data columns, status columns)
- Index names follow performance and uniqueness patterns
- Constraint names use type-specific prefixes
- Supabase Table Creation Agent used for validation
- Compliance report generated with improvement recommendations
- Critical naming issues identified and addressed
Common Pitfalls
- Discipline Code Errors - Using invalid or incorrect 5-digit discipline codes
- CamelCase Violations - Using camelCase instead of required snake_case
- Missing Prefixes - Forgetting required a_, t_, or v_ prefixes
- Inconsistent Entity Names - Using different names for the same entity across tables
- Audit Column Omissions - Missing required created_by, updated_by, etc. columns
- Index Naming Confusion - Mixing up performance vs unique index naming patterns
- Foreign Key Reference Issues - Incorrect foreign key column naming
Cross-References
Related Procedures
- Database Schema Management - Schema design and optimization
- Systematic Debugging - Database issue troubleshooting
- Verification Before Completion - Schema validation
Related Skills
database-schema-management- Database design and schema creationsystematic-debugging- Database issue investigationverification-before-completion- Schema validation and verification
Related Agents
Supabase Table Creation Agent- Automated table creation with validationDevForge_AI_Team- Database development assistanceQualityForge_AI_Team- Database quality assurance and validation