1pub fn extract_relation_prefix(fk_column: &str, current_table: &str, ref_column: &str) -> String {
41 let ref_suffix = format!("_{}", ref_column);
43
44 let without_ref = if fk_column.ends_with(&ref_suffix) {
46 &fk_column[..fk_column.len() - ref_suffix.len()]
47 } else {
48 fk_column
49 };
50
51 let current_lower = current_table.to_lowercase();
52 let without_ref_lower = without_ref.to_lowercase();
53
54 if without_ref_lower == current_lower {
57 return String::new();
58 }
59
60 let table_suffix = format!("_{}", current_lower);
63 if without_ref_lower.ends_with(&table_suffix) {
64 let prefix_len = without_ref.len() - table_suffix.len();
65 return without_ref[..prefix_len].to_string();
66 }
67
68 without_ref.to_string()
71}
72
73pub fn build_reverse_relation_field_name(
86 fk_columns: &[String],
87 current_table: &str,
88 source_table: &str,
89 ref_column: &str,
90 has_multiple_fks: bool,
91 is_one_to_one: bool,
92) -> String {
93 let base_name = if is_one_to_one {
94 source_table.to_string()
95 } else {
96 pluralize(source_table)
97 };
98
99 if !has_multiple_fks || fk_columns.is_empty() {
100 return base_name;
101 }
102
103 let prefix = extract_relation_prefix(&fk_columns[0], current_table, ref_column);
104
105 if prefix.is_empty() {
106 base_name
107 } else {
108 format!("{}_{}", prefix, base_name)
109 }
110}
111
112pub fn build_relation_enum_name(
126 fk_columns: &[String],
127 current_table: &str,
128 ref_column: &str,
129) -> String {
130 if fk_columns.is_empty() {
131 return String::new();
132 }
133
134 let prefix = extract_relation_prefix(&fk_columns[0], current_table, ref_column);
135
136 if prefix.is_empty() {
137 String::new()
138 } else {
139 to_pascal_case(&prefix)
140 }
141}
142
143pub fn to_pascal_case(s: &str) -> String {
154 let mut result = String::new();
155 let mut capitalize = true;
156 for c in s.chars() {
157 let is_separator = c == '_' || c == '-';
158 if is_separator {
159 capitalize = true;
160 continue;
161 }
162 let ch = if capitalize {
163 c.to_ascii_uppercase()
164 } else {
165 c
166 };
167 capitalize = false;
168 result.push(ch);
169 }
170 result
171}
172
173pub fn pluralize(name: &str) -> String {
184 if name.ends_with('s') || name.ends_with("es") {
185 name.to_string()
186 } else if name.ends_with('y')
187 && !name.ends_with("ay")
188 && !name.ends_with("ey")
189 && !name.ends_with("oy")
190 && !name.ends_with("uy")
191 {
192 format!("{}ies", &name[..name.len() - 1])
194 } else {
195 format!("{}s", name)
196 }
197}
198
199pub fn build_index_name(table: &str, columns: &[String], key: Option<&str>) -> String {
208 match key {
209 Some(k) => format!("ix_{}__{}", table, k),
210 None => format!("ix_{}__{}", table, columns.join("_")),
211 }
212}
213
214pub fn build_unique_constraint_name(table: &str, columns: &[String], key: Option<&str>) -> String {
219 match key {
220 Some(k) => format!("uq_{}__{}", table, k),
221 None => format!("uq_{}__{}", table, columns.join("_")),
222 }
223}
224
225pub fn build_foreign_key_name(table: &str, columns: &[String], key: Option<&str>) -> String {
230 match key {
231 Some(k) => format!("fk_{}__{}", table, k),
232 None => format!("fk_{}__{}", table, columns.join("_")),
233 }
234}
235
236pub fn build_check_constraint_name(table: &str, column: &str) -> String {
240 format!("chk_{}__{}", table, column)
241}
242
243pub fn build_enum_type_name(table: &str, enum_name: &str) -> String {
250 format!("{}_{}", table, enum_name)
251}
252
253#[cfg(test)]
254mod tests {
255 use super::*;
256
257 #[test]
262 fn test_extract_relation_prefix_default_fk() {
263 assert_eq!(extract_relation_prefix("user_id", "user", "id"), "");
265 assert_eq!(extract_relation_prefix("org_id", "org", "id"), "");
266 assert_eq!(extract_relation_prefix("post_id", "post", "id"), "");
267 }
268
269 #[test]
270 fn test_extract_relation_prefix_different_ref_column() {
271 assert_eq!(extract_relation_prefix("user_idx", "user", "idx"), "");
273 assert_eq!(extract_relation_prefix("user_pk", "user", "pk"), "");
274 assert_eq!(extract_relation_prefix("user_key", "user", "key"), "");
275 }
276
277 #[test]
278 fn test_extract_relation_prefix_semantic_prefix() {
279 assert_eq!(
281 extract_relation_prefix("answered_by_user_id", "user", "id"),
282 "answered_by"
283 );
284 assert_eq!(
285 extract_relation_prefix("created_by_user_id", "user", "id"),
286 "created_by"
287 );
288 assert_eq!(
289 extract_relation_prefix("target_user_id", "user", "id"),
290 "target"
291 );
292 assert_eq!(
293 extract_relation_prefix("parent_org_id", "org", "id"),
294 "parent"
295 );
296 }
297
298 #[test]
299 fn test_extract_relation_prefix_role_fk() {
300 assert_eq!(extract_relation_prefix("author_id", "user", "id"), "author");
302 assert_eq!(extract_relation_prefix("owner_id", "user", "id"), "owner");
303 assert_eq!(
304 extract_relation_prefix("creator_id", "user", "id"),
305 "creator"
306 );
307 }
308
309 #[test]
310 fn test_extract_relation_prefix_no_suffix() {
311 assert_eq!(extract_relation_prefix("user", "user", "id"), "");
313 assert_eq!(extract_relation_prefix("admin_user", "user", "id"), "admin");
314 }
315
316 #[test]
317 fn test_build_reverse_relation_field_name_single_fk() {
318 assert_eq!(
320 build_reverse_relation_field_name(
321 &["user_id".into()],
322 "user",
323 "inquiry",
324 "id",
325 false,
326 false
327 ),
328 "inquiries"
329 );
330 assert_eq!(
331 build_reverse_relation_field_name(
332 &["author_id".into()],
333 "user",
334 "comment",
335 "id",
336 false,
337 false
338 ),
339 "comments"
340 );
341 }
342
343 #[test]
344 fn test_build_reverse_relation_field_name_multiple_fks() {
345 assert_eq!(
347 build_reverse_relation_field_name(
348 &["user_id".into()],
349 "user",
350 "inquiry",
351 "id",
352 true,
353 false
354 ),
355 "inquiries"
356 );
357 assert_eq!(
358 build_reverse_relation_field_name(
359 &["answered_by_user_id".into()],
360 "user",
361 "inquiry",
362 "id",
363 true,
364 false
365 ),
366 "answered_by_inquiries"
367 );
368 }
369
370 #[test]
371 fn test_build_reverse_relation_field_name_one_to_one() {
372 assert_eq!(
373 build_reverse_relation_field_name(
374 &["user_id".into()],
375 "user",
376 "profile",
377 "id",
378 false,
379 true
380 ),
381 "profile"
382 );
383 assert_eq!(
384 build_reverse_relation_field_name(
385 &["backup_user_id".into()],
386 "user",
387 "settings",
388 "id",
389 true,
390 true
391 ),
392 "backup_settings"
393 );
394 }
395
396 #[test]
397 fn test_build_relation_enum_name() {
398 assert_eq!(build_relation_enum_name(&[], "user", "id"), "");
400
401 assert_eq!(
403 build_relation_enum_name(&["user_id".into()], "user", "id"),
404 ""
405 );
406
407 assert_eq!(
409 build_relation_enum_name(&["answered_by_user_id".into()], "user", "id"),
410 "AnsweredBy"
411 );
412 assert_eq!(
413 build_relation_enum_name(&["target_user_id".into()], "user", "id"),
414 "Target"
415 );
416
417 assert_eq!(
419 build_relation_enum_name(&["author_id".into()], "user", "id"),
420 "Author"
421 );
422 }
423
424 #[test]
425 fn test_to_pascal_case() {
426 assert_eq!(to_pascal_case("hello_world"), "HelloWorld");
427 assert_eq!(to_pascal_case("answered_by"), "AnsweredBy");
428 assert_eq!(to_pascal_case("user"), "User");
429 assert_eq!(to_pascal_case("hello-world"), "HelloWorld");
430 assert_eq!(to_pascal_case(""), "");
431 }
432
433 #[test]
434 fn test_pluralize() {
435 assert_eq!(pluralize("inquiry"), "inquiries");
436 assert_eq!(pluralize("category"), "categories");
437 assert_eq!(pluralize("comment"), "comments");
438 assert_eq!(pluralize("user"), "users");
439 assert_eq!(pluralize("status"), "status");
440 assert_eq!(pluralize("address"), "address");
441 }
442
443 #[test]
448 fn test_build_index_name_with_key() {
449 assert_eq!(
450 build_index_name("users", &["email".into()], Some("email_idx")),
451 "ix_users__email_idx"
452 );
453 }
454
455 #[test]
456 fn test_build_index_name_without_key() {
457 assert_eq!(
458 build_index_name("users", &["email".into()], None),
459 "ix_users__email"
460 );
461 }
462
463 #[test]
464 fn test_build_index_name_multiple_columns() {
465 assert_eq!(
466 build_index_name("users", &["first_name".into(), "last_name".into()], None),
467 "ix_users__first_name_last_name"
468 );
469 }
470
471 #[test]
472 fn test_build_unique_constraint_name_with_key() {
473 assert_eq!(
474 build_unique_constraint_name("users", &["email".into()], Some("email_unique")),
475 "uq_users__email_unique"
476 );
477 }
478
479 #[test]
480 fn test_build_unique_constraint_name_without_key() {
481 assert_eq!(
482 build_unique_constraint_name("users", &["email".into()], None),
483 "uq_users__email"
484 );
485 }
486
487 #[test]
488 fn test_build_foreign_key_name_with_key() {
489 assert_eq!(
490 build_foreign_key_name("posts", &["user_id".into()], Some("fk_user")),
491 "fk_posts__fk_user"
492 );
493 }
494
495 #[test]
496 fn test_build_foreign_key_name_without_key() {
497 assert_eq!(
498 build_foreign_key_name("posts", &["user_id".into()], None),
499 "fk_posts__user_id"
500 );
501 }
502
503 #[test]
504 fn test_build_check_constraint_name() {
505 assert_eq!(
506 build_check_constraint_name("users", "status"),
507 "chk_users__status"
508 );
509 }
510
511 #[test]
512 fn test_build_enum_type_name() {
513 assert_eq!(build_enum_type_name("users", "status"), "users_status");
514 }
515
516 #[test]
517 fn test_build_enum_type_name_with_existing_prefix() {
518 assert_eq!(
521 build_enum_type_name("users", "user_status"),
522 "users_user_status"
523 );
524 }
525
526 #[test]
527 fn test_build_enum_type_name_prevents_conflicts() {
528 assert_eq!(build_enum_type_name("users", "gender"), "users_gender");
530 assert_eq!(
531 build_enum_type_name("employees", "gender"),
532 "employees_gender"
533 );
534
535 assert_eq!(build_enum_type_name("orders", "status"), "orders_status");
536 assert_eq!(
537 build_enum_type_name("shipments", "status"),
538 "shipments_status"
539 );
540 }
541}