Skip to content

Commit

Permalink
Add vps table
Browse files Browse the repository at this point in the history
  • Loading branch information
ronnygunawan committed Feb 17, 2024
1 parent 800d38a commit c2bc6e0
Show file tree
Hide file tree
Showing 10 changed files with 335 additions and 65 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,8 @@ out AIFollowUpMessage? aiFollowUpMessage
try {
await _telegramBotClient.SendTextMessageAsync(
chatId: update.Message.Chat.Id,
text: $"Your SQL contains more than one statement.",
text: $"<code>Your SQL contains more than one statement.</code>",
parseMode: ParseMode.Html,
replyToMessageId: update.Message.MessageId,
cancellationToken: cancellationToken
);
Expand All @@ -192,7 +193,8 @@ await _telegramBotClient.SendTextMessageAsync(
try {
await _telegramBotClient.SendTextMessageAsync(
chatId: update.Message.Chat.Id,
text: $"Your SQL is not a SELECT statement.",
text: $"<code>Your SQL is not a SELECT statement.</code>",
parseMode: ParseMode.Html,
replyToMessageId: update.Message.MessageId,
cancellationToken: cancellationToken
);
Expand Down
103 changes: 59 additions & 44 deletions BotNet.CommandHandlers/SQL/SQLCommandHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using BotNet.Commands.SQL;
using BotNet.Services.SQL;
using BotNet.Services.Sqlite;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.DependencyInjection;
using SqlParser.Ast;
using Telegram.Bot;
Expand All @@ -20,7 +21,8 @@ public async Task Handle(SQLCommand command, CancellationToken cancellationToken
|| froms.Count == 0) {
await _telegramBotClient.SendTextMessageAsync(
chatId: command.Chat.Id,
text: "No FROM clause found.",
text: "<code>No FROM clause found.</code>",
parseMode: ParseMode.Html,
replyToMessageId: command.SQLMessageId,
cancellationToken: cancellationToken
);
Expand Down Expand Up @@ -53,9 +55,12 @@ await _telegramBotClient.SendTextMessageAsync(
await _telegramBotClient.SendTextMessageAsync(
chatId: command.Chat.Id,
text: $$"""
Table '{{table}}' not found. Available tables are:
<code>Table '{{table}}' not found. Available tables are:
- pilpres
- vps
</code>
""",
parseMode: ParseMode.Html,
replyToMessageId: command.SQLMessageId,
cancellationToken: cancellationToken
);
Expand All @@ -68,52 +73,64 @@ await _telegramBotClient.SendTextMessageAsync(
// Execute query
using ScopedDatabase scopedDatabase = serviceScope.ServiceProvider.GetRequiredService<ScopedDatabase>();
StringBuilder resultBuilder = new();
scopedDatabase.ExecuteReader(
commandText: command.RawStatement,
readAction: (reader) => {
string[] values = new string[reader.FieldCount];

// Get column names
for (int i = 0; i < reader.FieldCount; i++) {
values[i] = '"' + reader.GetName(i).Replace("\"", "\"\"") + '"';
}
resultBuilder.AppendLine(string.Join(',', values));

// Get rows
while (reader.Read()) {
try {
scopedDatabase.ExecuteReader(
commandText: command.RawStatement,
readAction: (reader) => {
string[] values = new string[reader.FieldCount];

// Get column names
for (int i = 0; i < reader.FieldCount; i++) {
if (reader.IsDBNull(i)) {
values[i] = "";
continue;
}
values[i] = '"' + reader.GetName(i).Replace("\"", "\"\"") + '"';
}
resultBuilder.AppendLine(string.Join(',', values));

Type fieldType = reader.GetFieldType(i);
if (fieldType == typeof(string)) {
values[i] = '"' + reader.GetString(i).Replace("\"", "\"\"") + '"';
} else if (fieldType == typeof(int)) {
values[i] = reader.GetInt32(i).ToString();
} else if (fieldType == typeof(long)) {
values[i] = reader.GetInt64(i).ToString();
} else if (fieldType == typeof(float)) {
values[i] = reader.GetFloat(i).ToString();
} else if (fieldType == typeof(double)) {
values[i] = reader.GetDouble(i).ToString();
} else if (fieldType == typeof(decimal)) {
values[i] = reader.GetDecimal(i).ToString();
} else if (fieldType == typeof(bool)) {
values[i] = reader.GetBoolean(i).ToString();
} else if (fieldType == typeof(DateTime)) {
values[i] = reader.GetDateTime(i).ToString();
} else if (fieldType == typeof(byte[])) {
values[i] = BitConverter.ToString(reader.GetFieldValue<byte[]>(i)).Replace("-", "");
} else {
values[i] = reader[i].ToString();
// Get rows
while (reader.Read()) {
for (int i = 0; i < reader.FieldCount; i++) {
if (reader.IsDBNull(i)) {
values[i] = "";
continue;
}

Type fieldType = reader.GetFieldType(i);
if (fieldType == typeof(string)) {
values[i] = '"' + reader.GetString(i).Replace("\"", "\"\"") + '"';
} else if (fieldType == typeof(int)) {
values[i] = reader.GetInt32(i).ToString();
} else if (fieldType == typeof(long)) {
values[i] = reader.GetInt64(i).ToString();
} else if (fieldType == typeof(float)) {
values[i] = reader.GetFloat(i).ToString();
} else if (fieldType == typeof(double)) {
values[i] = reader.GetDouble(i).ToString();
} else if (fieldType == typeof(decimal)) {
values[i] = reader.GetDecimal(i).ToString();
} else if (fieldType == typeof(bool)) {
values[i] = reader.GetBoolean(i).ToString();
} else if (fieldType == typeof(DateTime)) {
values[i] = reader.GetDateTime(i).ToString();
} else if (fieldType == typeof(byte[])) {
values[i] = BitConverter.ToString(reader.GetFieldValue<byte[]>(i)).Replace("-", "");
} else {
values[i] = reader[i].ToString();

Check warning on line 117 in BotNet.CommandHandlers/SQL/SQLCommandHandler.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference assignment.

Check warning on line 117 in BotNet.CommandHandlers/SQL/SQLCommandHandler.cs

View workflow job for this annotation

GitHub Actions / build

Possible null reference assignment.
}
}
resultBuilder.AppendLine(string.Join(',', values));
}
resultBuilder.AppendLine(string.Join(',', values));
}
}
);
);
} catch (SqliteException exc) {
await _telegramBotClient.SendTextMessageAsync(
chatId: command.Chat.Id,
text: "<code>" + exc.Message.Replace("SQLite Error", "Error") + "</code>",
parseMode: ParseMode.Html,
replyToMessageId: command.SQLMessageId,
cancellationToken: cancellationToken
);
return;
}

// Send result
await _telegramBotClient.SendTextMessageAsync(
Expand All @@ -123,8 +140,6 @@ await _telegramBotClient.SendTextMessageAsync(
replyToMessageId: command.SQLMessageId,
cancellationToken: cancellationToken
);

return;
}

private static void CollectTableNames(ref HashSet<string> tables, TableFactor tableFactor) {
Expand Down
112 changes: 112 additions & 0 deletions BotNet.Services/GoogleSheets/GoogleSheetsClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Google.Apis.Sheets.v4;
using Google.Apis.Sheets.v4.Data;

namespace BotNet.Services.GoogleSheets {
public sealed class GoogleSheetsClient(
SheetsService sheetsService
) {
private readonly SheetsService _sheetsService = sheetsService;

public async Task<ImmutableList<T>> GetDataAsync<T>(string spreadsheetId, string range, string firstColumn, CancellationToken cancellationToken) {
int firstColumnIndex = GetColumnIndex(firstColumn);

// Fetch data
SpreadsheetsResource.ValuesResource.GetRequest getRequest = _sheetsService.Spreadsheets.Values.Get(
spreadsheetId: spreadsheetId,
range: range
);
ValueRange response = await getRequest.ExecuteAsync(cancellationToken);

// Get type info
ConstructorInfo constructor = typeof(T).GetConstructors().Single();
PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly);

// Map data
ImmutableList<T>.Builder builder = ImmutableList.CreateBuilder<T>();
foreach (IList<object> row in response.Values) {
if (row.Count < properties.Length) continue;

object?[] parameters = new object?[properties.Length];
for (int i = 0; i < properties.Length; i++) {
PropertyInfo property = properties[i];

FromColumnAttribute fromColumn = property.GetCustomAttribute<FromColumnAttribute>()
?? throw new InvalidProgramException("Property not decorated with [FromColumn]");

int columnIndex = GetColumnIndex(fromColumn.Column) - firstColumnIndex;
if (columnIndex >= row.Count) {
parameters[i] = null;
continue;
}

if (row[columnIndex] is not string value) {
parameters[i] = null;
continue;
}

if (property.PropertyType == typeof(string)) {
parameters[i] = value;
} else if (property.PropertyType == typeof(decimal)) {
if (decimal.TryParse(value, out decimal decimalValue)) {
parameters[i] = decimalValue;
} else {
parameters[i] = 0m;
}
} else if (property.PropertyType == typeof(decimal?)) {
if (decimal.TryParse(value, out decimal decimalValue)) {
parameters[i] = decimalValue;
} else {
parameters[i] = (decimal?)null;
}
} else if (property.PropertyType == typeof(int)) {
if (int.TryParse(value, out int intValue)) {
parameters[i] = intValue;
} else {
parameters[i] = 0;
}
} else if (property.PropertyType == typeof(int?)) {
if (int.TryParse(value, out int intValue)) {
parameters[i] = intValue;
} else {
parameters[i] = (int?)null;
}
} else if (property.PropertyType == typeof(double)) {
if (double.TryParse(value, out double doubleValue)) {
parameters[i] = doubleValue;
} else {
parameters[i] = 0.0;
}
} else if (property.PropertyType == typeof(double?)) {
if (double.TryParse(value, out double doubleValue)) {
parameters[i] = doubleValue;
} else {
parameters[i] = (double?)null;
}
} else {
parameters[i] = Convert.ChangeType(value, property.PropertyType);
}
}

builder.Add((T)constructor.Invoke(parameters));
}

return builder.ToImmutable();
}

public static int GetColumnIndex(string columnName) {
int index = 0;
for (int i = 0; i < columnName.Length; i++) {
index *= 26;
index += (columnName[i] - 'A' + 1);
}
return index - 1;
}
}
}
5 changes: 5 additions & 0 deletions BotNet.Services/GoogleSheets/Options/GoogleSheetsOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace BotNet.Services.GoogleSheets.Options {
public class GoogleSheetsOptions {
public string? ApiKey { get; set; }
}
}
20 changes: 20 additions & 0 deletions BotNet.Services/GoogleSheets/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using BotNet.Services.GoogleSheets.Options;
using Google.Apis.Services;
using Google.Apis.Sheets.v4;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

namespace BotNet.Services.GoogleSheets {
public static class ServiceCollectionExtensions {
public static IServiceCollection AddGoogleSheets(this IServiceCollection services) {
services.AddSingleton(serviceProvider => {
GoogleSheetsOptions options = serviceProvider.GetRequiredService<IOptions<GoogleSheetsOptions>>().Value;
return new SheetsService(new BaseClientService.Initializer {
ApiKey = options.ApiKey
});
});
services.AddTransient<GoogleSheetsClient>();
return services;
}
}
}
11 changes: 11 additions & 0 deletions BotNet.Services/KokizzuVPSBenchmark/ServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using BotNet.Services.SQL;
using Microsoft.Extensions.DependencyInjection;

namespace BotNet.Services.KokizzuVPSBenchmark {
public static class ServiceCollectionExtensions {
public static IServiceCollection AddKokizzuVPSBenchmarkDataSource(this IServiceCollection services) {
services.AddKeyedTransient<IScopedDataSource, VPSBenchmarkDataSource>("vps");
return services;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
using System;
using BotNet.Services.GoogleSheets;
using BotNet.Services.GoogleSheets;

namespace BotNet.Services.VPS {
namespace BotNet.Services.KokizzuVPSBenchmark {
public sealed record VPSBenchmark(
[property: FromColumn("A")] string Provider,
[property: FromColumn("B")] string Location,
[property: FromColumn("C")] DateOnly BenchmarkDate,
[property: FromColumn("C")] string BenchmarkDate,
[property: FromColumn("E")] string? VerdictCons,
[property: FromColumn("F")] decimal IdrMo,
[property: FromColumn("G")] int Core,
Expand Down
Loading

0 comments on commit c2bc6e0

Please sign in to comment.