entity-mapper.vala 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. using Invercargill.DataStructures;
  2. using Invercargill.Mapping;
  3. namespace InvercargillSql.Orm {
  4. /**
  5. * EntityMapper wraps PropertyMapper<T> and adds ORM-specific metadata.
  6. *
  7. * This class is the core of the ORM system, providing bidirectional mapping
  8. * between entity objects and database records. It maintains column definitions
  9. * and schema metadata needed for SQL generation.
  10. *
  11. * Schema metadata (primary key, auto-increment, etc.) is discovered through
  12. * database introspection when using register_with_schema<T>().
  13. *
  14. * Example usage:
  15. * {{{
  16. * var mapper = EntityMapper.build_for<User>(b => b
  17. * .table("users")
  18. * .column<int64>("id", u => u.id, (u, v) => u.id = v)
  19. * .column<string>("name", u => u.name, (u, v) => u.name = v));
  20. * }}}
  21. */
  22. public class EntityMapper<T> : Object {
  23. /**
  24. * The database table name for this entity.
  25. */
  26. public string table_name { get; internal set; }
  27. /**
  28. * The underlying PropertyMapper used for entity materialization.
  29. */
  30. public PropertyMapper<T> property_mapper { get; internal set; }
  31. /**
  32. * Column definitions for this entity.
  33. */
  34. public Vector<ColumnDefinition> columns { get; internal set; }
  35. /**
  36. * The introspected table schema, or null if not using schema introspection.
  37. */
  38. public TableSchema? table_schema { get; internal set; }
  39. /**
  40. * The name of the primary key column, or null if not defined.
  41. *
  42. * This is set either through schema introspection or defaults to "id".
  43. */
  44. public string? primary_key_column { get; internal set; }
  45. /**
  46. * Creates an EntityMapper using a builder function.
  47. *
  48. * This is the recommended way to create EntityMapper instances.
  49. *
  50. * @param func A builder function that configures the mapper
  51. * @return A fully configured EntityMapper instance
  52. *
  53. * Example:
  54. * {{{
  55. * var mapper = EntityMapper.build_for<User>(b => b
  56. * .table("users")
  57. * .column<int64>("id", u => u.id, (u, v) => u.id = v)
  58. * .column<string>("name", u => u.name, (u, v) => u.name = v));
  59. * }}}
  60. */
  61. public static EntityMapper<T> build_for<T>(owned GLib.Func<EntityMapperBuilder<T>> func) {
  62. var builder = new EntityMapperBuilder<T>();
  63. func(builder);
  64. return builder.build();
  65. }
  66. /**
  67. * Materializes an entity from a Properties collection.
  68. *
  69. * This method is typically called with the results of a database query,
  70. * where column names map to property names.
  71. *
  72. * @param properties The property values from a database row
  73. * @return A new entity instance populated with the property values
  74. * @throws Error if materialization fails
  75. */
  76. public T materialise(Invercargill.Properties properties) throws Error {
  77. return property_mapper.materialise(properties);
  78. }
  79. /**
  80. * Maps an entity to a Properties collection.
  81. *
  82. * This method is used when preparing entity data for database insertion
  83. * or update operations.
  84. *
  85. * @param entity The entity to map
  86. * @return A Properties collection containing the entity's values
  87. * @throws Error if mapping fails
  88. */
  89. public Invercargill.Properties map_from(T entity) throws Error {
  90. return property_mapper.map_from(entity);
  91. }
  92. /**
  93. * Checks if a column is auto-incrementing.
  94. *
  95. * This method uses schema introspection data if available.
  96. *
  97. * @param column_name The column name to check
  98. * @return true if the column is auto-incrementing
  99. */
  100. public bool is_auto_increment(string column_name) {
  101. if (table_schema != null) {
  102. var col = table_schema.get_column(column_name);
  103. return col != null && col.auto_increment;
  104. }
  105. return false;
  106. }
  107. /**
  108. * Gets the effective primary key column name.
  109. *
  110. * Returns the primary key from schema introspection if available,
  111. * otherwise returns the explicitly set primary_key_column,
  112. * otherwise defaults to "id".
  113. *
  114. * @return The primary key column name
  115. */
  116. public string get_effective_primary_key() {
  117. if (table_schema != null && table_schema.primary_key_column != null) {
  118. return table_schema.primary_key_column;
  119. }
  120. return primary_key_column ?? "id";
  121. }
  122. /**
  123. * Sets a property value on an existing entity.
  124. *
  125. * This method is used to back-populate generated values (like auto-increment
  126. * primary keys) after an insert operation.
  127. *
  128. * @param entity The entity to modify
  129. * @param column_name The column/property name to set
  130. * @param value The value to set (will be converted to appropriate type)
  131. * @throws Error if the property cannot be set
  132. */
  133. public void set_property_value(T entity, string column_name, int64 value) throws Error {
  134. // Create a Properties collection with just this value
  135. var props = new PropertyDictionary();
  136. props.set_native<int64?>(column_name, value);
  137. // Use the PropertyMapper's map_into to set the value on the existing entity
  138. property_mapper.map_into(entity, props);
  139. }
  140. }
  141. }