Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix possible exception due to an invalid SQL query and make project compatible with .NET 8 #3

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
25 changes: 14 additions & 11 deletions src/AnkiNet/CollectionFile/Database/CardRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ public CardRepository(SqliteConnection connection) : base(connection)

protected override string TableName => "[cards]";

protected override string Columns =>
"[id], [nid], [did], [ord], [mod], " +
"[usn], [type], [queue], [due], [ivl], " +
"[factor], [reps], [lapses], [left], [odue]," +
"[odid], [flags], [data]";
protected override IReadOnlyList<string> Columns { get; } =
[
"[id]", "[nid]", "[did]", "[ord]", "[mod]",
"[usn]", "[type]", "[queue]", "[due]", "[ivl]",
"[factor]", "[reps]", "[lapses]", "[left]", "[odue]",
"[odid]", "[flags]", "[data]"
];

protected override string GetValues(card i)
protected override IReadOnlyList<object> GetValues(card i)
{
return
$"{i.id},{i.nid},{i.did},{i.ord},{i.mod}," +
$"{i.usn},{i.type},{i.queue},{i.due},{i.ivl}," +
$"{i.factor},{i.reps},{i.lapses},{i.left},{i.odue}," +
$"{i.odid},{i.flags},'{i.data}'";
return [
i.id, i.nid, i.did, i.ord, i.mod,
i.usn, i.type, i.queue, i.due, i.ivl,
i.factor, i.reps, i.lapses, i.left, i.odue,
i.odid, i.flags, i.data
];
}

protected override card Map(SqliteDataReader reader)
Expand Down
21 changes: 12 additions & 9 deletions src/AnkiNet/CollectionFile/Database/ColRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,20 @@ public ColRepository(SqliteConnection connection) : base(connection)

protected override string TableName => "[col]";

protected override string Columns =>
"[id], [crt], [mod], [scm], [ver], " +
"[dty], [usn], [ls], [conf], [models], " +
"[decks], [dconf], [tags]";
protected override IReadOnlyList<string> Columns { get; } =
[
"[id]", "[crt]", "[mod]", "[scm]", "[ver]",
"[dty]", "[usn]", "[ls]", "[conf]", "[models]",
"[decks]", "[dconf]", "[tags]"
];

protected override string GetValues(col i)
protected override IReadOnlyList<object> GetValues(col i)
{
return
$"{i.id},{i.crt},{i.mod},{i.scm},{i.ver}," +
$"{i.dty},{i.usn},{i.ls},'{i.conf}','{i.models}'," +
$"'{i.decks}','{i.dconf}','{i.tags}'";
return [
i.id, i.crt, i.mod, i.scm, i.ver,
i.dty, i.usn, i.ls, i.conf, i.models,
i.decks, i.dconf, i.tags
];
}

protected override col Map(SqliteDataReader reader)
Expand Down
7 changes: 4 additions & 3 deletions src/AnkiNet/CollectionFile/Database/GraveRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@ public GraveRepository(SqliteConnection connection) : base(connection)

protected override string TableName => "[graves]";

protected override string Columns => "[usn], [oid], [type]";
protected override IReadOnlyList<string> Columns { get; } =
["[usn]", "[oid]", "[type]"];

protected override string GetValues(grave i)
protected override IReadOnlyList<object> GetValues(grave i)
{
return $"{i.usn},{i.oid},{i.type}";
return [i.usn, i.oid, i.type];
}

protected override grave Map(SqliteDataReader reader)
Expand Down
25 changes: 14 additions & 11 deletions src/AnkiNet/CollectionFile/Database/NoteRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ public NoteRepository(SqliteConnection connection) : base(connection)

protected override string TableName => "[notes]";

protected override string Columns =>
"[id], [guid], [mid], " +
"[mod], [usn], [tags], " +
"[flds], [sfld], [csum], " +
"[flags], [data]";
protected override IReadOnlyList<string> Columns { get; } =
[
"[id]", "[guid]", "[mid]",
"[mod]", "[usn]", "[tags]",
"[flds]", "[sfld]", "[csum]",
"[flags]", "[data]"
];

protected override string GetValues(note i)
protected override IReadOnlyList<object> GetValues(note i)
{
return
$"{i.id},'{i.guid}',{i.mid}," +
$"{i.mod},{i.usn},'{i.tags}'," +
$"'{i.flds}','{i.sfld}',{i.csum}," +
$"{i.flags},'{i.data}'";
return [
i.id, i.guid, i.mid,
i.mod, i.usn, i.tags,
i.flds, i.sfld, i.csum,
i.flags, i.data
];
}

protected override note Map(SqliteDataReader reader)
Expand Down
17 changes: 10 additions & 7 deletions src/AnkiNet/CollectionFile/Database/RevLogRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,18 @@ public RevLogRepository(SqliteConnection connection) : base(connection)

protected override string TableName => "[revlog]";

protected override string Columns =>
"[id], [cid], [usn], [ease], [ivl], " +
"[lastIvl], [factor], [time], [type]";
protected override IReadOnlyList<string> Columns { get; } =
[
"[id]", "[cid]", "[usn]", "[ease]", "[ivl]",
"[lastIvl]", "[factor]", "[time]", "[type]"
];

protected override string GetValues(revLog i)
protected override IReadOnlyList<object> GetValues(revLog i)
{
return
$"{i.id},{i.cid},{i.usn},{i.ease},{i.ivl}," +
$"{i.lastIvl},{i.factor},{i.time},{i.type}";
return [
i.id, i.cid, i.usn, i.ease, i.ivl,
i.lastIvl, i.factor, i.time, i.type
];
}

