BotSharp/src/Plugins/BotSharp.Plugin.SqlDriver/Functions/SqlSelect.cs
2025-10-14 15:55:12 +08:00

163 lines
5.8 KiB
C#

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;
public class SqlSelect : IFunctionCallback
{
public string Name => "sql_select";
private readonly IServiceProvider _services;
public SqlSelect(IServiceProvider services)
{
_services = services;
}
public async Task<bool> Execute(RoleDialogModel message)
{
var args = JsonSerializer.Deserialize<SqlStatement>(message.FunctionArgs);
if (args.GeneratedWithoutTableDefinition)
{
message.Content = $"Get the table definition first.";
return false;
}
// check if need to instantely
var dbHook = _services.GetRequiredService<ISqlDriverHook>();
var dbType = dbHook.GetDatabaseType(message);
var result = dbType switch
{
"mysql" => RunQueryInMySql(args),
"sqlserver" or "mssql" => RunQueryInSqlServer(args),
"redshift" => RunQueryInRedshift(args),
"mongodb" => RunQueryInMongoDb(args),
_ => throw new NotImplementedException($"Database type {dbType} is not supported.")
};
if (result == null)
{
message.Content = "Record not found";
}
else
{
if (dbType == "mongodb") message.StopCompletion = true;
message.Content = JsonSerializer.Serialize(result);
args.Return.Value = message.Content;
}
return true;
}
private IEnumerable<dynamic> RunQueryInMySql(SqlStatement args)
{
var settings = _services.GetRequiredService<SqlDriverSetting>();
using var connection = new MySqlConnection(settings.MySqlExecutionConnectionString);
var dictionary = new Dictionary<string, object>();
foreach (var p in args.Parameters)
{
dictionary["@" + p.Name] = p.Value;
}
return connection.Query(args.Statement, dictionary);
}
private IEnumerable<dynamic> RunQueryInSqlServer(SqlStatement args)
{
var settings = _services.GetRequiredService<SqlDriverSetting>();
using var connection = new SqlConnection(settings.SqlServerExecutionConnectionString ?? settings.SqlServerConnectionString);
var dictionary = new Dictionary<string, object>();
foreach (var p in args.Parameters)
{
dictionary["@" + p.Name] = p.Value;
}
return connection.Query(args.Statement, dictionary);
}
private IEnumerable<dynamic> RunQueryInRedshift(SqlStatement args)
{
var settings = _services.GetRequiredService<SqlDriverSetting>();
using var connection = new NpgsqlConnection(settings.RedshiftConnectionString);
var dictionary = new Dictionary<string, object>();
foreach (var p in args.Parameters)
{
dictionary["@" + p.Name] = p.Value;
}
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;
}
}