| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- using Invercargill.DataStructures;
- using Invercargill.Expressions;
- namespace InvercargillSql.Orm.Projections {
-
- /**
- * Result of resolving a nested property path.
- *
- * When navigating into nested projections (e.g., "profile.id"), this class
- * holds information about the nested selection and the resolved path.
- *
- * Example:
- * {{{
- * // For a projection with:
- * // .select<ProfileSummary>("profile", expr("pr"), ...)
- *
- * var resolution = resolver.resolve_nested_property("profile.id");
- * // resolution.selection refers to the ProfileSummary selection
- * // resolution.remaining_path == "id"
- * // resolution.resolved_expression is an Expression representing "pr.id"
- * }}}
- */
- public class NestedResolution : Object {
- /**
- * The selection definition for the nested projection.
- *
- * This is the selection that corresponds to the first segment
- * of the property path (e.g., "profile" in "profile.id").
- */
- public SelectionDefinition selection { get; construct; }
-
- /**
- * The type of the nested projection.
- *
- * For NestedProjectionSelection, this is the TNested type.
- * For CollectionProjectionSelection, this is the TItem type.
- */
- public Type nested_projection_type { get; construct; }
-
- /**
- * The remaining path after the first segment.
- *
- * For "profile.id", this would be "id".
- * For "profile.address.city", this would be "address.city".
- */
- public string remaining_path { get; construct; }
-
- /**
- * The first segment of the path (the friendly name).
- *
- * For "profile.id", this would be "profile".
- */
- public string first_segment { get; construct; }
-
- /**
- * The final resolved expression, if available.
- *
- * This may be null if the resolution only identified the nested
- * selection but did not fully resolve the property within it.
- *
- * This is an Expression object that can be used directly in
- * expression trees or converted to string using ExpressionStringVisitor.
- */
- public Expression? resolved_expression { get; construct; }
-
- /**
- * Indicates whether this is a nested projection (1:1).
- */
- public bool is_single_nested { get; construct; }
-
- /**
- * Indicates whether this is a collection projection (1:N).
- */
- public bool is_collection { get; construct; }
-
- /**
- * Creates a new NestedResolution.
- *
- * @param selection The nested selection definition
- * @param nested_projection_type The type of the nested projection
- * @param remaining_path The path after the first segment
- * @param first_segment The first segment (friendly name)
- * @param resolved_expression The fully resolved Expression (optional)
- * @param is_single_nested True if this is a 1:1 nested projection
- * @param is_collection True if this is a 1:N collection projection
- */
- public NestedResolution(
- SelectionDefinition selection,
- Type nested_projection_type,
- string remaining_path,
- string first_segment,
- Expression? resolved_expression,
- bool is_single_nested,
- bool is_collection
- ) {
- Object(
- selection: selection,
- nested_projection_type: nested_projection_type,
- remaining_path: remaining_path,
- first_segment: first_segment,
- resolved_expression: resolved_expression,
- is_single_nested: is_single_nested,
- is_collection: is_collection
- );
- }
- }
-
- /**
- * Resolves friendly names to Expression objects and handles nested navigation.
- *
- * FriendlyNameResolver provides lookup services for the friendly names
- * defined in a projection. These names are used in WHERE and ORDER BY
- * expressions to reference selection values.
- *
- * The resolver also handles nested navigation, allowing expressions like
- * "profile.id" to reference properties within nested projections.
- *
- * Example:
- * {{{
- * // For a projection with:
- * // .select<int64>("user_id", expr("r.id"), ...)
- * // .select<ProfileSummary>("profile", expr("pr"), ...)
- *
- * var resolver = new FriendlyNameResolver(definition);
- *
- * // Simple resolution - returns Expression object
- * Expression? expr = resolver.resolve_to_expression("user_id");
- *
- * // Nested navigation
- * var nested = resolver.resolve_nested_property("profile.id");
- * // nested.first_segment == "profile"
- * // nested.remaining_path == "id"
- *
- * // Check if name exists
- * resolver.is_friendly_name("user_id") // true
- * }}}
- */
- public class FriendlyNameResolver : Object {
-
- /**
- * The projection definition this resolver is associated with.
- */
- private ProjectionDefinition _definition;
-
- /**
- * Cache of friendly name to Expression mappings.
- * Built lazily on first access.
- */
- private Dictionary<string, Expression>? _friendly_name_cache;
-
- /**
- * Cache of friendly name to selection mappings.
- * Built lazily on first access.
- */
- private Dictionary<string, SelectionDefinition>? _selection_cache;
-
- /**
- * Cache of all friendly names including nested ones.
- * Built lazily on first access.
- */
- private Vector<string>? _all_names_cache;
-
- /**
- * Creates a new FriendlyNameResolver for the given projection.
- *
- * @param definition The projection definition to resolve names for
- */
- public FriendlyNameResolver(ProjectionDefinition definition) {
- _definition = definition;
- }
-
- /**
- * Resolves a friendly name to its underlying Expression.
- *
- * For scalar selections, this returns the Expression defined in
- * the .select() call. For nested projections, this returns the
- * entry point expression.
- *
- * @param friendly_name The friendly name to resolve
- * @return The underlying Expression, or null if not found
- */
- public Expression? resolve_to_expression(string friendly_name) {
- ensure_cache_built();
-
- // First check if it's a direct friendly name
- // Use has() before get() to avoid exception on missing key
- if (_friendly_name_cache.has(friendly_name)) {
- return _friendly_name_cache.get(friendly_name);
- }
-
- // Check if it's a nested path (e.g., "profile.id")
- int dot_index = friendly_name.index_of(".");
- if (dot_index > 0) {
- // Find the nested selection
- var nested = resolve_nested_property(friendly_name);
- if (nested != null) {
- return nested.resolved_expression;
- }
- }
-
- return null;
- }
-
- /**
- * Resolves a nested property path.
- *
- * This method handles paths like "profile.id" or "address.city.name"
- * by finding the nested selection and determining the remaining path.
- *
- * @param property_path The full property path (e.g., "profile.id")
- * @return A NestedResolution with details, or null if not found
- */
- public NestedResolution? resolve_nested_property(string property_path) {
- ensure_cache_built();
-
- int dot_index = property_path.index_of(".");
- if (dot_index < 0) {
- // Not a nested path
- return null;
- }
-
- string first_segment = property_path.substring(0, dot_index);
- string remaining_path = property_path.substring(dot_index + 1);
-
- // Find the selection for the first segment
- SelectionDefinition? selection = _selection_cache[first_segment];
- if (selection == null) {
- return null;
- }
-
- // Check if it's a nested or collection projection
- Type? nested_type = selection.nested_projection_type;
- if (nested_type == null) {
- // Not a nested projection - can't navigate further
- return null;
- }
-
- // Determine if it's a single nested or collection by checking type name
- string type_name = selection.get_type().name();
- bool is_single = type_name.contains("NestedProjectionSelection");
- bool is_collection = type_name.contains("CollectionProjectionSelection");
-
- // Get the entry point expression for the nested selection
- Expression? entry_point = get_entry_point_from_selection(selection);
-
- // Build the resolved expression by combining entry point with remaining path
- Expression? resolved_expr = null;
- if (entry_point != null && remaining_path.length > 0) {
- // Create a property chain expression: entry_point.remaining_path
- resolved_expr = create_property_chain(entry_point, remaining_path);
- } else if (entry_point != null) {
- resolved_expr = entry_point;
- }
-
- return new NestedResolution(
- selection,
- nested_type,
- remaining_path,
- first_segment,
- resolved_expr,
- is_single,
- is_collection
- );
- }
-
- /**
- * Checks if a name is a known friendly name.
- *
- * This checks only top-level friendly names, not nested paths.
- *
- * @param name The name to check
- * @return True if the name is a defined friendly name
- */
- public bool is_friendly_name(string name) {
- ensure_cache_built();
-
- // Check for exact match first
- if (_selection_cache.has(name)) {
- return true;
- }
-
- // Check if it's a nested path with a valid first segment
- int dot_index = name.index_of(".");
- if (dot_index > 0) {
- string first_segment = name.substring(0, dot_index);
- return _selection_cache.has(first_segment);
- }
-
- return false;
- }
-
- /**
- * Gets all friendly names defined in this projection.
- *
- * This includes both scalar and nested projection selections,
- * but does not include properties within nested projections.
- *
- * @return A vector of all friendly names
- */
- public Vector<string> get_all_friendly_names() {
- ensure_cache_built();
-
- var names = new Vector<string>();
- foreach (var selection in _definition.selections) {
- names.add(selection.friendly_name);
- }
- return names;
- }
-
- /**
- * Gets all friendly names including nested property paths.
- *
- * This method recursively explores nested projections to build
- * a complete list of navigable property paths.
- *
- * Note: This requires the nested projections to be registered
- * in the session to fully expand their properties.
- *
- * @param session Optional session for looking up nested projection definitions
- * @return A vector of all friendly names including nested paths
- */
- public Vector<string> get_all_friendly_names_recursive(
- InvercargillSql.Orm.OrmSession? session = null
- ) {
- ensure_cache_built();
-
- var all_names = new Vector<string>();
-
- foreach (var selection in _definition.selections) {
- all_names.add(selection.friendly_name);
-
- // If this is a nested projection, explore its properties
- if (selection.nested_projection_type != null && session != null) {
- add_nested_names(selection, selection.friendly_name, session, all_names, 0);
- }
- }
-
- return all_names;
- }
-
- /**
- * Gets the selection definition for a friendly name.
- *
- * @param friendly_name The friendly name to look up
- * @return The selection definition, or null if not found
- */
- public SelectionDefinition? get_selection(string friendly_name) {
- ensure_cache_built();
-
- // Handle nested paths
- int dot_index = friendly_name.index_of(".");
- if (dot_index > 0) {
- string first_segment = friendly_name.substring(0, dot_index);
- if (_selection_cache.has(first_segment)) {
- return _selection_cache[first_segment];
- }
- return null;
- }
-
- if (_selection_cache.has(friendly_name)) {
- return _selection_cache[friendly_name];
- }
- return null;
- }
-
- /**
- * Checks if a friendly name refers to a nested projection.
- *
- * @param friendly_name The friendly name to check
- * @return True if it's a nested projection selection
- */
- public bool is_nested_projection(string friendly_name) {
- var selection = get_selection(friendly_name);
- if (selection == null) {
- return false;
- }
- return selection.nested_projection_type != null;
- }
-
- /**
- * Checks if a friendly name refers to a collection projection.
- *
- * @param friendly_name The friendly name to check
- * @return True if it's a collection projection selection
- */
- public bool is_collection_projection(string friendly_name) {
- var selection = get_selection(friendly_name);
- if (selection == null) {
- return false;
- }
- string type_name = selection.get_type().name();
- return type_name.contains("CollectionProjectionSelection");
- }
-
- /**
- * Gets the type of value for a friendly name.
- *
- * @param friendly_name The friendly name to look up
- * @return The value type, or null if not found
- */
- public Type? get_value_type(string friendly_name) {
- var selection = get_selection(friendly_name);
- if (selection == null) {
- return null;
- }
- return selection.value_type;
- }
-
- /**
- * Ensures the internal caches are built.
- */
- private void ensure_cache_built() {
- if (_friendly_name_cache != null && _selection_cache != null) {
- return;
- }
-
- _friendly_name_cache = new Dictionary<string, Expression>();
- _selection_cache = new Dictionary<string, SelectionDefinition>();
-
- foreach (var selection in _definition.selections) {
- _selection_cache[selection.friendly_name] = selection;
-
- // Get the expression for the selection
- Expression? expr = get_expression_from_selection(selection);
- if (expr != null) {
- _friendly_name_cache[selection.friendly_name] = expr;
- }
- }
- }
-
- /**
- * Gets the Expression from a selection using reflection.
- *
- * Uses GObject's property system to access the expression property
- * without knowing the generic type parameters.
- *
- * @param selection The selection to get the expression from
- * @return The Expression object, or null if not applicable
- */
- private Expression? get_expression_from_selection(SelectionDefinition selection) {
- // Try to get "expression" property (ScalarSelection)
- var param = selection.get_class().find_property("expression");
- if (param != null) {
- Value val = Value(typeof(Object));
- selection.get_property("expression", ref val);
- return val.get_object() as Expression;
- }
-
- // Try to get "entry-point-expression" property (nested/collection)
- param = selection.get_class().find_property("entry-point-expression");
- if (param != null) {
- Value val = Value(typeof(Object));
- selection.get_property("entry-point-expression", ref val);
- return val.get_object() as Expression;
- }
-
- return null;
- }
-
- /**
- * Gets the entry point Expression from a nested/collection selection.
- *
- * @param selection The selection to get the entry point from
- * @return The entry point Expression, or null if not applicable
- */
- private Expression? get_entry_point_from_selection(SelectionDefinition selection) {
- var param = selection.get_class().find_property("entry-point-expression");
- if (param != null) {
- Value val = Value(typeof(Object));
- selection.get_property("entry-point-expression", ref val);
- return val.get_object() as Expression;
- }
- return null;
- }
-
- /**
- * Creates a property chain expression from a base expression and path.
- *
- * Given a base expression (e.g., VariableExpression("pr")) and a path
- * (e.g., "id"), creates a chain of PropertyExpressions.
- *
- * For "pr.id", creates: PropertyExpression(VariableExpression("pr"), "id")
- * For "pr.address.city", creates nested PropertyExpressions.
- *
- * @param base_expr The base expression to build upon
- * @param path The property path (dot-separated)
- * @return A new Expression representing the property chain
- */
- private Expression create_property_chain(Expression base_expr, string path) {
- // Split the path by dots and build property chain
- string[] parts = path.split(".");
- Expression current = base_expr;
-
- foreach (string part in parts) {
- if (part.length > 0) {
- current = new PropertyExpression(current, part);
- }
- }
-
- return current;
- }
-
- /**
- * Recursively adds nested friendly names.
- */
- private void add_nested_names(
- SelectionDefinition selection,
- string prefix,
- InvercargillSql.Orm.OrmSession session,
- Vector<string> names,
- int depth
- ) {
- // Prevent infinite recursion
- if (depth > 10) {
- return;
- }
-
- // We can't easily look up nested projection definitions without
- // the actual generic types, so we just add the prefix itself
- // In a full implementation, we would look up the nested projection
- // definition and add its selections with the prefix
- }
- }
- }
|