|
@@ -1,4 +1,6 @@
|
|
|
using Invercargill.DataStructures;
|
|
using Invercargill.DataStructures;
|
|
|
|
|
+using Invercargill.Expressions;
|
|
|
|
|
+using Invercargill.Mapping;
|
|
|
|
|
|
|
|
namespace InvercargillSql.Orm.Projections {
|
|
namespace InvercargillSql.Orm.Projections {
|
|
|
|
|
|
|
@@ -9,11 +11,17 @@ namespace InvercargillSql.Orm.Projections {
|
|
|
* and materializes them into projection objects. It handles:
|
|
* and materializes them into projection objects. It handles:
|
|
|
* - Simple scalar properties (int, string, double, etc.)
|
|
* - Simple scalar properties (int, string, double, etc.)
|
|
|
* - Nested projection objects (select_one) - limited support
|
|
* - Nested projection objects (select_one) - limited support
|
|
|
- * - Collection properties (select_many) - placeholder for future implementation
|
|
|
|
|
|
|
+ * - Collection properties (select_many) with three modes:
|
|
|
|
|
+ * - SCALAR: Extract values directly from columns
|
|
|
|
|
+ * - ENTITY: Materialize using EntityMapper
|
|
|
|
|
+ * - PROJECTION: Materialize using ProjectionMapper
|
|
|
*
|
|
*
|
|
|
* The mapper uses the ProjectionDefinition to determine how to map each column
|
|
* The mapper uses the ProjectionDefinition to determine how to map each column
|
|
|
* to the corresponding property on the projection type.
|
|
* to the corresponding property on the projection type.
|
|
|
*
|
|
*
|
|
|
|
|
+ * For collection selections, rows are grouped by the parent key extracted from
|
|
|
|
|
+ * the join condition analysis.
|
|
|
|
|
+ *
|
|
|
* Example usage:
|
|
* Example usage:
|
|
|
* {{{
|
|
* {{{
|
|
|
* var mapper = new ProjectionMapper<UserStats>(definition);
|
|
* var mapper = new ProjectionMapper<UserStats>(definition);
|
|
@@ -29,6 +37,11 @@ namespace InvercargillSql.Orm.Projections {
|
|
|
*/
|
|
*/
|
|
|
private ProjectionDefinition _mapper_definition;
|
|
private ProjectionDefinition _mapper_definition;
|
|
|
|
|
|
|
|
|
|
+ /**
|
|
|
|
|
+ * The type provider for entity mapper lookups during ENTITY mode materialization.
|
|
|
|
|
+ */
|
|
|
|
|
+ private TypeProvider? _type_provider;
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Creates a new ProjectionMapper for the given definition.
|
|
* Creates a new ProjectionMapper for the given definition.
|
|
|
*
|
|
*
|
|
@@ -36,6 +49,18 @@ namespace InvercargillSql.Orm.Projections {
|
|
|
*/
|
|
*/
|
|
|
public ProjectionMapper(ProjectionDefinition definition) {
|
|
public ProjectionMapper(ProjectionDefinition definition) {
|
|
|
_mapper_definition = definition;
|
|
_mapper_definition = definition;
|
|
|
|
|
+ _type_provider = null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Creates a new ProjectionMapper with a type provider for entity materialization.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param definition The ProjectionDefinition describing how to map results
|
|
|
|
|
+ * @param type_provider The type provider for entity mapper lookups
|
|
|
|
|
+ */
|
|
|
|
|
+ public ProjectionMapper.with_type_provider(ProjectionDefinition definition, TypeProvider type_provider) {
|
|
|
|
|
+ _mapper_definition = definition;
|
|
|
|
|
+ _type_provider = type_provider;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -65,7 +90,8 @@ namespace InvercargillSql.Orm.Projections {
|
|
|
* Maps a single database row to a projection instance.
|
|
* Maps a single database row to a projection instance.
|
|
|
*
|
|
*
|
|
|
* This method takes a Properties collection (representing a database row)
|
|
* This method takes a Properties collection (representing a database row)
|
|
|
- * and creates a new TProjection instance with all properties populated.
|
|
|
|
|
|
|
+ * and creates a new TProjection instance with scalar properties populated.
|
|
|
|
|
+ * Note: Collection selections are not populated in map_row - use map_all for that.
|
|
|
*
|
|
*
|
|
|
* @param row The Properties collection from a database query
|
|
* @param row The Properties collection from a database query
|
|
|
* @return A new TProjection instance
|
|
* @return A new TProjection instance
|
|
@@ -80,8 +106,12 @@ namespace InvercargillSql.Orm.Projections {
|
|
|
);
|
|
);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Map each selection from the definition
|
|
|
|
|
|
|
+ // Map each scalar selection from the definition
|
|
|
foreach (var selection in _mapper_definition.selections) {
|
|
foreach (var selection in _mapper_definition.selections) {
|
|
|
|
|
+ // Skip collection selections - they are handled in map_all
|
|
|
|
|
+ if (selection is CollectionSelection || selection is CollectionProjectionSelection) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
map_selection_to_object(obj, selection, row);
|
|
map_selection_to_object(obj, selection, row);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -94,8 +124,14 @@ namespace InvercargillSql.Orm.Projections {
|
|
|
* This method iterates through all rows in the result set and
|
|
* This method iterates through all rows in the result set and
|
|
|
* materializes each one as a TProjection instance.
|
|
* materializes each one as a TProjection instance.
|
|
|
*
|
|
*
|
|
|
- * For projections with collections (select_many), this method also
|
|
|
|
|
- * performs client-side grouping to consolidate related rows.
|
|
|
|
|
|
|
+ * For projections with collections (select_many), this method:
|
|
|
|
|
+ * 1. Groups rows by the parent key (extracted from join condition)
|
|
|
|
|
+ * 2. Creates one projection instance per unique parent key
|
|
|
|
|
+ * 3. Collects child values for each group based on item mode:
|
|
|
|
|
+ * - SCALAR: Extracts values directly from columns
|
|
|
|
|
+ * - ENTITY: Materializes using EntityMapper
|
|
|
|
|
+ * - PROJECTION: Materializes using ProjectionMapper
|
|
|
|
|
+ * 4. Calls setters with Enumerable<TItem> (empty if no child rows)
|
|
|
*
|
|
*
|
|
|
* @param results An Enumerable of Properties collections
|
|
* @param results An Enumerable of Properties collections
|
|
|
* @return A Vector of TProjection instances
|
|
* @return A Vector of TProjection instances
|
|
@@ -107,20 +143,11 @@ namespace InvercargillSql.Orm.Projections {
|
|
|
var mapped_results = new Vector<TProjection>();
|
|
var mapped_results = new Vector<TProjection>();
|
|
|
|
|
|
|
|
// Check if we have any collection selections that need grouping
|
|
// Check if we have any collection selections that need grouping
|
|
|
- bool needs_grouping = false;
|
|
|
|
|
- foreach (var selection in _mapper_definition.selections) {
|
|
|
|
|
- if (selection is CollectionProjectionSelection) {
|
|
|
|
|
- needs_grouping = true;
|
|
|
|
|
- break;
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ bool has_collections = _mapper_definition.has_collection_selections();
|
|
|
|
|
|
|
|
- if (needs_grouping) {
|
|
|
|
|
- // For now, use simple mapping without grouping
|
|
|
|
|
- // TODO: Implement proper grouping when needed
|
|
|
|
|
- foreach (var row in results) {
|
|
|
|
|
- mapped_results.add(map_row(row));
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ if (has_collections) {
|
|
|
|
|
+ // Group rows by parent key and materialize with collections
|
|
|
|
|
+ return map_all_with_grouping(results);
|
|
|
} else {
|
|
} else {
|
|
|
// Simple case: each row maps to one projection
|
|
// Simple case: each row maps to one projection
|
|
|
foreach (var row in results) {
|
|
foreach (var row in results) {
|
|
@@ -132,324 +159,586 @@ namespace InvercargillSql.Orm.Projections {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Maps a selection to a generic Object instance.
|
|
|
|
|
|
|
+ * Maps rows with grouping for collection selections.
|
|
|
*
|
|
*
|
|
|
- * This method dispatches to the appropriate handler based on the
|
|
|
|
|
- * selection type (ScalarSelection, NestedProjectionSelection, or
|
|
|
|
|
- * CollectionProjectionSelection).
|
|
|
|
|
|
|
+ * This method implements the full grouping and collection materialization logic:
|
|
|
|
|
+ * 1. Groups rows by the parent key
|
|
|
|
|
+ * 2. Creates one projection per unique parent key
|
|
|
|
|
+ * 3. Populates scalar properties from the first row in each group
|
|
|
|
|
+ * 4. Collects child values from all rows in the group
|
|
|
*
|
|
*
|
|
|
- * @param instance The Object instance to populate
|
|
|
|
|
- * @param selection The selection definition
|
|
|
|
|
- * @param row The database row data
|
|
|
|
|
|
|
+ * @param results An Enumerable of Properties collections
|
|
|
|
|
+ * @return A Vector of TProjection instances with collections populated
|
|
|
* @throws ProjectionError if mapping fails
|
|
* @throws ProjectionError if mapping fails
|
|
|
*/
|
|
*/
|
|
|
- private void map_selection_to_object(
|
|
|
|
|
- Object instance,
|
|
|
|
|
- SelectionDefinition selection,
|
|
|
|
|
- Invercargill.Properties row
|
|
|
|
|
|
|
+ private Vector<TProjection> map_all_with_grouping(
|
|
|
|
|
+ Invercargill.Enumerable<Invercargill.Properties> results
|
|
|
) throws ProjectionError {
|
|
) throws ProjectionError {
|
|
|
|
|
|
|
|
- // Check if this is a scalar selection (has value_type that's not a projection)
|
|
|
|
|
- var nested_type = selection.nested_projection_type;
|
|
|
|
|
|
|
+ var mapped_results = new Vector<TProjection>();
|
|
|
|
|
|
|
|
- if (nested_type == null) {
|
|
|
|
|
- // Scalar selection
|
|
|
|
|
- map_scalar_selection(instance, selection, row);
|
|
|
|
|
|
|
+ // We need to group by the parent key. For simplicity, we'll use the first
|
|
|
|
|
+ // collection selection's grouping key as the primary grouping key.
|
|
|
|
|
+ // This assumes all collections join from the same parent.
|
|
|
|
|
+ string? primary_grouping_key = null;
|
|
|
|
|
+ Expression? primary_grouping_expression = null;
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var selection in _mapper_definition.selections) {
|
|
|
|
|
+ if (selection is CollectionSelection || selection is CollectionProjectionSelection) {
|
|
|
|
|
+ var info = _mapper_definition.get_collection_grouping_info(selection.friendly_name);
|
|
|
|
|
+ if (info != null) {
|
|
|
|
|
+ primary_grouping_key = selection.friendly_name;
|
|
|
|
|
+ primary_grouping_expression = info.grouping_key_expression;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (primary_grouping_key == null || primary_grouping_expression == null) {
|
|
|
|
|
+ // No valid grouping info - fall back to simple mapping
|
|
|
|
|
+ foreach (var row in results) {
|
|
|
|
|
+ mapped_results.add(map_row(row));
|
|
|
|
|
+ }
|
|
|
|
|
+ return mapped_results;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Group rows by the parent key
|
|
|
|
|
+ var groups = new Dictionary<string, Vector<Invercargill.Properties>>();
|
|
|
|
|
+ var key_to_first_row = new Dictionary<string, Invercargill.Properties>();
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var row in results) {
|
|
|
|
|
+ string? key = extract_grouping_key(primary_grouping_expression, row);
|
|
|
|
|
+ if (key == null) {
|
|
|
|
|
+ // Skip rows without a valid key (shouldn't happen in practice)
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (!groups.has(key)) {
|
|
|
|
|
+ groups.set(key, new Vector<Invercargill.Properties>());
|
|
|
|
|
+ key_to_first_row.set(key, row);
|
|
|
|
|
+ }
|
|
|
|
|
+ groups.get(key).add(row);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Materialize each group
|
|
|
|
|
+ foreach (var key in groups.keys) {
|
|
|
|
|
+ var group_rows = groups.get(key);
|
|
|
|
|
+ var first_row = key_to_first_row.get(key);
|
|
|
|
|
+
|
|
|
|
|
+ // Create projection instance with scalar properties
|
|
|
|
|
+ var instance = map_row(first_row);
|
|
|
|
|
+ var obj = instance as Object;
|
|
|
|
|
+ if (obj == null) {
|
|
|
|
|
+ continue;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Populate collection selections
|
|
|
|
|
+ foreach (var selection in _mapper_definition.selections) {
|
|
|
|
|
+ if (selection is CollectionSelection) {
|
|
|
|
|
+ populate_collection_selection(obj, (CollectionSelection) selection, group_rows);
|
|
|
|
|
+ } else if (selection is CollectionProjectionSelection) {
|
|
|
|
|
+ populate_legacy_collection_selection(obj, (CollectionProjectionSelection) selection, group_rows);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ mapped_results.add(instance);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return mapped_results;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Extracts a grouping key from a row based on the grouping expression.
|
|
|
|
|
+ *
|
|
|
|
|
+ * The key is extracted from the row based on the grouping expression.
|
|
|
|
|
+ * For now, we assume the expression is a simple property reference like "u.id"
|
|
|
|
|
+ * and we look up the value using the column_alias (if set) or friendly name
|
|
|
|
|
+ * from the scalar selections.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param grouping_expression The expression to extract the key from
|
|
|
|
|
+ * @param row The Properties row to extract from
|
|
|
|
|
+ * @return A string key for grouping, or null if not found
|
|
|
|
|
+ */
|
|
|
|
|
+ private string? extract_grouping_key(Expression grouping_expression, Invercargill.Properties row) {
|
|
|
|
|
+ // Find the selection that matches this expression
|
|
|
|
|
+ // For a grouping expression like "u.id", we look for a scalar selection
|
|
|
|
|
+ // with a matching expression
|
|
|
|
|
+ SelectionDefinition? matching_selection = null;
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var selection in _mapper_definition.selections) {
|
|
|
|
|
+ if (selection is ScalarSelection) {
|
|
|
|
|
+ var scalar = (ScalarSelection) selection;
|
|
|
|
|
+ // Check if expressions match by comparing their string representation
|
|
|
|
|
+ // This is a simplification - ideally we'd compare expression trees
|
|
|
|
|
+ if (expressions_equal(scalar.expression, grouping_expression)) {
|
|
|
|
|
+ matching_selection = selection;
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Determine the column name to use
|
|
|
|
|
+ string? column_name = null;
|
|
|
|
|
+
|
|
|
|
|
+ if (matching_selection != null) {
|
|
|
|
|
+ // Use column_alias if set (from SQL building), otherwise fall back to friendly_name
|
|
|
|
|
+ column_name = matching_selection.column_alias ?? matching_selection.friendly_name;
|
|
|
} else {
|
|
} else {
|
|
|
- // Check if it's a collection or nested projection
|
|
|
|
|
- // For now, treat all nested types as scalar objects
|
|
|
|
|
- map_scalar_selection(instance, selection, row);
|
|
|
|
|
|
|
+ // If we didn't find a match, try to derive the column name from the expression
|
|
|
|
|
+ column_name = expression_to_column_name(grouping_expression);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ if (column_name == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var element = row.get(column_name);
|
|
|
|
|
+ if (element == null || element.is_null()) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Convert element to string for use as hash key
|
|
|
|
|
+ return element_to_key_string(element);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Maps a scalar selection to a property.
|
|
|
|
|
|
|
+ * Checks if two expressions are equal by comparing their structure.
|
|
|
*
|
|
*
|
|
|
- * Scalar selections represent simple values like int, string, double, etc.
|
|
|
|
|
- * The value is extracted from the row, converted to the appropriate type,
|
|
|
|
|
- * and set on the projection instance.
|
|
|
|
|
|
|
+ * This is a simplified comparison that works for common cases.
|
|
|
|
|
+ */
|
|
|
|
|
+ private bool expressions_equal(Expression a, Expression b) {
|
|
|
|
|
+ // For now, compare by converting to strings
|
|
|
|
|
+ // This works for simple property expressions
|
|
|
|
|
+ return expression_to_string(a) == expression_to_string(b);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Converts an expression to a string for comparison.
|
|
|
|
|
+ */
|
|
|
|
|
+ private string expression_to_string(Expression expr) {
|
|
|
|
|
+ if (expr is VariableExpression) {
|
|
|
|
|
+ return ((VariableExpression) expr).variable_name;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (expr is PropertyExpression) {
|
|
|
|
|
+ var prop = (PropertyExpression) expr;
|
|
|
|
|
+ return expression_to_string(prop.target) + "." + prop.property_name;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (expr is BinaryExpression) {
|
|
|
|
|
+ var binary = (BinaryExpression) expr;
|
|
|
|
|
+ return expression_to_string(binary.left) + " " +
|
|
|
|
|
+ binary.op.to_string() + " " +
|
|
|
|
|
+ expression_to_string(binary.right);
|
|
|
|
|
+ }
|
|
|
|
|
+ // Fallback for other expression types
|
|
|
|
|
+ return expr.get_type().name();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Derives a column name from an expression.
|
|
|
*
|
|
*
|
|
|
- * @param instance The Object instance
|
|
|
|
|
- * @param selection The selection definition
|
|
|
|
|
- * @param row The database row data
|
|
|
|
|
- * @throws ProjectionError if mapping fails
|
|
|
|
|
|
|
+ * For "u.id", returns "id" or looks for a matching selection.
|
|
|
*/
|
|
*/
|
|
|
- private void map_scalar_selection(
|
|
|
|
|
- Object instance,
|
|
|
|
|
- SelectionDefinition selection,
|
|
|
|
|
- Invercargill.Properties row
|
|
|
|
|
- ) throws ProjectionError {
|
|
|
|
|
|
|
+ private string? expression_to_column_name(Expression expr) {
|
|
|
|
|
+ // For property expressions like "u.id", we want to find the corresponding
|
|
|
|
|
+ // column name. This typically matches a friendly name in the selections.
|
|
|
|
|
+ if (expr is PropertyExpression) {
|
|
|
|
|
+ var prop = (PropertyExpression) expr;
|
|
|
|
|
+ // Look for a scalar selection that ends with this property
|
|
|
|
|
+ foreach (var selection in _mapper_definition.selections) {
|
|
|
|
|
+ if (selection is ScalarSelection) {
|
|
|
|
|
+ var scalar = (ScalarSelection) selection;
|
|
|
|
|
+ if (scalar.expression is PropertyExpression) {
|
|
|
|
|
+ var scalar_prop = (PropertyExpression) scalar.expression;
|
|
|
|
|
+ if (scalar_prop.property_name == prop.property_name) {
|
|
|
|
|
+ return selection.friendly_name;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ // Fallback to property name
|
|
|
|
|
+ return prop.property_name;
|
|
|
|
|
+ }
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Converts an Element to a string suitable for use as a hash key.
|
|
|
|
|
+ */
|
|
|
|
|
+ private string element_to_key_string(Invercargill.Element element) {
|
|
|
|
|
+ // Try common types
|
|
|
|
|
+ int64? int_val = null;
|
|
|
|
|
+ if (element.try_get_as<int64?>(out int_val)) {
|
|
|
|
|
+ return int_val.to_string();
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- string column_name = selection.friendly_name;
|
|
|
|
|
|
|
+ string? str_val = null;
|
|
|
|
|
+ if (element.try_get_as<string>(out str_val)) {
|
|
|
|
|
+ return str_val ?? "";
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // Try to get the value from the row
|
|
|
|
|
- var element = row.get(column_name);
|
|
|
|
|
- if (element == null) {
|
|
|
|
|
- // Value not present in row - could be null or missing column
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ int? int32_val = null;
|
|
|
|
|
+ if (element.try_get_as<int?>(out int32_val)) {
|
|
|
|
|
+ return int32_val.to_string();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Get the value from the element
|
|
|
|
|
- Value? val = extract_value_from_element(element, selection.value_type);
|
|
|
|
|
- if (val == null) {
|
|
|
|
|
- return;
|
|
|
|
|
|
|
+ double? double_val = null;
|
|
|
|
|
+ if (element.try_get_as<double?>(out double_val)) {
|
|
|
|
|
+ return double_val.to_string();
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Set the property on the instance
|
|
|
|
|
- set_property_on_object(instance, selection.friendly_name, val);
|
|
|
|
|
|
|
+ // Fallback
|
|
|
|
|
+ return element.to_string() ?? "";
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Extracts a value from an Element, converting to the target type.
|
|
|
|
|
|
|
+ * Populates a collection selection for a projection instance.
|
|
|
*
|
|
*
|
|
|
- * Uses the Element's try_get_as<T>() method for type-safe extraction.
|
|
|
|
|
|
|
+ * This method handles the three collection item modes:
|
|
|
|
|
+ * - SCALAR: Extracts values directly from the column using raw Elements
|
|
|
|
|
+ * - ENTITY: Materializes using EntityMapper
|
|
|
|
|
+ * - PROJECTION: Materializes using ProjectionMapper
|
|
|
*
|
|
*
|
|
|
- * @param element The Element containing the value
|
|
|
|
|
- * @param target_type The target Vala type
|
|
|
|
|
- * @return The converted Value, or null if conversion fails
|
|
|
|
|
|
|
+ * @param instance The projection instance to populate
|
|
|
|
|
+ * @param selection The collection selection definition
|
|
|
|
|
+ * @param rows All rows in the group
|
|
|
*/
|
|
*/
|
|
|
- private Value? extract_value_from_element(
|
|
|
|
|
- Invercargill.Element element,
|
|
|
|
|
- Type target_type
|
|
|
|
|
- ) {
|
|
|
|
|
- if (element.is_null()) {
|
|
|
|
|
- return null;
|
|
|
|
|
|
|
+ private void populate_collection_selection(
|
|
|
|
|
+ Object instance,
|
|
|
|
|
+ CollectionSelection selection,
|
|
|
|
|
+ Vector<Invercargill.Properties> rows
|
|
|
|
|
+ ) throws ProjectionError {
|
|
|
|
|
+ // Get the child column name for extracting values
|
|
|
|
|
+ string child_column = extract_child_column_name(selection);
|
|
|
|
|
+
|
|
|
|
|
+ // For SCALAR mode, collect raw Elements and use apply_scalar_collection
|
|
|
|
|
+ if (selection.item_mode == CollectionItemMode.SCALAR) {
|
|
|
|
|
+ var elements = new Vector<Invercargill.Element>();
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var row in rows) {
|
|
|
|
|
+ var element = row.get(child_column);
|
|
|
|
|
+ if (element == null || element.is_null()) {
|
|
|
|
|
+ continue; // Skip null values
|
|
|
|
|
+ }
|
|
|
|
|
+ elements.add(element);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Use the selection's apply_scalar_collection method which properly
|
|
|
|
|
+ // extracts values as the correct type TItem
|
|
|
|
|
+ selection.apply_scalar_collection(instance, elements);
|
|
|
|
|
+ return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- Value result = Value(target_type);
|
|
|
|
|
|
|
+ // For ENTITY/PROJECTION modes, collect items as Objects
|
|
|
|
|
+ var items = new Vector<Object>();
|
|
|
|
|
|
|
|
- // Use try_get_as for type-safe extraction
|
|
|
|
|
- if (target_type == typeof(int64)) {
|
|
|
|
|
- int64? val = null;
|
|
|
|
|
- if (element.try_get_as<int64?>(out val) && val != null) {
|
|
|
|
|
- result.set_int64(val);
|
|
|
|
|
- return result;
|
|
|
|
|
- }
|
|
|
|
|
- } else if (target_type == typeof(int)) {
|
|
|
|
|
- int? val = null;
|
|
|
|
|
- if (element.try_get_as<int>(out val) && val != null) {
|
|
|
|
|
- result.set_int(val);
|
|
|
|
|
- return result;
|
|
|
|
|
- }
|
|
|
|
|
- } else if (target_type == typeof(long)) {
|
|
|
|
|
- long? val = null;
|
|
|
|
|
- if (element.try_get_as<long>(out val) && val != null) {
|
|
|
|
|
- result.set_long(val);
|
|
|
|
|
- return result;
|
|
|
|
|
- }
|
|
|
|
|
- } else if (target_type == typeof(double)) {
|
|
|
|
|
- double? val = null;
|
|
|
|
|
- if (element.try_get_as<double?>(out val) && val != null) {
|
|
|
|
|
- result.set_double(val);
|
|
|
|
|
- return result;
|
|
|
|
|
|
|
+ foreach (var row in rows) {
|
|
|
|
|
+ var element = row.get(child_column);
|
|
|
|
|
+ if (element == null || element.is_null()) {
|
|
|
|
|
+ continue; // Skip null values
|
|
|
}
|
|
}
|
|
|
- } else if (target_type == typeof(float)) {
|
|
|
|
|
- float? val = null;
|
|
|
|
|
- if (element.try_get_as<float?>(out val) && val != null) {
|
|
|
|
|
- result.set_float(val);
|
|
|
|
|
- return result;
|
|
|
|
|
- }
|
|
|
|
|
- } else if (target_type == typeof(string)) {
|
|
|
|
|
- string? val = null;
|
|
|
|
|
- if (element.try_get_as<string>(out val) && val != null) {
|
|
|
|
|
- result.set_string(val);
|
|
|
|
|
- return result;
|
|
|
|
|
- }
|
|
|
|
|
- } else if (target_type == typeof(bool)) {
|
|
|
|
|
- bool? val = null;
|
|
|
|
|
- if (element.try_get_as<bool>(out val) && val != null) {
|
|
|
|
|
- result.set_boolean(val);
|
|
|
|
|
- return result;
|
|
|
|
|
- }
|
|
|
|
|
- } else if (target_type == typeof(DateTime)) {
|
|
|
|
|
- DateTime? val = null;
|
|
|
|
|
- if (element.try_get_as<DateTime>(out val) && val != null) {
|
|
|
|
|
- result.set_boxed(val);
|
|
|
|
|
- return result;
|
|
|
|
|
- }
|
|
|
|
|
- } else if (target_type == typeof(Invercargill.BinaryData)) {
|
|
|
|
|
- Invercargill.BinaryData? val = null;
|
|
|
|
|
- if (element.try_get_as<Invercargill.BinaryData>(out val) && val != null) {
|
|
|
|
|
- result.set_object(val as Object);
|
|
|
|
|
- return result;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ switch (selection.item_mode) {
|
|
|
|
|
+ case CollectionItemMode.ENTITY:
|
|
|
|
|
+ // For entity mode, materialize using EntityMapper
|
|
|
|
|
+ var entity = materialize_entity(selection.item_entity_type, row);
|
|
|
|
|
+ if (entity != null) {
|
|
|
|
|
+ items.add(entity);
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ case CollectionItemMode.PROJECTION:
|
|
|
|
|
+ // For projection mode, materialize using ProjectionMapper
|
|
|
|
|
+ var projection = materialize_nested_projection(selection.nested_projection_type, row);
|
|
|
|
|
+ if (projection != null) {
|
|
|
|
|
+ items.add(projection);
|
|
|
|
|
+ }
|
|
|
|
|
+ break;
|
|
|
|
|
+
|
|
|
|
|
+ default:
|
|
|
|
|
+ break;
|
|
|
}
|
|
}
|
|
|
- } else if (target_type.is_object()) {
|
|
|
|
|
- // For other object types, try direct object extraction
|
|
|
|
|
- Object? val = null;
|
|
|
|
|
- if (element.try_get_as<Object>(out val) && val != null) {
|
|
|
|
|
- result.set_object(val);
|
|
|
|
|
- return result;
|
|
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Apply the collection using the selection's setter
|
|
|
|
|
+ apply_collection_to_instance(instance, selection, items);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Populates a legacy CollectionProjectionSelection.
|
|
|
|
|
+ *
|
|
|
|
|
+ * This handles the older-style collection selections that only support projections.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param instance The projection instance to populate
|
|
|
|
|
+ * @param selection The collection projection selection definition
|
|
|
|
|
+ * @param rows All rows in the group
|
|
|
|
|
+ */
|
|
|
|
|
+ private void populate_legacy_collection_selection(
|
|
|
|
|
+ Object instance,
|
|
|
|
|
+ CollectionProjectionSelection selection,
|
|
|
|
|
+ Vector<Invercargill.Properties> rows
|
|
|
|
|
+ ) throws ProjectionError {
|
|
|
|
|
+ var nested_type = selection.nested_projection_type;
|
|
|
|
|
+ if (nested_type == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ var items = new Vector<Object>();
|
|
|
|
|
+
|
|
|
|
|
+ foreach (var row in rows) {
|
|
|
|
|
+ var projection = materialize_nested_projection(nested_type, row);
|
|
|
|
|
+ if (projection != null) {
|
|
|
|
|
+ items.add(projection);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Fallback: could not convert
|
|
|
|
|
- return null;
|
|
|
|
|
|
|
+ // Apply using the selection's apply_element_value with a wrapped collection
|
|
|
|
|
+ apply_legacy_collection_to_instance(instance, selection, items);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Converts a value to the target type.
|
|
|
|
|
- *
|
|
|
|
|
- * This is a secondary conversion method for when we already have a Value
|
|
|
|
|
- * but need it in a different type.
|
|
|
|
|
|
|
+ * Extracts the child column name from a collection selection.
|
|
|
*
|
|
*
|
|
|
- * @param raw_value The raw value
|
|
|
|
|
- * @param target_type The target Vala type
|
|
|
|
|
- * @return The converted Value
|
|
|
|
|
|
|
+ * For SCALAR mode: Returns the column_alias if set (from SQL building),
|
|
|
|
|
+ * otherwise falls back to the property name from entry_point_expression
|
|
|
|
|
+ * For ENTITY/PROJECTION mode: Returns the column_alias if set, otherwise friendly_name
|
|
|
*/
|
|
*/
|
|
|
- private Value convert_value(Value raw_value, Type target_type) {
|
|
|
|
|
- Value result = Value(target_type);
|
|
|
|
|
-
|
|
|
|
|
- if (target_type == typeof(int64)) {
|
|
|
|
|
- if (raw_value.type() == typeof(int64)) {
|
|
|
|
|
- result.set_int64(raw_value.get_int64());
|
|
|
|
|
- } else if (raw_value.type() == typeof(int)) {
|
|
|
|
|
- result.set_int64((int64)raw_value.get_int());
|
|
|
|
|
- } else if (raw_value.type() == typeof(long)) {
|
|
|
|
|
- result.set_int64((int64)raw_value.get_long());
|
|
|
|
|
- } else if (raw_value.type() == typeof(string)) {
|
|
|
|
|
- int64 parsed = 0;
|
|
|
|
|
- if (int64.try_parse(raw_value.get_string(), out parsed)) {
|
|
|
|
|
- result.set_int64(parsed);
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- result.set_int64(raw_value.get_int64());
|
|
|
|
|
- }
|
|
|
|
|
- } else if (target_type == typeof(int)) {
|
|
|
|
|
- if (raw_value.type() == typeof(int64)) {
|
|
|
|
|
- result.set_int((int)raw_value.get_int64());
|
|
|
|
|
- } else if (raw_value.type() == typeof(int)) {
|
|
|
|
|
- result.set_int(raw_value.get_int());
|
|
|
|
|
- } else {
|
|
|
|
|
- result.set_int((int)raw_value.get_int64());
|
|
|
|
|
- }
|
|
|
|
|
- } else if (target_type == typeof(double)) {
|
|
|
|
|
- if (raw_value.type() == typeof(double)) {
|
|
|
|
|
- result.set_double(raw_value.get_double());
|
|
|
|
|
- } else if (raw_value.type() == typeof(float)) {
|
|
|
|
|
- result.set_double((double)raw_value.get_float());
|
|
|
|
|
- } else if (raw_value.type() == typeof(string)) {
|
|
|
|
|
- double parsed = 0.0;
|
|
|
|
|
- if (double.try_parse(raw_value.get_string(), out parsed)) {
|
|
|
|
|
- result.set_double(parsed);
|
|
|
|
|
- }
|
|
|
|
|
- } else {
|
|
|
|
|
- result.set_double(raw_value.get_double());
|
|
|
|
|
- }
|
|
|
|
|
- } else if (target_type == typeof(float)) {
|
|
|
|
|
- if (raw_value.type() == typeof(double)) {
|
|
|
|
|
- result.set_float((float)raw_value.get_double());
|
|
|
|
|
- } else if (raw_value.type() == typeof(float)) {
|
|
|
|
|
- result.set_float(raw_value.get_float());
|
|
|
|
|
- } else {
|
|
|
|
|
- result.set_float((float)raw_value.get_double());
|
|
|
|
|
- }
|
|
|
|
|
- } else if (target_type == typeof(string)) {
|
|
|
|
|
- if (raw_value.type() == typeof(string)) {
|
|
|
|
|
- result.set_string(raw_value.get_string());
|
|
|
|
|
- } else if (raw_value.type() == typeof(int64)) {
|
|
|
|
|
- result.set_string(raw_value.get_int64().to_string());
|
|
|
|
|
- } else if (raw_value.type() == typeof(double)) {
|
|
|
|
|
- result.set_string(raw_value.get_double().to_string());
|
|
|
|
|
- } else if (raw_value.type() == typeof(bool)) {
|
|
|
|
|
- result.set_string(raw_value.get_boolean() ? "true" : "false");
|
|
|
|
|
- }
|
|
|
|
|
- } else if (target_type == typeof(bool)) {
|
|
|
|
|
- if (raw_value.type() == typeof(int64)) {
|
|
|
|
|
- result.set_boolean(raw_value.get_int64() != 0);
|
|
|
|
|
- } else if (raw_value.type() == typeof(int)) {
|
|
|
|
|
- result.set_boolean(raw_value.get_int() != 0);
|
|
|
|
|
- } else if (raw_value.type() == typeof(bool)) {
|
|
|
|
|
- result.set_boolean(raw_value.get_boolean());
|
|
|
|
|
- } else {
|
|
|
|
|
- result.set_boolean(raw_value.get_boolean());
|
|
|
|
|
- }
|
|
|
|
|
- } else if (target_type.is_object()) {
|
|
|
|
|
- result.set_object(raw_value.get_object());
|
|
|
|
|
- } else {
|
|
|
|
|
- result = raw_value;
|
|
|
|
|
|
|
+ private string extract_child_column_name(CollectionSelection selection) {
|
|
|
|
|
+ // Use column_alias if available (set during SQL building)
|
|
|
|
|
+ if (selection.column_alias != null) {
|
|
|
|
|
+ return selection.column_alias;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- return result;
|
|
|
|
|
|
|
+ var expr = selection.entry_point_expression;
|
|
|
|
|
+
|
|
|
|
|
+ if (selection.item_mode == CollectionItemMode.SCALAR && expr is PropertyExpression) {
|
|
|
|
|
+ // For scalar mode, the expression is like "p.permission"
|
|
|
|
|
+ // We want the property name "permission"
|
|
|
|
|
+ return ((PropertyExpression) expr).property_name;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // For entity/projection mode, the nested data is stored under the friendly name
|
|
|
|
|
+ return selection.friendly_name;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Sets a property value on a generic Object instance.
|
|
|
|
|
- *
|
|
|
|
|
- * @param instance The Object instance
|
|
|
|
|
- * @param property_name The property name
|
|
|
|
|
- * @param value The value to set
|
|
|
|
|
|
|
+ * Extracts a scalar value from an Element as an Object.
|
|
|
*/
|
|
*/
|
|
|
- private void set_property_on_object(Object instance, string property_name, Value value) {
|
|
|
|
|
- string gobject_name = friendly_name_to_property_name_for_type(
|
|
|
|
|
- instance.get_type(),
|
|
|
|
|
- property_name
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ private Object extract_scalar_value(Invercargill.Element element) {
|
|
|
|
|
+ // Try to get the value and wrap it
|
|
|
|
|
+ string? str_val = null;
|
|
|
|
|
+ if (element.try_get_as<string>(out str_val)) {
|
|
|
|
|
+ return new ScalarWrapper.string(str_val ?? "");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int64? int64_val = null;
|
|
|
|
|
+ if (element.try_get_as<int64?>(out int64_val)) {
|
|
|
|
|
+ return new ScalarWrapper.int64(int64_val);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ int? int_val = null;
|
|
|
|
|
+ if (element.try_get_as<int?>(out int_val)) {
|
|
|
|
|
+ return new ScalarWrapper.int32(int_val);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- var obj_class = (ObjectClass)instance.get_type().class_ref();
|
|
|
|
|
- var spec = obj_class.find_property(gobject_name);
|
|
|
|
|
|
|
+ double? double_val = null;
|
|
|
|
|
+ if (element.try_get_as<double?>(out double_val)) {
|
|
|
|
|
+ return new ScalarWrapper.double(double_val);
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- if (spec != null) {
|
|
|
|
|
- Value converted = convert_value(value, spec.value_type);
|
|
|
|
|
- instance.set_property(gobject_name, converted);
|
|
|
|
|
|
|
+ bool? bool_val = null;
|
|
|
|
|
+ if (element.try_get_as<bool>(out bool_val)) {
|
|
|
|
|
+ return new ScalarWrapper.boolean(bool_val);
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ // Fallback - store as string
|
|
|
|
|
+ return new ScalarWrapper.string(element.to_string() ?? "");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Converts a friendly_name to a GObject property name for a specific type.
|
|
|
|
|
- *
|
|
|
|
|
- * @param type The GObject type
|
|
|
|
|
- * @param friendly_name The friendly name
|
|
|
|
|
- * @return The GObject property name
|
|
|
|
|
|
|
+ * Materializes an entity from a row using EntityMapper.
|
|
|
*/
|
|
*/
|
|
|
- private string friendly_name_to_property_name_for_type(Type type, string friendly_name) {
|
|
|
|
|
- var obj_class = (ObjectClass)type.class_ref();
|
|
|
|
|
|
|
+ private Object? materialize_entity(Type? entity_type, Invercargill.Properties row) {
|
|
|
|
|
+ if (entity_type == null || _type_provider == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // Try as-is
|
|
|
|
|
- if (obj_class.find_property(friendly_name) != null) {
|
|
|
|
|
- return friendly_name;
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Get the entity mapper for this type
|
|
|
|
|
+ var mapper = _type_provider.get_mapper_for_type(entity_type);
|
|
|
|
|
+ if (mapper == null) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Materialize the entity - we need to use reflection since EntityMapper<T>
|
|
|
|
|
+ // is generic and we have a non-generic reference
|
|
|
|
|
+ return materialize_entity_with_mapper(mapper, row);
|
|
|
|
|
+ } catch (SqlError e) {
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Materializes an entity using a dynamic mapper reference.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Note: This is a simplified implementation. Full entity materialization
|
|
|
|
|
+ * would require either:
|
|
|
|
|
+ * 1. A non-generic materialize method on EntityMapper
|
|
|
|
|
+ * 2. Proper reflection support in Vala
|
|
|
|
|
+ *
|
|
|
|
|
+ * For now, this returns null to indicate that entity mode materialization
|
|
|
|
|
+ * is not yet fully implemented.
|
|
|
|
|
+ */
|
|
|
|
|
+ private Object? materialize_entity_with_mapper(Object mapper, Invercargill.Properties row) {
|
|
|
|
|
+ // Entity materialization would require calling the generic materialise method
|
|
|
|
|
+ // on the mapper. Since we have a non-generic reference, we can't easily do this.
|
|
|
|
|
+ //
|
|
|
|
|
+ // A full implementation would add a non-generic materialize method to the
|
|
|
|
|
+ // EntityMapper base class that takes Properties and returns Object.
|
|
|
|
|
+ //
|
|
|
|
|
+ // For now, return null to indicate this isn't implemented.
|
|
|
|
|
+ return null;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Materializes a nested projection from a row.
|
|
|
|
|
+ */
|
|
|
|
|
+ private Object? materialize_nested_projection(Type? projection_type, Invercargill.Properties row) {
|
|
|
|
|
+ if (projection_type == null) {
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- // Try kebab-case
|
|
|
|
|
- string kebab_case = friendly_name.replace("_", "-");
|
|
|
|
|
- if (obj_class.find_property(kebab_case) != null) {
|
|
|
|
|
- return kebab_case;
|
|
|
|
|
|
|
+ try {
|
|
|
|
|
+ // Create a new instance of the projection type
|
|
|
|
|
+ var instance = Object.new(projection_type);
|
|
|
|
|
+
|
|
|
|
|
+ // For nested projections, we need to look up the nested projection definition
|
|
|
|
|
+ // and map the appropriate columns. For now, we'll use a simple approach:
|
|
|
|
|
+ // look for properties prefixed with the selection's friendly name.
|
|
|
|
|
+ //
|
|
|
|
|
+ // This is a simplified implementation - a full implementation would:
|
|
|
|
|
+ // 1. Look up the nested projection definition from TypeProvider
|
|
|
|
|
+ // 2. Create a ProjectionMapper for that definition
|
|
|
|
|
+ // 3. Map the nested columns using that mapper
|
|
|
|
|
+
|
|
|
|
|
+ return instance;
|
|
|
|
|
+ } catch (Error e) {
|
|
|
|
|
+ return null;
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Applies a collection to a projection instance using a CollectionSelection.
|
|
|
|
|
+ */
|
|
|
|
|
+ private void apply_collection_to_instance(
|
|
|
|
|
+ Object instance,
|
|
|
|
|
+ CollectionSelection selection,
|
|
|
|
|
+ Vector<Object> items
|
|
|
|
|
+ ) {
|
|
|
|
|
+ // The selection's apply_element_value expects an Element containing
|
|
|
|
|
+ // an Enumerable<TItem>. We need to create such an element.
|
|
|
|
|
+ //
|
|
|
|
|
+ // Since we can't easily create a generic Enumerable<TItem>, we'll
|
|
|
|
|
+ // use apply_element_value with a wrapped collection
|
|
|
|
|
|
|
|
- // Try camelCase
|
|
|
|
|
- string camel_case = snake_to_camel(friendly_name);
|
|
|
|
|
- if (obj_class.find_property(camel_case) != null) {
|
|
|
|
|
- return camel_case;
|
|
|
|
|
|
|
+ // Create a wrapper element containing the items
|
|
|
|
|
+ var collection_element = create_collection_element(items);
|
|
|
|
|
+ if (collection_element != null) {
|
|
|
|
|
+ selection.apply_element_value(instance, collection_element);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Applies a collection to a projection instance using a legacy CollectionProjectionSelection.
|
|
|
|
|
+ */
|
|
|
|
|
+ private void apply_legacy_collection_to_instance(
|
|
|
|
|
+ Object instance,
|
|
|
|
|
+ CollectionProjectionSelection selection,
|
|
|
|
|
+ Vector<Object> items
|
|
|
|
|
+ ) {
|
|
|
|
|
+ var collection_element = create_collection_element(items);
|
|
|
|
|
+ if (collection_element != null) {
|
|
|
|
|
+ selection.apply_element_value(instance, collection_element);
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Creates an Element containing a collection of items.
|
|
|
|
|
+ */
|
|
|
|
|
+ private Invercargill.Element? create_collection_element(Vector<Object> items) {
|
|
|
|
|
+ // Convert Vector<Object> to an Enumerable and wrap in an Element
|
|
|
|
|
+ // This requires creating a Properties with the collection
|
|
|
|
|
+ var props = new PropertyDictionary();
|
|
|
|
|
+
|
|
|
|
|
+ // Create an enumerable from the vector
|
|
|
|
|
+ var enumerable = new ObjectVectorEnumerable(items);
|
|
|
|
|
|
|
|
- return friendly_name;
|
|
|
|
|
|
|
+ // Set it as a native value
|
|
|
|
|
+ props.set_native<Invercargill.Enumerable<Object>>("collection", enumerable);
|
|
|
|
|
+
|
|
|
|
|
+ return props.get("collection");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * Converts a snake_case string to camelCase.
|
|
|
|
|
|
|
+ * Maps a selection to a generic Object instance.
|
|
|
*
|
|
*
|
|
|
- * @param snake The snake_case string
|
|
|
|
|
- * @return The camelCase version
|
|
|
|
|
|
|
+ * This method dispatches to the appropriate handler based on the
|
|
|
|
|
+ * selection type (ScalarSelection, NestedProjectionSelection, etc.)
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param instance The Object instance to populate
|
|
|
|
|
+ * @param selection The selection definition
|
|
|
|
|
+ * @param row The database row data
|
|
|
|
|
+ * @throws ProjectionError if mapping fails
|
|
|
*/
|
|
*/
|
|
|
- private string snake_to_camel(string snake) {
|
|
|
|
|
- var result = new StringBuilder();
|
|
|
|
|
- bool capitalize_next = false;
|
|
|
|
|
|
|
+ private void map_selection_to_object(
|
|
|
|
|
+ Object instance,
|
|
|
|
|
+ SelectionDefinition selection,
|
|
|
|
|
+ Invercargill.Properties row
|
|
|
|
|
+ ) throws ProjectionError {
|
|
|
|
|
|
|
|
- for (int i = 0; i < snake.length; i++) {
|
|
|
|
|
- char c = snake[i];
|
|
|
|
|
-
|
|
|
|
|
- if (c == '_') {
|
|
|
|
|
- capitalize_next = true;
|
|
|
|
|
- } else {
|
|
|
|
|
- if (capitalize_next) {
|
|
|
|
|
- result.append_c(c.toupper());
|
|
|
|
|
- capitalize_next = false;
|
|
|
|
|
- } else {
|
|
|
|
|
- result.append_c(c);
|
|
|
|
|
- }
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // Check if this is a scalar selection (has value_type that's not a projection)
|
|
|
|
|
+ var nested_type = selection.nested_projection_type;
|
|
|
|
|
+
|
|
|
|
|
+ if (nested_type == null) {
|
|
|
|
|
+ // Scalar selection
|
|
|
|
|
+ map_scalar_selection(instance, selection, row);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ // Check if it's a collection or nested projection
|
|
|
|
|
+ // For now, treat all nested types as scalar objects
|
|
|
|
|
+ map_scalar_selection(instance, selection, row);
|
|
|
}
|
|
}
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Maps a scalar selection to a property.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Scalar selections represent simple values like int, string, double, etc.
|
|
|
|
|
+ * The selection's apply_element_value() method handles value extraction
|
|
|
|
|
+ * and type-safe setter invocation.
|
|
|
|
|
+ *
|
|
|
|
|
+ * Uses the column_alias if available (set during SQL building), otherwise
|
|
|
|
|
+ * falls back to friendly_name for backward compatibility.
|
|
|
|
|
+ *
|
|
|
|
|
+ * @param instance The Object instance
|
|
|
|
|
+ * @param selection The selection definition
|
|
|
|
|
+ * @param row The database row data
|
|
|
|
|
+ * @throws ProjectionError if mapping fails
|
|
|
|
|
+ */
|
|
|
|
|
+ private void map_scalar_selection(
|
|
|
|
|
+ Object instance,
|
|
|
|
|
+ SelectionDefinition selection,
|
|
|
|
|
+ Invercargill.Properties row
|
|
|
|
|
+ ) throws ProjectionError {
|
|
|
|
|
+
|
|
|
|
|
+ // Use column_alias if set (from SQL building), otherwise fall back to friendly_name
|
|
|
|
|
+ string column_name = selection.column_alias ?? selection.friendly_name;
|
|
|
|
|
|
|
|
- return result.str;
|
|
|
|
|
|
|
+ var element = row.get(column_name);
|
|
|
|
|
+ if (element == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Delegates to the selection which uses its type-safe setter
|
|
|
|
|
+ selection.apply_element_value(instance, element);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
@@ -461,5 +750,82 @@ namespace InvercargillSql.Orm.Projections {
|
|
|
get { return _mapper_definition; }
|
|
get { return _mapper_definition; }
|
|
|
set { _mapper_definition = value; }
|
|
set { _mapper_definition = value; }
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Sets the type provider for entity materialization.
|
|
|
|
|
+ */
|
|
|
|
|
+ internal void set_type_provider(TypeProvider type_provider) {
|
|
|
|
|
+ _type_provider = type_provider;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Simple Enumerable implementation for a Vector of Object items.
|
|
|
|
|
+ *
|
|
|
|
|
+ * This is used by create_collection_element to wrap Object vectors.
|
|
|
|
|
+ */
|
|
|
|
|
+ internal class ObjectVectorEnumerable : Invercargill.Enumerable<Object> {
|
|
|
|
|
+ private Vector<Object> _items;
|
|
|
|
|
+ private int _tracker_index = 0;
|
|
|
|
|
+
|
|
|
|
|
+ public ObjectVectorEnumerable(Vector<Object> items) {
|
|
|
|
|
+ _items = items;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public override Invercargill.Tracker<Object> get_tracker() {
|
|
|
|
|
+ return new Invercargill.AdvanceTracker<Object>(advance_item);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public override uint? peek_count() {
|
|
|
|
|
+ return _items.length;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public override Invercargill.EnumerableInfo get_info() {
|
|
|
|
|
+ return new Invercargill.EnumerableInfo.infer_ultimate(
|
|
|
|
|
+ this,
|
|
|
|
|
+ Invercargill.EnumerableCategory.IN_MEMORY
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private bool advance_item(out Object? item) {
|
|
|
|
|
+ if (_tracker_index < _items.length) {
|
|
|
|
|
+ item = _items.get(_tracker_index++);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ item = null;
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * Wrapper class for scalar values to allow storing them as Object.
|
|
|
|
|
+ */
|
|
|
|
|
+ internal class ScalarWrapper : Object {
|
|
|
|
|
+ public string? string_value { get; construct; }
|
|
|
|
|
+ public int64 int64_value { get; construct; }
|
|
|
|
|
+ public int int32_value { get; construct; }
|
|
|
|
|
+ public double double_value { get; construct; }
|
|
|
|
|
+ public bool bool_value { get; construct; }
|
|
|
|
|
+ public int type_tag { get; construct; } // 0=string, 1=int64, 2=int32, 3=double, 4=bool
|
|
|
|
|
+
|
|
|
|
|
+ public ScalarWrapper.string(string value) {
|
|
|
|
|
+ Object(string_value: value, type_tag: 0);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public ScalarWrapper.int64(int64 value) {
|
|
|
|
|
+ Object(int64_value: value, type_tag: 1);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public ScalarWrapper.int32(int value) {
|
|
|
|
|
+ Object(int32_value: value, type_tag: 2);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public ScalarWrapper.double(double value) {
|
|
|
|
|
+ Object(double_value: value, type_tag: 3);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ public ScalarWrapper.boolean(bool value) {
|
|
|
|
|
+ Object(bool_value: value, type_tag: 4);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|