Skip to content

Commit f6e9f46

Browse files
committed
sql select support mongodb
1 parent d91b0a3 commit f6e9f46

File tree

6 files changed

+84
-5
lines changed

6 files changed

+84
-5
lines changed

src/Plugins/BotSharp.Plugin.SqlDriver/BotSharp.Plugin.SqlDriver.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
<ItemGroup>
106106
<PackageReference Include="MySqlConnector" />
107107
<PackageReference Include="Npgsql" />
108+
<PackageReference Include="MongoDB.Driver" />
108109
</ItemGroup>
109110

110111
<ItemGroup>

src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
using Microsoft.Data.SqlClient;
22
using MySqlConnector;
33
using Npgsql;
4+
using MongoDB.Driver;
5+
using MongoDB.Bson;
6+
using System.Text.RegularExpressions;
47
using static Dapper.SqlMapper;
58

69
namespace BotSharp.Plugin.SqlDriver.Functions;
@@ -34,6 +37,7 @@ public async Task<bool> Execute(RoleDialogModel message)
3437
"mysql" => RunQueryInMySql(args),
3538
"sqlserver" or "mssql" => RunQueryInSqlServer(args),
3639
"redshift" => RunQueryInRedshift(args),
40+
"mongodb" => RunQueryInMongoDb(args),
3741
_ => throw new NotImplementedException($"Database type {dbType} is not supported.")
3842
};
3943

@@ -43,6 +47,7 @@ public async Task<bool> Execute(RoleDialogModel message)
4347
}
4448
else
4549
{
50+
if (dbType == "mongodb") message.StopCompletion = true;
4651
message.Content = JsonSerializer.Serialize(result);
4752
args.Return.Value = message.Content;
4853
}
@@ -85,4 +90,73 @@ private IEnumerable<dynamic> RunQueryInRedshift(SqlStatement args)
8590
}
8691
return connection.Query(args.Statement, dictionary);
8792
}
93+
94+
private IEnumerable<dynamic> RunQueryInMongoDb(SqlStatement args)
95+
{
96+
var settings = _services.GetRequiredService<SqlDriverSetting>();
97+
var client = new MongoClient(settings.MongoDbConnectionString);
98+
99+
// Normalize multi-line query to single line
100+
var statement = Regex.Replace(args.Statement.Trim(), @"\s+", " ");
101+
102+
// Parse MongoDB query: database.collection.find({query}).projection({}).sort({}).limit(100)
103+
var match = Regex.Match(statement,
104+
@"^([^.]+)\.([^.]+)\.find\s*\((.*?)\)(.*)?$",
105+
RegexOptions.Singleline);
106+
107+
if (!match.Success)
108+
return ["Invalid MongoDB query format. Expected: database.collection.find({query})"];
109+
110+
var queryJson = ApplyParameters(match.Groups[3].Value.Trim(), args.Parameters);
111+
112+
try
113+
{
114+
var database = client.GetDatabase(match.Groups[1].Value);
115+
var collection = database.GetCollection<BsonDocument>(match.Groups[2].Value);
116+
117+
var filter = string.IsNullOrWhiteSpace(queryJson) || queryJson == "{}"
118+
? Builders<BsonDocument>.Filter.Empty
119+
: BsonDocument.Parse(queryJson);
120+
121+
var findFluent = collection.Find(filter);
122+
findFluent = ApplyChainedOperations(findFluent, match.Groups[4].Value);
123+
124+
return findFluent.ToList().Select(doc => BsonTypeMapper.MapToDotNetValue(doc));
125+
}
126+
catch (Exception ex)
127+
{
128+
return [$"Invalid MongoDB query: {ex.Message}"];
129+
}
130+
}
131+
132+
private string ApplyParameters(string query, Models.SqlParameter[] parameters)
133+
{
134+
foreach (var p in parameters)
135+
query = query.Replace($"@{p.Name}", p.Value?.ToString() ?? "null");
136+
return query;
137+
}
138+
139+
private IFindFluent<BsonDocument, BsonDocument> ApplyChainedOperations(
140+
IFindFluent<BsonDocument, BsonDocument> findFluent, string chainedOps)
141+
{
142+
if (string.IsNullOrWhiteSpace(chainedOps)) return findFluent;
143+
144+
// Apply projection
145+
var projMatch = Regex.Match(chainedOps, @"\.projection\s*\((.*?)\)", RegexOptions.Singleline);
146+
if (projMatch.Success)
147+
findFluent = findFluent.Project<BsonDocument>(BsonDocument.Parse(projMatch.Groups[1].Value.Trim()));
148+
149+
// Apply sort
150+
var sortMatch = Regex.Match(chainedOps, @"\.sort\s*\((.*?)\)", RegexOptions.Singleline);
151+
if (sortMatch.Success)
152+
findFluent = findFluent.Sort(BsonDocument.Parse(sortMatch.Groups[1].Value.Trim()));
153+
154+
// Apply limit
155+
var limitMatch = Regex.Match(chainedOps, @"\.limit\s*\((\d+)\)");
156+
if (limitMatch.Success && int.TryParse(limitMatch.Groups[1].Value, out var limit))
157+
findFluent = findFluent.Limit(limit);
158+
159+
return findFluent;
160+
}
161+
88162
}

src/Plugins/BotSharp.Plugin.SqlDriver/Settings/SqlDriverSetting.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ public class SqlDriverSetting
99
public string SqlServerConnectionString { get; set; } = null!;
1010
public string SqlServerExecutionConnectionString { get; set; } = null!;
1111
public string RedshiftConnectionString { get; set; } = null!;
12+
public string MongoDbConnectionString { get; set; } = null!;
1213
public bool ExecuteSqlSelectAutonomous { get; set; } = false;
1314
public bool FormattingResult { get; set; } = true;
1415
}

src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/agent.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"id": "beda4c12-e1ec-4b4b-b328-3df4a6687c4f",
33
"name": "SQL Driver",
4-
"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.",
4+
"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.",
55
"iconUrl": "https://cdn-icons-png.flaticon.com/512/3161/3161158.png",
66
"type": "task",
77
"createdDateTime": "2023-11-15T13:49:00Z",
@@ -18,7 +18,7 @@
1818
"field": "sql_statement",
1919
"required": true,
2020
"field_type": "string",
21-
"description": "SQL statement explicitly provided in the context."
21+
"description": "SQL statement or MongoDB query explicitly provided in the context."
2222
}
2323
]
2424
}

src/Plugins/BotSharp.Plugin.SqlDriver/data/agents/beda4c12-e1ec-4b4b-b328-3df4a6687c4f/functions/sql_select.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"properties": {
77
"sql_statement": {
88
"type": "string",
9-
"description": "SQL statement with SELECT provided in context."
9+
"description": "Provide an SQL query statement or a JSON query for MongoDB."
1010
},
1111
"reason": {
1212
"type": "string",
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
1-
You're a SQL driver who can execute the sql statement.
1+
You're a database driver who can execute SQL statements and MongoDB queries.
22

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

0 commit comments

Comments
 (0)