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
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,11 @@ A collection of production-ready .NET utility libraries published as the **omy.U
- **omy.Utils.Reflection** – reflection extensions such as `PropertyOrFieldInfo` and dynamic delegate invocation.
- **omy.Utils.Xml** – attribute-driven XML processing and `XmlDataProcessor` helpers.
- **omy.Utils.VirtualMachine** – minimal VM framework with attribute-defined instructions and configurable endianness.
- **omy.Utils.OData** – OData client helpers and related generators.
- **omy.Utils.DependencyInjection** – dependency injection helpers plus source generators.
- **omy.Utils.OData** – OData client helpers and metadata utilities.
- **omy.Utils.OData.Generators** – Roslyn source generator for OData models from EDMX metadata.
- **omy.Utils.DependencyInjection** – attribute-based dependency injection helpers.
- **omy.Utils.DependencyInjection.Generators** – Roslyn source generator that emits DI registrations.
- **omy.Utils.IO.Serialization.Generators** – Roslyn source generator for stream serializers.

> Additional project-level READMEs provide deeper details for specialized packages like serialization or generators.

Expand Down
33 changes: 26 additions & 7 deletions Utils.Data/Sql/DeleteStatementParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,32 @@ public SqlDeleteStatement Parse(WithClause? withClause)
{
parser.ExpectKeyword("DELETE");

var segments = this.ReadSegments(
DeleteReader,
FromReader,
OutputReader,
WhereReader,
ReturningReader
);
var segments = new Dictionary<ClauseStart, SqlSegment>
{
[DeleteReader.Clause] = DeleteReader.TryRead(
parser,
FromReader.Clause,
UsingReader.Clause,
OutputReader.Clause,
WhereReader.Clause,
ReturningReader.Clause,
ClauseStart.StatementEnd),
};

ReadSegments(
segments,
FromReader,
UsingReader,
OutputReader,
WhereReader,
ReturningReader);

if (segments.GetValueOrDefault(DeleteReader.Clause) == null
&& segments.GetValueOrDefault(OutputReader.Clause) != null
&& segments.GetValueOrDefault(FromReader.Clause) is SqlSegment fromSegment)
{
segments[DeleteReader.Clause] = new SqlSegment("Target", fromSegment.Parts, parser.SyntaxOptions);
}

return new SqlDeleteStatement(
segments.GetValueOrDefault(DeleteReader.Clause),
Expand Down
26 changes: 22 additions & 4 deletions Utils.Data/Sql/InsertStatementParser.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Utils.Collections;

namespace Utils.Data.Sql;
Expand Down Expand Up @@ -29,9 +30,26 @@ public SqlInsertStatement Parse(WithClause? withClause)
parser.ExpectKeyword("INTO");
}

var segments = base.ReadSegments(
IntoReader,
OutputReader);
var segments = new Dictionary<ClauseStart, SqlSegment>
{
[IntoReader.Clause] = IntoReader.TryRead(
parser,
OutputReader.Clause,
ValuesReader.Clause,
ClauseStart.Select,
ReturningReader.Clause,
ClauseStart.StatementEnd),
};

if (parser.CheckKeyword("OUTPUT"))
{
segments[OutputReader.Clause] = OutputReader.TryRead(
parser,
ValuesReader.Clause,
ClauseStart.Select,
ReturningReader.Clause,
ClauseStart.StatementEnd);
}

if (parser.CheckKeyword("VALUES"))
{
Expand All @@ -48,7 +66,7 @@ public SqlInsertStatement Parse(WithClause? withClause)
withClause
);
}
else if (parser.CheckKeyword("SELECT"))
else if (parser.CheckKeyword("SELECT") || parser.CheckKeyword("WITH"))
{
var sourceQuery = parser.ParseStatement();
ReadSegments(
Expand Down
38 changes: 27 additions & 11 deletions Utils.Data/Sql/SelectStatementParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,25 @@ public SqlSelectStatement Parse(WithClause? withClause)
parser.ExpectKeyword("SELECT");
bool isDistinct = parser.TryConsumeKeyword("DISTINCT");

var segments = this.ReadSegments(
SelectReader,
FromReader,
var segments = new Dictionary<ClauseStart, SqlSegment>
{
[SelectReader.Clause] = SelectReader.TryRead(
parser,
FromReader.Clause,
WhereReader.Clause,
GroupByReader.Clause,
HavingReader.Clause,
OrderByReader.Clause,
LimitReader.Clause,
OffsetReader.Clause,
ReturningReader.Clause,
SetOperatorReader.Clause,
ClauseStart.StatementEnd),
};

ReadSegments(
segments,
FromReader,
WhereReader,
GroupByReader,
HavingReader,
Expand All @@ -42,14 +58,14 @@ public SqlSelectStatement Parse(WithClause? withClause)

return new SqlSelectStatement(
segments.GetValueOrDefault(SelectReader.Clause),
segments.GetValueOrDefault(FromReader.Clause),
segments.GetValueOrDefault(WhereReader.Clause),
segments.GetValueOrDefault(GroupByReader.Clause),
segments.GetValueOrDefault(HavingReader.Clause),
segments.GetValueOrDefault(OrderByReader.Clause),
segments.GetValueOrDefault(LimitReader.Clause),
segments.GetValueOrDefault(OffsetReader.Clause),
segments.GetValueOrDefault(SetOperatorReader.Clause),
segments.GetValueOrDefault(FromReader.Clause),
segments.GetValueOrDefault(WhereReader.Clause),
segments.GetValueOrDefault(GroupByReader.Clause),
segments.GetValueOrDefault(HavingReader.Clause),
segments.GetValueOrDefault(OrderByReader.Clause),
segments.GetValueOrDefault(LimitReader.Clause),
segments.GetValueOrDefault(OffsetReader.Clause),
segments.GetValueOrDefault(SetOperatorReader.Clause),
withClause,
isDistinct);
}
Expand Down
12 changes: 10 additions & 2 deletions Utils.Data/Sql/SqlQueryAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,14 @@ public static class SqlQueryAnalyzer
public static SqlQuery Parse(string sql, SqlSyntaxOptions? syntaxOptions = null)
{
ArgumentException.ThrowIfNullOrWhiteSpace(sql);
string trimmedSql = sql.Trim();
if (trimmedSql.Length > 1
&& trimmedSql[0] == '"'
&& trimmedSql[^1] == '"'
&& trimmedSql.Count(c => c == '"') == 2)
{
sql = trimmedSql[1..^1];
}
syntaxOptions ??= SqlSyntaxOptions.Default;
var parser = SqlParser.Create(sql, syntaxOptions);
SqlStatement statement = parser.ParseStatement();
Expand Down Expand Up @@ -1110,7 +1118,7 @@ public SqlDeleteStatement(SqlSegment? target, SqlSegment from, SqlSegment? @usin
wherePart = where == null ? null : new WherePart(where);
partReferenceBindings =
[
new PartReferenceBinding<DeletePart>(DeletePartReader.Singleton, part => deletePart ??= part),
new PartReferenceBinding<DeletePart>("Target", DeletePartReader.Singleton.PartFactory, part => deletePart ??= part),
new PartReferenceBinding<WherePart>(WherePartReader.Singleton, part => wherePart ??= part),
];
}
Expand Down Expand Up @@ -1201,7 +1209,7 @@ protected override string BuildSql()
}

builder.Append("DELETE");
if (Target != null)
if (Target != null && !string.Equals(Target.ToSql(), From.ToSql(), StringComparison.Ordinal))
{
builder.Append(' ');
builder.Append(Target.ToSql());
Expand Down
15 changes: 15 additions & 0 deletions Utils.Data/Sql/SqlStatementPartReaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,7 @@ private WherePartReader() { }
/// <returns>The parsed WHERE predicate when found; otherwise, <c>null</c>.</returns>
public SqlSegment? TryRead(SqlParser parser, params IEnumerable<ClauseStart> clauseTerminators)
{
parser.TryConsumeSegmentKeyword("WHERE", out _);
var predicateReader = new PredicateReader(parser);
return predicateReader.ReadPredicate("Where", [..clauseTerminators]);
}
Expand Down Expand Up @@ -354,6 +355,7 @@ private GroupByPartReader() { }
/// <returns>The parsed GROUP BY segment when found; otherwise, <c>null</c>.</returns>
public SqlSegment? TryRead(SqlParser parser, params IEnumerable<ClauseStart> clauseTerminators)
{
parser.TryConsumeSegmentKeyword("GROUP BY", out _);
var expressionListReader = new ExpressionListReader(parser);
var expressions = expressionListReader.ReadExpressions("GroupByExpr", false, [.. clauseTerminators]);

Expand Down Expand Up @@ -414,6 +416,7 @@ private HavingPartReader() { }
/// <returns>The parsed HAVING segment when found; otherwise, <c>null</c>.</returns>
public SqlSegment? TryRead(SqlParser parser, params IEnumerable<ClauseStart> clauseTerminators)
{
parser.TryConsumeSegmentKeyword("HAVING", out _);
var predicateReader = new PredicateReader(parser);
return predicateReader.ReadPredicate("Having", [.. clauseTerminators]);
}
Expand Down Expand Up @@ -456,6 +459,7 @@ private OrderByPartReader() { }
/// <returns>The parsed ORDER BY segment when found; otherwise, <c>null</c>.</returns>
public SqlSegment? TryRead(SqlParser parser, params IEnumerable<ClauseStart> clauseTerminators)
{
parser.TryConsumeSegmentKeyword("ORDER BY", out _);
var expressionListReader = new ExpressionListReader(parser);
var expressions = expressionListReader.ReadExpressions(
"OrderByExpr",
Expand Down Expand Up @@ -519,6 +523,7 @@ private LimitPartReader() { }
/// <returns>The parsed LIMIT segment when found; otherwise, <c>null</c>.</returns>
public SqlSegment? TryRead(SqlParser parser, params IEnumerable<ClauseStart> clauseTerminators)
{
parser.TryConsumeSegmentKeyword("LIMIT", out _);
var tokens = parser.ReadSectionTokens([..clauseTerminators]);
return parser.BuildSegment("Limit", tokens);
}
Expand Down Expand Up @@ -561,6 +566,7 @@ private OffsetPartReader() { }
/// <returns>The parsed OFFSET segment when found; otherwise, <c>null</c>.</returns>
public SqlSegment? TryRead(SqlParser parser, params IEnumerable<ClauseStart> clauseTerminators)
{
parser.TryConsumeSegmentKeyword("OFFSET", out _);
var tokens = parser.ReadSectionTokens(
[..clauseTerminators]);
return parser.BuildSegment("Offset", tokens);
Expand Down Expand Up @@ -601,6 +607,7 @@ public ValuesPartReader() { }
/// <returns>The parsed VALUES segment.</returns>
public SqlSegment? TryRead(SqlParser parser, params IEnumerable<ClauseStart> clauseTerminators)
{
parser.TryConsumeSegmentKeyword("VALUES", out _);
var tokens = parser.ReadSectionTokens([..clauseTerminators]);
return parser.BuildSegment("Values", tokens);
}
Expand Down Expand Up @@ -639,6 +646,7 @@ private OutputPartReader() { }
/// <returns>The parsed OUTPUT segment.</returns>
public SqlSegment? TryRead(SqlParser parser, params IEnumerable<ClauseStart> clauseTerminators)
{
parser.TryConsumeSegmentKeyword("OUTPUT", out _);
var expressionListReader = new ExpressionListReader(parser);
var expressions = expressionListReader.ReadExpressions("OutputExpr", true, [..clauseTerminators]);
return BuildExpressionListSegment(parser, "Output", expressions);
Expand Down Expand Up @@ -695,6 +703,7 @@ private ReturningPartReader() { }
/// <returns>The parsed RETURNING segment.</returns>
public SqlSegment? TryRead(SqlParser parser, params IEnumerable<ClauseStart> clauseTerminators)
{
parser.TryConsumeSegmentKeyword("RETURNING", out _);
var tokens = parser.ReadSectionTokens([..clauseTerminators]);
return parser.BuildSegment("Returning", tokens);
}
Expand Down Expand Up @@ -801,6 +810,11 @@ private DeletePartReader() { }
tokens.Add(parser.Read());
}

if (tokens.Count == 0)
{
return null;
}

return parser.BuildSegment("Target", tokens);
}
}
Expand Down Expand Up @@ -876,6 +890,7 @@ private SetPartReader() { }
/// <returns>The parsed SET segment.</returns>
public SqlSegment? TryRead(SqlParser parser, params IEnumerable<ClauseStart> clauseTerminators)
{
parser.TryConsumeSegmentKeyword("SET", out _);
var tokens = parser.ReadSectionTokens([..clauseTerminators]);
return parser.BuildSegment("Set", tokens);
}
Expand Down
5 changes: 3 additions & 2 deletions Utils.Data/Sql/StatementParserBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ abstract internal class StatementParserBase
protected readonly SqlParser parser;

public static IPartReader SelectReader = SelectPartReader.Singleton;
public static IPartReader SpdateTargetReader => UpdatePartReader.Singleton;
public static IPartReader UpdateTargetReader => UpdatePartReader.Singleton;
public static IPartReader DeleteReader => DeletePartReader.Singleton;
public static IPartReader FromReader => FromPartReader.Singleton;
public static IPartReader UsingReader => UsingPartReader.Singleton;
Expand Down Expand Up @@ -54,7 +54,8 @@ protected Dictionary<ClauseStart, SqlSegment> ReadSegments(Dictionary<ClauseStar
foreach (var keyWordSequence in reader.KeywordSequences)
{
if (!parser.CheckKeywordSequence(keyWordSequence)) continue;
segment = reader.TryRead(parser, readersQueue.Select(r => r.Clause));
var clauseTerminators = readersQueue.Select(r => r.Clause).Append(ClauseStart.StatementEnd);
segment = reader.TryRead(parser, clauseTerminators);
}
}
segments[reader.Clause] = segment;
Expand Down
25 changes: 18 additions & 7 deletions Utils.Data/Sql/UpdateStatementParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,33 @@ public SqlUpdateStatement Parse(WithClause? withClause)
{
parser.ExpectKeyword("UPDATE");

var segments = base.ReadSegments(
SpdateTargetReader,
var segments = new Dictionary<ClauseStart, SqlSegment>
{
[UpdateTargetReader.Clause] = UpdateTargetReader.TryRead(
parser,
SetReader.Clause,
OutputReader.Clause,
FromReader.Clause,
WhereReader.Clause,
ReturningReader.Clause,
ClauseStart.StatementEnd),
};

ReadSegments(
segments,
SetReader,
OutputReader,
FromReader,
WhereReader,
ReturningReader
);
ReturningReader);

return new SqlUpdateStatement(
segments.GetValueOrDefault(SpdateTargetReader.Clause),
segments.GetValueOrDefault(UpdateTargetReader.Clause),
segments.GetValueOrDefault(SetReader.Clause),
segments.GetValueOrDefault(FromReader.Clause),
segments.GetValueOrDefault(WhereReader.Clause),
segments.GetValueOrDefault(OutputReader.Clause),
segments.GetValueOrDefault(ReturningReader.Clause),
segments.GetValueOrDefault(OutputReader.Clause),
segments.GetValueOrDefault(ReturningReader.Clause),
withClause);
}
}
17 changes: 16 additions & 1 deletion Utils.Net/Net/NntpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,22 @@ protected override async Task OnConnect(Stream stream, bool leaveOpen, Cancellat
await EnsureCompletionAsync(greeting);
}

/// <summary>
/// Parses NNTP response lines, treating non-numeric data lines as preliminary responses.
/// </summary>
/// <param name="line">Response line from the server.</param>
/// <returns>The parsed response.</returns>
protected override ServerResponse ParseResponseLine(string line)
{
ServerResponse response = base.ParseResponseLine(line);
if (response.Severity == ResponseSeverity.Unknown && response.Code == line)
{
return new ServerResponse(response.Code, ResponseSeverity.Preliminary, response.Message);
}

return response;
}

/// <summary>
/// Selects the specified newsgroup.
/// </summary>
Expand Down Expand Up @@ -307,4 +323,3 @@ private async Task<IReadOnlyList<string>> ReadMultilineAsync(CancellationToken c
}
}
}

Loading
Loading