sql select support mongodb

This commit is contained in:
nick.yi 2025-10-14 15:55:12 +08:00
parent d91b0a3f63
commit f6e9f4689b
6 changed files with 84 additions and 5 deletions

View file

@ -105,6 +105,7 @@
<ItemGroup>
<PackageReference Include="MySqlConnector" />
<PackageReference Include="Npgsql" />
<PackageReference Include="MongoDB.Driver" />
</ItemGroup>
<ItemGroup>

View file

@ -1,6 +1,9 @@
using Microsoft.Data.SqlClient;
using MySqlConnector;
using Npgsql;
using MongoDB.Driver;
using MongoDB.Bson;
using System.Text.RegularExpressions;
using static Dapper.SqlMapper;
namespace BotSharp.Plugin.SqlDriver.Functions;
@ -34,6 +37,7 @@ public class SqlSelect : IFunctionCallback
"mysql" => RunQueryInMySql(args),
"sqlserver" or "mssql" => RunQueryInSqlServer(args),
"redshift" => RunQueryInRedshift(args),
"mongodb" => RunQueryInMongoDb(args),
_ => throw new NotImplementedException($"Database type {dbType} is not supported.")
};
@ -43,6 +47,7 @@ public class SqlSelect : IFunctionCallback
}
else
{
if (dbType == "mongodb") message.StopCompletion = true;
message.Content = JsonSerializer.Serialize(result);
args.Return.Value = message.Content;
}
@ -85,4 +90,73 @@ public class SqlSelect : IFunctionCallback
}
return connection.Query(args.Statement, dictionary);
}
private IEnumerable<dynamic> RunQueryInMongoDb(SqlStatement args)
{
var settings = _services.GetRequiredService<SqlDriverSetting>();
var client = new MongoClient(settings.MongoDbConnectionString);
// Normalize multi-line query to single line
var statement = Regex.Replace(args.Statement.Trim(), @"\s+", " ");
// Parse MongoDB query: database.collection.find({query}).projection({}).sort({}).limit(100)
var match = Regex.Match(statement,
@"^([^.]+)\.([^.]+)\.find\s*\((.*?)\)(.*)?$",
RegexOptions.Singleline);
if (!match.Success)
return ["Invalid MongoDB query format. Expected: database.collection.find({query})"];
var queryJson = ApplyParameters(match.Groups[3].Value.Trim(), args.Parameters);
try
{
var database = client.GetDatabase(match.Groups[1].Value);
var collection = database.GetCollection<BsonDocument>(match.Groups[2].Value);
var filter = string.IsNullOrWhiteSpace(queryJson) || queryJson == "{}"
? Builders<BsonDocument>.Filter.Empty
: BsonDocument.Parse(queryJson);
var findFluent = collection.Find(filter);
findFluent = ApplyChainedOperations(findFluent, match.Groups[4].Value);
return findFluent.ToList().Select(doc => BsonTypeMapper.MapToDotNetValue(doc));
}
catch (Exception ex)
{
return [$"Invalid MongoDB query: {ex.Message}"];
}
}
private string ApplyParameters(string query, Models.SqlParameter[] parameters)
{
foreach (var p in parameters)
query = query.Replace($"@{p.Name}", p.Value?.ToString() ?? "null");
return query;
}
private IFindFluent<BsonDocument, BsonDocument> ApplyChainedOperations(
IFindFluent<BsonDocument, BsonDocument> findFluent, string chainedOps)
{
if (string.IsNullOrWhiteSpace(chainedOps)) return findFluent;
// Apply projection
var projMatch = Regex.Match(chainedOps, @"\.projection\s*\((.*?)\)", RegexOptions.Singleline);
if (projMatch.Success)
findFluent = findFluent.Project<BsonDocument>(BsonDocument.Parse(projMatch.Groups[1].Value.Trim()));
// Apply sort
var sortMatch = Regex.Match(chainedOps, @"\.sort\s*\((.*?)\)", RegexOptions.Singleline);
if (sortMatch.Success)
findFluent = findFluent.Sort(BsonDocument.Parse(sortMatch.Groups[1].Value.Trim()));
// Apply limit
var limitMatch = Regex.Match(chainedOps, @"\.limit\s*\((\d+)\)");
if (limitMatch.Success && int.TryParse(limitMatch.Groups[1].Value, out var limit))
findFluent = findFluent.Limit(limit);
return findFluent;
}
}

View file

@ -9,6 +9,7 @@ public class SqlDriverSetting
public string SqlServerConnectionString { get; set; } = null!;
public string SqlServerExecutionConnectionString { get; set; } = null!;
public string RedshiftConnectionString { get; set; } = null!;
public string MongoDbConnectionString { get; set; } = null!;
public bool ExecuteSqlSelectAutonomous { get; set; } = false;
public bool FormattingResult { get; set; } = true;
}

View file

@ -1,7 +1,7 @@
{
"id": "beda4c12-e1ec-4b4b-b328-3df4a6687c4f",
"name": "SQL Driver",
"description": "Transfer to this Agent when user mentions to execute the sql statement. Only call when executable SQL statements are explicitly provided in the context.",
"description": "Transfer to this Agent when user mentions to execute the sql statement or MongoDB query. Only call when executable SQL statements or MongoDB queries are explicitly provided in the context.",
"iconUrl": "https://cdn-icons-png.flaticon.com/512/3161/3161158.png",
"type": "task",
"createdDateTime": "2023-11-15T13:49:00Z",
@ -18,7 +18,7 @@
"field": "sql_statement",
"required": true,
"field_type": "string",
"description": "SQL statement explicitly provided in the context."
"description": "SQL statement or MongoDB query explicitly provided in the context."
}
]
}

View file

@ -6,7 +6,7 @@
"properties": {
"sql_statement": {
"type": "string",
"description": "SQL statement with SELECT provided in context."
"description": "Provide an SQL query statement or a JSON query for MongoDB."
},
"reason": {
"type": "string",

View file

@ -1,6 +1,9 @@
You're a SQL driver who can execute the sql statement.
You're a database driver who can execute SQL statements and MongoDB queries.
Your response must meet below requirements:
* You can only execute the SQL from the conversation. You can't generate one by yourself;
* You can only execute the SQL statement or MongoDB query from the conversation. You can't generate one by yourself;
* **IMPORTANT**: Keep the original query format. Do NOT convert MongoDB queries to SQL or SQL to MongoDB queries;
* For SQL statements: Execute as-is for MySQL, SQL Server, or Redshift;
* For MongoDB queries: Use format `database.collection.find({query})`. Example: `db.users.find({"age": 25})`;
* The return field alias should be meaningful, you can use the combination of column and value as the alias name;
* Use "Unique Index" to help check record existence;