diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..61277bf
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,54 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the
+// README at: https://github.com/devcontainers/templates/tree/main/src/universal
+{
+ "name": "Default Linux Universal",
+ // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
+ "image": "mcr.microsoft.com/devcontainers/universal:5-linux",
+ "features": {
+ "ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
+ "ghcr.io/devcontainers/features/python:1": {},
+ "ghcr.io/devcontainers/features/dotnet:2": {},
+ "ghcr.io/stu-bell/devcontainer-features/gemini-cli:0": {},
+ "ghcr.io/devcontainers-extra/features/shellcheck:1": {},
+ "ghcr.io/devcontainers-extra/features/uv:1": {},
+ "ghcr.io/devcontainers-extra/features/direnv:1": {},
+ "ghcr.io/devcontainers/features/node:1": {
+ "version": "lts"
+ }
+ },
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "ms-dotnettools.csdevkit",
+ "ms-azuretools.vscode-containers",
+ "Upstash.context7-mcp",
+ "mads-hartmann.bash-ide-vscode",
+ "fill-labs.dependi",
+ "docker.docker",
+ "tamasfe.even-better-toml",
+ "vscjava.vscode-java-pack",
+ "PKief.material-icon-theme",
+ "Postman.postman-for-vscode",
+ "charliermarsh.ruff",
+ "mtxr.sqltools",
+ "redhat.vscode-yaml",
+ "astral-sh.ty"
+ ]
+ }
+ }
+
+ // Features to add to the dev container. More info: https://containers.dev/features.
+ // "features": {},
+
+ // Use 'forwardPorts' to make a list of ports inside the container available locally.
+ // "forwardPorts": [],
+
+ // Use 'postCreateCommand' to run commands after the container is created.
+ // "postCreateCommand": "uname -a",
+
+ // Configure tool-specific properties.
+ // "customizations": {},
+
+ // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
+ // "remoteUser": "root"
+}
diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..307141a
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,20 @@
+XRG13_SSH_USERNAME=
+XRG13_SSH_PRIVATE_KEY_PATH=~/.ssh/id_rsa # change to valid path
+
+OLLAMA_PORT=11434
+DOTNET_SERVICE_PORT=5083
+JAVA_SERVICE_PORT=8090
+MONGODB_MIGRATOR_PORT=8091
+DB_TOOLBOX_PORT=5010
+
+MSSQL_PORT=1444
+MSSQL_USER=sa
+MSSQL_PASSWORD=Testingorms123
+MONGODB_PORT=27027
+MONGODB_USER=uom_readonly
+MONGODB_PASSWORD=uom_readonly
+MONGODB_DATABASE=uom
+NEO4J_PORT=7697
+NEO4J_BROWSER_PORT=7484
+NEO4J_USERNAME=neo4j
+NEO4J_PASSWORD=password
\ No newline at end of file
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..8c29662
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,5 @@
+* text=auto
+
+*.{cmd,[cC][mM][dD]} text eol=crlf
+*.{bat,[bB][aA][tT]} text eol=crlf
+*.sh text eol=lf
diff --git a/.gitignore b/.gitignore
index d3bd805..4fece80 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,7 @@
*.bkp
-.gemini/
gha-creds-*.json
+.env
+.env.dev
+/.idea/
+*.dump
+.serena/
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..d111564
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,70 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "type": "java",
+ "name": "Neo4jSchemaValidationEntrypoint",
+ "request": "launch",
+ "mainClass": "uom.services.Neo4jSchemaValidationEntrypoint",
+ "projectName": "neo4j_sandbox",
+ "envFile": "${workspaceFolder}/services/java-services/.env.dev"
+ },
+ {
+ "type": "java",
+ "name": "MongoSchemaValidationEntrypoint",
+ "request": "launch",
+ "mainClass": "uom.services.MongoSchemaValidationEntrypoint",
+ "projectName": "mongo_sandbox",
+ "envFile": "${workspaceFolder}/services/java-services/.env.dev"
+ },
+ {
+ "type": "java",
+ "name": "MongoQueryEntrypoint",
+ "request": "launch",
+ "mainClass": "uom.services.MongoQueryEntrypoint",
+ "projectName": "mongo_sandbox",
+ "envFile": "${workspaceFolder}/services/java-services/.env.dev"
+ },
+ {
+ "type": "java",
+ "name": "Neo4jQueryEntrypoint",
+ "request": "launch",
+ "mainClass": "uom.services.Neo4jQueryEntrypoint",
+ "projectName": "neo4j_sandbox",
+ "envFile": "${workspaceFolder}/services/java-services/.env.dev"
+ },
+ {
+ "name": "[orchestrator] Python Debugger: Remote Attach",
+ "type": "debugpy",
+ "request": "attach",
+ "connect": {
+ "host": "localhost",
+ "port": 5678
+ },
+ "justMyCode": false,
+ "pathMappings": [
+ {
+ "localRoot": "${workspaceFolder}/services/orchestrator",
+ "remoteRoot": "."
+ }
+ ]
+ },
+ {
+ "name": "C#: Debug Active File",
+ "type": "dotnet",
+ "request": "launch",
+ "projectPath": "${file}",
+ "linux": {
+ "args": [
+ "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True"
+ ],
+ "env": {
+ "CONNECTION_STRING": "Server=localhost,1444;Database=WideWorldImporters;User Id=sa;Password=Testingorms123;TrustServerCertificate=True",
+ "EFCORE_RESULTS_PATH": "/home/corovcak/Uni/DP/Universal-Object-Mapping/services/dotnet-service/efcore-sandbox/results",
+ "NHIBERNATE_RESULTS_PATH": "/home/corovcak/Uni/DP/Universal-Object-Mapping/services/dotnet-service/nhibernate-sandbox/results",
+ "DAPPER_RESULTS_PATH": "/home/corovcak/Uni/DP/Universal-Object-Mapping/services/dotnet-service/dapper-sandbox/results"
+ }
+ }
+ }
+ ]
+}
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..7e71970
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,65 @@
+{
+ "java.compile.nullAnalysis.mode": "automatic",
+ "java.configuration.updateBuildConfiguration": "automatic",
+ "python-envs.pythonProjects": [
+ {
+ "path": "./services/orchestrator",
+ "envManager": "ms-python.python:venv",
+ "packageManager": "ms-python.python:pip"
+ }
+ ],
+ "python.analysis.useNearestConfiguration": true,
+ "python.analysis.include": ["./services/orchestrator/[src,tests]/**"],
+ "python.testing.unittestEnabled": false,
+ "python.testing.pytestEnabled": true,
+ "pythonTestExplorer.testFramework": "pytest",
+ "python.envFile": "${workspaceFolder}/services/orchestrator/.env.dev",
+ "python.testing.cwd": "${workspaceFolder}/services/orchestrator",
+ "python.testing.autoTestDiscoverOnSaveEnabled": true,
+ "python.testing.autoTestDiscoverOnSavePattern": "${workspaceFolder}/services/orchestrator/tests/**/*.py",
+ "python.terminal.useEnvFile": true,
+ "python.testing.pytestPath": "${workspaceFolder}/services/orchestrator/.venv/bin/pytest",
+ "python.testing.pytestArgs": [
+ "-v",
+ "${workspaceFolder}/services/orchestrator/tests"
+ ],
+ "python.analysis.inlayHints.pytestParameters": true,
+ // "python.languageServer": "Pylance",
+ // if using Pylance in VSCode
+ "ty.disableLanguageServices": false,
+ "sqltools.connections": [
+ {
+ "ssh": "Disabled",
+ "previewLimit": 50,
+ "server": "localhost",
+ "port": 5433,
+ "driver": "PostgreSQL",
+ "name": "langgraph-postgres",
+ "database": "postgres",
+ "username": "postgres",
+ "password": "postgres"
+ },
+ {
+ "connectionTimeout": 15,
+ "mssqlOptions": {
+ "appName": "SQLTools",
+ "useUTC": true,
+ "encrypt": true,
+ "trustServerCertificate": true
+ },
+ "ssh": "Disabled",
+ "previewLimit": 50,
+ "server": "localhost",
+ "port": 1444,
+ "driver": "MSSQL",
+ "name": "uom-mssql",
+ "username": "sa",
+ "password": "Testingorms123"
+ }
+ ],
+ "editor.codeActionsOnSave": {
+ "source.organizeImports": "explicit"
+ },
+ "maven.view": "flat",
+ "java.sources.organizeImports.staticStarThreshold": 1
+}
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..bff5000
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,59 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/services/dotnet-service/dotnet-service.sln",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary;ForceNoAlign"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/services/dotnet-service/dotnet-service.sln",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary;ForceNoAlign"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "--project",
+ "${workspaceFolder}/services/dotnet-service/dotnet-service.sln"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "fix /run/user/1000",
+ "type": "shell",
+ "command": "sudo mkdir /run/user/1000 && sudo chown -R $USER:$USER /run/user/1000",
+ "problemMatcher": []
+ },
+ {
+ "label": "Setup Windows proxy DNS for Daytona",
+ "type": "shell",
+ "command": "sudo.exe powershell -ExecutionPolicy Bypass -File ./services/orchestrator/scripts/daytona-setup-proxy-dns.ps1",
+ "problemMatcher": []
+ },
+ {
+ "label": "Reset Windows proxy DNS for Daytona",
+ "type": "shell",
+ "command": "sudo.exe powershell -ExecutionPolicy Bypass -File ./services/orchestrator/scripts/daytona-reset-proxy-dns.ps1",
+ "problemMatcher": []
+ },
+ ]
+}
\ No newline at end of file
diff --git a/ORMConvertor/README.md b/ORMConvertor/README.md
index b97c17e..779e4a6 100644
--- a/ORMConvertor/README.md
+++ b/ORMConvertor/README.md
@@ -1,27 +1,36 @@
+# ORMConvertor
+
A web-based tool for translating accross different .NET ORMs.
# Deployment
+
The application can be run in two main ways. The more user-friendly approach is to open the solution file `ORMConvertor.sln` in Visual Studio. `ORMConvertorAPI` has to be set as the startup project, if not selected by default. The app can be launched in `Debug` configuration for development or compiled in `Release` configuration for production. Only the `http` launch profile defined in `ORMConvertorAPI/Properties/launchSettings.json` file has been configured and tested. The application can be started with `CTRL + F5` to run without debugging or `F5` to launch with the debugger. A browser window should open automatically.
Alternatively, the application can be started using .NET CLI using the following commands:
+
```sh
dotnet run --configuration Release --launch-profile http --project ORMConvertorAPI/ORMConvertorAPI.csproj
```
+
This approach does not open a browser automatically. Instead, the local URL is printed to the console (typically [http://localhost:5072/orm/](http://localhost:5072/orm/)).
Tests can be executed via the Visual Studio's Test Explorer window or with a .NET CLI command:
+
```sh
dotnet test Tests/Tests.csproj --configuration Release
```
+
The output displays the results of succeeded and failed tests. Tests are also run automatically on each commit using a GitHub Actions pipeline. The pipeline configuration is located in the `.github` folder at the root of the repository.
The Angular frontend is precompiled and served by the ASP.NET web server running the API. To prepare the frontend, its source files must be compiled and copied to the `wwwroot` directory, from which they are served as static files. This process is performed by executing the following commands in the `ORMConvertorAPI/frontend` directory.
+
```sh
npm install
ng build --configuration "production" --base-href "/orm/" --deploy-url "/orm/" && rmdir /s /q "..\wwwroot" && mkdir "..\wwwroot" && xcopy /s /e /y "dist\browser\*" "..\wwwroot\"
```
For Linux:
+
```sh
npm install
ng build --configuration "production" --base-href "/orm/" --deploy-url "/orm/" && rm -rf "../wwwroot" && mkdir "../wwwroot" && cp -r dist/browser/* ../wwwroot/
diff --git a/README.md b/README.md
index dbda17f..e893c20 100644
--- a/README.md
+++ b/README.md
@@ -1,31 +1,55 @@
-# Universal Object Mapping
+# Universal Object Mapping (UOM)
[](https://github.com/corovcam/Universal-Object-Mapping/actions/workflows/ormconvertor-tests.yml)
-## Repository structure
+Universal Object Mapping (UOM) is a research project focused on automating the translation and optimization of database schemas and queries across diverse Object-Relational/Document/Graph Mapping (ORM/ODM/OGM) frameworks.
-The repository is structured into several directories. The `diagrams` directory holds diagrams created using [draw.io](https://www.drawio.com/). Experimental comparisons, including unit tests and benchmarks, are located in the `benchmarks` directory. The `ORMConvertor` folder includes a prototype tool for translating between different .NET ORM frameworks.
+The project leverages an **LLM-driven iterative translation architecture** to bridge the gap between relational, document, and graph databases. It extends the earlier work of ORMorpher, replacing its heuristic rule engine with an LLM-driven pipeline.
-## ORMConvertor
+## Key Features
-The translation and advisor tool are currently hosted at [http://116.203.208.55/orm/home](http://116.203.208.55/orm/home).
+- **Automated Translation**: Migrate .NET (EF Core, Dapper, NHibernate) entity mappings and LINQ queries to Java Spring Data (MongoDB, Neo4j).
+- **LLM Orchestration**: Uses a ReAct agent powered by LangGraph for autonomous code generation, validation, and correction with tool calls.
+- **Isolated Benchmarking**: Measures ORM overhead (mapping/translation) independently of database latency via a specialized mocking layer.
+- **Intelligent Advisor**: Suggests optimal ORM/ODM configurations using Integer Linear Programming (ILP) based on benchmark data.
+- **Multi-Database Support**: MS SQL Server (relational), MongoDB (document), and Neo4j (graph) integration.
+- **ETL Pipelines**: Pre-configured ETL processes to map and load data from relational schemas to NoSQL targets.
-## Frameworks and ORM/OGM/ODMs compared
+## Architecture
-The compared ORMs are:
+The system is built as a set of modular microservices and tools coordinated by a Python-based orchestrator, managed via Docker Compose.
-- [Dapper](https://github.com/DapperLib/Dapper)
-- [NHibernate](https://github.com/nhibernate)
-- [Entity Framework Core](https://github.com/dotnet/efcore)
+- **Orchestrator (`services/orchestrator`)**: Python/LangGraph service driving the translation workflow using a "Council of Models" and a ReAct agent.
+- **Service Layer**:
+ - **[.NET Service](services/dotnet-service)**: Compilation and validation for C# (EF Core, NHibernate, Dapper).
+ - **[Java Service](services/java-services)**: Compilation and validation for Java (Spring Data MongoDB, Spring Data Neo4j).
+- **[ORMConvertor](ORMConvertor/)**: The original .NET-based tool providing the web UI, heuristic reference logic, and the ILP-based Advisor.
+- **[Benchmarks](benchmarks/)**: A comprehensive suite for measuring ORM performance.
-The compared OGMs are:
+Detailed architectural documentation can be found in [**notes/architecture.md**](notes/architecture.md).
-- [Spring Data Neo4j](https://github.com/spring-projects/spring-data-neo4j)
+## Getting Started
-The compared ODMs are:
+### Prerequisites
-- [Spring Data MongoDB](https://github.com/spring-projects/spring-data-mongodb)
+- [Docker Desktop](https://www.docker.com/products/docker-desktop/) or Docker Engine with Compose
+- [.NET 8 SDK](https://dotnet.microsoft.com/download/dotnet/8.0) (or 10.0)
+- [Python 3.11+](https://www.python.org/) (managed with [uv](https://astral.sh/uv/))
+- [OpenJDK 25+](https://jdk.java.net/25/) and [Maven](https://maven.apache.org/download.cgi) (for Java services)
+- [Node.js 24](https://nodejs.org/en/download/) (for Frontend)
+
+### Development Setup
-## Acknowledgements
+1. **Spin up the stack**:
+ ```bash
+ docker compose up -d --build
+ ```
+2. **Configure Environment**:
+ Copy `.env.example` to `.env` and configure your LLM provider keys (Ollama or OpenAI-compatible like E-Infra vLLM).
+
+3. **Explore Documentation**:
+ - [Project Specification (LaTeX)](notes/project-specification/specification-llm-advisor.tex)
+ - [C4 Architecture Diagrams](notes/architecture)
+## Acknowledgements
Part of the `ORMConvertor` and the `benchmarks` source code, including some workflows and diagrams, were developed by Milan Abrahám as part of his Master thesis titled _Framework-Agnostic Query Adaptation: Ensuring SQL Compatibility Across .NET Database Frameworks_. The thesis is available at http://hdl.handle.net/20.500.11956/203083, and the source code is available at https://github.com/milan252525/orm-convertor.
\ No newline at end of file
diff --git a/benchmarks/Common/Common.csproj b/benchmarks/Common/Common.csproj
index b855a78..6bded8b 100644
--- a/benchmarks/Common/Common.csproj
+++ b/benchmarks/Common/Common.csproj
@@ -18,8 +18,8 @@
-
-
+
+
diff --git a/benchmarks/Common/Mock/AmbientScopeBase.cs b/benchmarks/Common/Mock/AmbientScopeBase.cs
new file mode 100644
index 0000000..3930aaa
--- /dev/null
+++ b/benchmarks/Common/Mock/AmbientScopeBase.cs
@@ -0,0 +1,33 @@
+using System.Threading;
+
+namespace Common.Mock;
+
+public abstract class AmbientScopeBase : IDisposable where T : AmbientScopeBase
+{
+ private static readonly AsyncLocal _current = new();
+
+ private readonly T? _previousScope;
+ private bool _disposed;
+
+ public static T? Current => _current.Value;
+
+ protected AmbientScopeBase()
+ {
+ _previousScope = _current.Value;
+ }
+
+ protected static T StartNew(T instance)
+ {
+ _current.Value = instance;
+ return instance;
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+ _current.Value = _previousScope;
+ }
+ }
+}
diff --git a/benchmarks/Common/Mock/BenchmarkCommandExecutor.cs b/benchmarks/Common/Mock/BenchmarkCommandExecutor.cs
new file mode 100644
index 0000000..ddedbd2
--- /dev/null
+++ b/benchmarks/Common/Mock/BenchmarkCommandExecutor.cs
@@ -0,0 +1,91 @@
+using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
+using Microsoft.EntityFrameworkCore.Storage;
+using System;
+using System.Collections.Generic;
+using System.Data;
+using System.Data.Common;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace Common.Mock;
+
+///
+/// A command executor that returns mock DataTableReader instances populated with
+/// default values based on query plan metadata (estimated rows and column schema).
+/// Designed for benchmarking ORM overhead without actual database round-trips.
+///
+public class BenchmarkCommandExecutor
+{
+ private int _estimatedRows = 1;
+ private QueryOutputInfoHelper.QueryInfo _queryInfo = new();
+ private bool _useUniqueColumnNames = false;
+
+ public static BenchmarkCommandExecutor Instance { get; } = new();
+
+ private BenchmarkCommandExecutor()
+ {
+ }
+
+ ///
+ /// Configures the executor with query plan metadata for the next benchmark iteration.
+ ///
+ public void Configure(QueryOutputInfoHelper.QueryInfo queryInfo, bool useUniqueColumnNames = false)
+ {
+ _estimatedRows = queryInfo.EstimatedRows;
+ _queryInfo = queryInfo;
+ _useUniqueColumnNames = useUniqueColumnNames;
+ }
+
+ public void UseUniqueColumnNames(bool useUniqueColumnNames)
+ {
+ _useUniqueColumnNames = useUniqueColumnNames;
+ }
+
+ public virtual int ExecuteNonQuery(DbCommand command)
+ => -1;
+
+ public virtual object ExecuteScalar(DbCommand command)
+ => 1.00m;
+
+ public virtual DbDataReader ExecuteReader(DbCommand command, CommandBehavior behavior)
+ => BuildMockReader();
+
+ public virtual Task ExecuteNonQueryAsync(DbCommand command, CancellationToken cancellationToken)
+ => Task.FromResult(-1);
+
+ public virtual Task