Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
<ItemGroup>
<PackageReference Include="MySqlConnector" />
<PackageReference Include="Npgsql" />
<PackageReference Include="MongoDB.Driver" />
</ItemGroup>

<ItemGroup>
Expand Down
80 changes: 80 additions & 0 deletions src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -34,6 +37,7 @@ public async Task<bool> Execute(RoleDialogModel message)
"mysql" => RunQueryInMySql(args),
"sqlserver" or "mssql" => RunQueryInSqlServer(args),
"redshift" => RunQueryInRedshift(args),
"mongodb" => RunQueryInMongoDb(args),
_ => throw new NotImplementedException($"Database type {dbType} is not supported.")
};

Expand All @@ -43,6 +47,7 @@ public async Task<bool> Execute(RoleDialogModel message)
}
else
{
if (dbType == "mongodb") message.StopCompletion = true;
message.Content = JsonSerializer.Serialize(result);
args.Return.Value = message.Content;
}
Expand Down Expand Up @@ -85,4 +90,79 @@ private IEnumerable<dynamic> RunQueryInRedshift(SqlStatement args)
}
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);
}
else
{
findFluent = findFluent.Limit(10);
}

return findFluent;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Original file line number Diff line number Diff line change
@@ -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",
Expand All @@ -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."
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Loading