protected override revLog Map(SqliteDataReader reader)
Expand Down
53 changes: 38 additions & 15 deletions src/AnkiNet/CollectionFile/Database/SqliteRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ internal abstract class SqliteRepository<T>
private readonly SqliteConnection _connection;

protected abstract string TableName { get; }
protected abstract string Columns { get; }
protected abstract string GetValues(T item);
protected abstract IReadOnlyList<string> Columns { get; }

protected abstract IReadOnlyList<object> GetValues(T item);


protected abstract T Map(SqliteDataReader reader);

Expand All @@ -21,12 +23,12 @@ public async Task<List<T>> ReadAll()
{
var result = new List<T>();

var readAllSqlQuery = $"SELECT {Columns} FROM {TableName}";
var readAllSqlQuery = $"SELECT {string.Join(",", Columns)} FROM {TableName}";

try
{
using var command = new SqliteCommand(readAllSqlQuery, _connection);
using var reader = await command.ExecuteReaderAsync();
await using var command = new SqliteCommand(readAllSqlQuery, _connection);
await using var reader = await command.ExecuteReaderAsync();

while (await reader.ReadAsync())
{
Expand All @@ -42,29 +44,50 @@ public async Task<List<T>> ReadAll()
return result;
}

public async Task Add(List<T> items)
public async Task Add(IReadOnlyList<T> items)
{
if (!items.Any())
if (items.Count == 0)
{
return;
}

var writeSqlQuery = $@"
INSERT INTO {TableName}
({Columns})
VALUES ";
string writeSqlQuery;
{
var values = Enumerable.Range(0, items.Count).Select(itemIndex =>
{
var @params = Enumerable.Range(0, Columns.Count).Select(paramIndex => ParamName(itemIndex, paramIndex));

var values = items.Select(i => $"({GetValues(i)})");
writeSqlQuery += string.Join(',', values);
return $"({string.Join(',', @params)})";
});

writeSqlQuery = $"INSERT INTO {TableName} ({string.Join(",", Columns)}) VALUES {string.Join(',', values)}";
}

try
{
using var command = new SqliteCommand(writeSqlQuery, _connection);
var i = await command.ExecuteNonQueryAsync();
await using var command = new SqliteCommand(writeSqlQuery, _connection);

foreach (var (item, itemIndex) in items.Select((item, itemIndex) => (item, itemIndex)))
{
var itemValues = GetValues(item);
foreach (var (itemValue, paramIndex) in itemValues.Select((itemValue, paramIndex) => (itemValue, paramIndex)))
{
var paramName = ParamName(itemIndex, paramIndex);
command.Parameters.AddWithValue(paramName, itemValue);
}
}

var numberOfItemsInserted = await command.ExecuteNonQueryAsync();
}
catch (Exception e)
{
throw new IOException($"Cannot Add {typeof(T).Name}", e);
}

#region Helper function(s)

static string ParamName(int itemIndex, int paramIndex) => $"@p{itemIndex}_{paramIndex}";

#endregion
}
}
22 changes: 14 additions & 8 deletions src/AnkiNet/CollectionFile/Mapper/CollectionMapper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using AnkiNet.CollectionFile.Database.Model;
using AnkiNet.CollectionFile.Model;
using AnkiNet.CollectionFile.Model.Json;
Expand All @@ -7,12 +8,17 @@ namespace AnkiNet.CollectionFile.Mapper;

internal static class CollectionMapper
{
private static readonly JsonSerializerOptions SerializerOptions = new()
{
TypeInfoResolver = new DefaultJsonTypeInfoResolver(),
};

public static Collection FromDb(col col)
{
var configuration = JsonSerializer.Deserialize<JsonConfiguration>(col.conf);
var models = JsonSerializer.Deserialize<Dictionary<long, JsonModel>>(col.models);
var decks = JsonSerializer.Deserialize<Dictionary<long, JsonDeck>>(col.decks);
var decksConfiguration = JsonSerializer.Deserialize<Dictionary<long, JsonDeckConfguration>>(col.dconf);
var configuration = JsonSerializer.Deserialize<JsonConfiguration>(col.conf, SerializerOptions);
var models = JsonSerializer.Deserialize<Dictionary<long, JsonModel>>(col.models, SerializerOptions);
var decks = JsonSerializer.Deserialize<Dictionary<long, JsonDeck>>(col.decks, SerializerOptions);
var decksConfiguration = JsonSerializer.Deserialize<Dictionary<long, JsonDeckConfguration>>(col.dconf, SerializerOptions);

return new Collection(
col.id,
Expand All @@ -33,10 +39,10 @@ public static Collection FromDb(col col)

public static col ToDb(Collection collection)
{
var conf = JsonSerializer.Serialize(collection.Configuration);
var models = JsonSerializer.Serialize(collection.Models);
var decks = JsonSerializer.Serialize(collection.Decks);
var dconf = JsonSerializer.Serialize(collection.DecksConfiguration);
var conf = JsonSerializer.Serialize(collection.Configuration, SerializerOptions);
var models = JsonSerializer.Serialize(collection.Models, SerializerOptions);
var decks = JsonSerializer.Serialize(collection.Decks, SerializerOptions);
var dconf = JsonSerializer.Serialize(collection.DecksConfiguration, SerializerOptions);

return new col(
collection.Id,
Expand Down