Skip to content

Commit 4cef711

Browse files
committed
Enhance SqlitePlugin with new features and improvements
- Added nullable `Data` property to `InputParameter`. - Updated `SqlitePlugin.Metadata` to use simplified initializer and incremented version to `1.2.0`. - Reformatted `OperationMap` for better readability. - Enhanced `Initialize` method to convert `Specifications` to `SqlitePluginSpecifications`. - Refactored `ExecuteAsync` for improved error handling and to utilize the new `Data` property. - Updated `ExecuteNonQueryAsync` and `ExecuteQueryAsync` methods for better SQL execution and logging. - Introduced `ExecuteSingleNonQueryAsync` for handling single non-query executions. - Renamed `GetSqlAndParameters` to `ExtractSqlParameters` and improved its clarity and error handling. - Added `ParseInputData` method for input data parsing. - Updated tests in `SqlitePluginTests` to reflect method renaming and validate missing SQL parameters. #4
1 parent d12ab40 commit 4cef711

3 files changed

Lines changed: 101 additions & 64 deletions

File tree

src/Models/InputParameter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ internal class InputParameter
55
public string Operation { get; set; } = string.Empty;
66
public string Sql { get; set; } = string.Empty;
77
public Dictionary<string, object>? Params { get; set; }
8+
public object? Data { get; set; }
89
}

src/SqlitePlugin.cs

Lines changed: 99 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ internal SqlitePlugin(IGuidProvider guidProvider, IReflectionGuard reflectionGua
2222
_reflectionGuard = reflectionGuard ?? throw new ArgumentNullException(nameof(reflectionGuard));
2323
}
2424

25-
public PluginMetadata Metadata => new PluginMetadata
25+
public PluginMetadata Metadata => new()
2626
{
2727
Id = Guid.Parse("6457ab5d-0487-4c06-a313-1ebf789f2b52"),
2828
Name = "Sqlite",
2929
CompanyName = "FlowSynx",
3030
Description = Resources.PluginDescription,
31-
Version = new Version(1, 1, 1),
31+
Version = new Version(1, 2, 0),
3232
Category = PluginCategory.Database,
3333
Authors = new List<string> { "FlowSynx" },
3434
Copyright = "© FlowSynx. All rights reserved.",
@@ -44,21 +44,24 @@ internal SqlitePlugin(IGuidProvider guidProvider, IReflectionGuard reflectionGua
4444

4545
public Type SpecificationsType => typeof(SqlitePluginSpecifications);
4646

47-
private Dictionary<string, Func<InputParameter, CancellationToken, Task<object?>>> OperationMap => new(StringComparer.OrdinalIgnoreCase)
48-
{
49-
["query"] = async (parameters, cancellationToken) => await ExecuteQueryAsync(parameters, cancellationToken),
50-
["execute"] = async (parameters, cancellationToken) => { await ExecuteNonQueryAsync(parameters, cancellationToken); return null; }
51-
};
47+
private Dictionary<string, Func<InputParameter, CancellationToken, Task<object?>>> OperationMap =>
48+
new Dictionary<string, Func<InputParameter, CancellationToken, Task<object?>>>(StringComparer.OrdinalIgnoreCase)
49+
{
50+
["query"] = async (p, t) => await ExecuteQueryAsync(p, t),
51+
["execute"] = async (p, t) => { await ExecuteNonQueryAsync(p, t); return null; }
52+
};
5253

5354
public IReadOnlyCollection<string> SupportedOperations => OperationMap.Keys;
5455

5556
public Task Initialize(IPluginLogger logger)
5657
{
5758
ThrowIfReflection();
5859
ArgumentNullException.ThrowIfNull(logger);
60+
5961
_sqliteSpecifications = Specifications.ToObject<SqlitePluginSpecifications>();
6062
_logger = logger;
6163
_isInitialized = true;
64+
6265
return Task.CompletedTask;
6366
}
6467

@@ -68,108 +71,114 @@ public Task Initialize(IPluginLogger logger)
6871
ThrowIfReflection();
6972
ThrowIfNotInitialized();
7073

71-
var inputParameter = parameters.ToObject<InputParameter>();
72-
var operation = inputParameter.Operation;
73-
74-
if (OperationMap.TryGetValue(operation, out var handler))
75-
{
76-
return await handler(inputParameter, cancellationToken);
77-
}
74+
var input = parameters.ToObject<InputParameter>();
75+
if (!OperationMap.TryGetValue(input.Operation, out var handler))
76+
throw new NotSupportedException($"Sqlite plugin: Operation '{input.Operation}' is not supported.");
7877

79-
throw new NotSupportedException($"Sqlite plugin: Operation '{operation}' is not supported.");
78+
return await handler(input, cancellationToken);
8079
}
8180

82-
private void ThrowIfReflection()
83-
{
84-
if (_reflectionGuard.IsCalledViaReflection())
85-
throw new InvalidOperationException(Resources.ReflectionBasedAccessIsNotAllowed);
86-
}
87-
88-
private void ThrowIfNotInitialized()
89-
{
90-
if (!_isInitialized)
91-
throw new InvalidOperationException($"Plugin '{Metadata.Name}' v{Metadata.Version} is not initialized.");
92-
}
81+
#region private methods
9382

94-
private async Task ExecuteNonQueryAsync(InputParameter parameters, CancellationToken cancellationToken)
83+
private async Task ExecuteNonQueryAsync(InputParameter input, CancellationToken token)
9584
{
96-
var (sql, sqlParams) = GetSqlAndParameters(parameters);
85+
var (sql, sqlParams) = ExtractSqlParameters(input);
86+
var connectionString = _sqliteSpecifications.ConnectionString;
9787

9888
try
9989
{
100-
var connection = new SqliteConnection(_sqliteSpecifications.ConnectionString);
101-
await connection.OpenAsync();
102-
using var cmd = new SqliteCommand(parameters.Sql, connection);
90+
await using var connection = new SqliteConnection(connectionString);
91+
await connection.OpenAsync(token);
10392

104-
AddParameters(cmd, sqlParams);
105-
106-
cancellationToken.ThrowIfCancellationRequested();
93+
var context = ParseInputData(input.Data);
10794

108-
int affectedRows = await cmd.ExecuteNonQueryAsync(cancellationToken);
109-
_logger?.LogInfo($"Non-query executed successfully. Rows affected: {affectedRows}.");
95+
if (context.StructuredData?.Count > 0)
96+
{
97+
await ExecuteStructuredDataAsync(connection, sql, context, token);
98+
}
99+
else
100+
{
101+
await ExecuteSingleNonQueryAsync(connection, sql, sqlParams, token);
102+
}
110103
}
111104
catch (Exception ex)
112105
{
113-
_logger?.LogError($"Error executing Sqlite sql statement. Error: {ex.Message}");
106+
_logger?.LogError($"Error executing Sqlite SQL statement: {ex.Message}");
114107
throw;
115108
}
116109
}
117110

118-
private async Task<PluginContext> ExecuteQueryAsync(InputParameter parameters, CancellationToken cancellationToken)
111+
private async Task<PluginContext> ExecuteQueryAsync(InputParameter input, CancellationToken token)
119112
{
120-
var (sql, sqlParams) = GetSqlAndParameters(parameters);
113+
var (sql, sqlParams) = ExtractSqlParameters(input);
114+
var connectionString = _sqliteSpecifications.ConnectionString;
121115

122116
try
123117
{
124118
var result = new List<Dictionary<string, object>>();
125-
var connection = new SqliteConnection(_sqliteSpecifications.ConnectionString);
126-
await connection.OpenAsync();
127-
using var cmd = new SqliteCommand(sql, connection);
119+
await using var connection = new SqliteConnection(connectionString);
120+
await connection.OpenAsync(token);
128121

122+
await using var cmd = new SqliteCommand(sql, connection);
129123
AddParameters(cmd, sqlParams);
130124

131-
cancellationToken.ThrowIfCancellationRequested();
132-
133-
using var reader = await cmd.ExecuteReaderAsync(cancellationToken);
134-
135-
while (await reader.ReadAsync(cancellationToken))
125+
await using var reader = await cmd.ExecuteReaderAsync(token);
126+
while (await reader.ReadAsync(token))
136127
{
137-
var row = new Dictionary<string, object>();
138-
for (int i = 0; i < reader.FieldCount; i++)
139-
{
140-
row[reader.GetName(i)] = reader.GetValue(i);
141-
}
128+
var row = Enumerable.Range(0, reader.FieldCount)
129+
.ToDictionary(reader.GetName, reader.GetValue);
142130
result.Add(row);
143131
}
144132

145133
_logger?.LogInfo($"Query executed successfully. Rows returned: {result.Count}.");
146-
string key = $"{_guidProvider.NewGuid()}";
147-
return new PluginContext(key, "Data")
134+
135+
return new PluginContext(_guidProvider.NewGuid().ToString(), "Data")
148136
{
149137
Format = "Database",
150138
StructuredData = result
151139
};
152140
}
153141
catch (Exception ex)
154142
{
155-
_logger?.LogError($"Error executing Sqlite sql statement. Error: {ex.Message}");
143+
_logger?.LogError($"Error executing Sqlite query: {ex.Message}");
156144
throw;
157145
}
158146
}
159147

160-
private (string Sql, Dictionary<string, object> Parameters) GetSqlAndParameters(InputParameter parameters)
148+
private async Task ExecuteStructuredDataAsync(
149+
SqliteConnection connection, string sql, PluginContext context, CancellationToken token)
161150
{
162-
if (string.IsNullOrEmpty(parameters.Sql))
163-
throw new ArgumentException("Missing 'sql' parameter.");
164-
165-
Dictionary<string, object> sqlParams = new();
151+
int totalAffected = 0;
166152

167-
if (parameters.Params is Dictionary<string, object> paramDict)
153+
if (context.StructuredData is not null)
168154
{
169-
sqlParams = paramDict;
155+
foreach (var row in context.StructuredData)
156+
{
157+
await using var cmd = new SqliteCommand(sql, connection);
158+
AddParameters(cmd, row);
159+
totalAffected += await cmd.ExecuteNonQueryAsync(token);
160+
}
170161
}
171162

172-
return (parameters.Sql, sqlParams);
163+
_logger?.LogInfo($"Executed structured SQL for {context.StructuredData?.Count} rows. Total affected: {totalAffected}");
164+
}
165+
166+
private async Task ExecuteSingleNonQueryAsync(
167+
SqliteConnection connection, string sql, Dictionary<string, object> sqlParams, CancellationToken token)
168+
{
169+
await using var cmd = new SqliteCommand(sql, connection);
170+
AddParameters(cmd, sqlParams);
171+
int affected = await cmd.ExecuteNonQueryAsync(token);
172+
_logger?.LogInfo($"Non-query executed successfully. Rows affected: {affected}.");
173+
}
174+
175+
private (string Sql, Dictionary<string, object> Params) ExtractSqlParameters(InputParameter input)
176+
{
177+
if (string.IsNullOrWhiteSpace(input.Sql))
178+
throw new ArgumentException("Missing 'sql' parameter.");
179+
180+
var sqlParams = input.Params as Dictionary<string, object> ?? new();
181+
return (input.Sql, sqlParams);
173182
}
174183

175184
private void AddParameters(SqliteCommand cmd, Dictionary<string, object>? parameters)
@@ -226,4 +235,31 @@ private void AddParameters(SqliteCommand cmd, Dictionary<string, object>? parame
226235
}
227236
}
228237
}
238+
239+
private PluginContext ParseInputData(object? data)
240+
{
241+
if (data is null)
242+
throw new ArgumentNullException(nameof(data), "Input data cannot be null.");
243+
244+
return data switch
245+
{
246+
PluginContext context => context,
247+
IEnumerable<PluginContext> => throw new NotSupportedException("List of PluginContext is not supported."),
248+
_ => throw new NotSupportedException("Unsupported input data format.")
249+
};
250+
}
251+
252+
private void ThrowIfReflection()
253+
{
254+
if (_reflectionGuard.IsCalledViaReflection())
255+
throw new InvalidOperationException(Resources.ReflectionBasedAccessIsNotAllowed);
256+
}
257+
258+
private void ThrowIfNotInitialized()
259+
{
260+
if (!_isInitialized)
261+
throw new InvalidOperationException($"Plugin '{Metadata.Name}' v{Metadata.Version} is not initialized.");
262+
}
263+
264+
#endregion
229265
}

tests/SqlitePluginTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public void GetSqlAndParameters_ThrowsOnMissingSql()
5757
{
5858
var plugin = CreatePlugin();
5959
var input = new InputParameter { Sql = null! };
60-
var method = typeof(SqlitePlugin).GetMethod("GetSqlAndParameters", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
60+
var method = typeof(SqlitePlugin).GetMethod("ExtractSqlParameters", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)!;
6161
var ex = Assert.Throws<TargetInvocationException>(() => method.Invoke(plugin, new object[] { input }));
6262
Assert.IsType<ArgumentException>(ex.InnerException);
6363
Assert.Contains("Missing 'sql' parameter", ex.InnerException!.Message);

0 commit comments

Comments
 (0)