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
22 changes: 0 additions & 22 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,6 @@ jobs:
build:

runs-on: ubuntu-latest
env:
POSTGRES_PORT: 5432
POSTGRES_USERNAME: postgres
POSTGRES_PASSWORD: password
POSTGRES_DATABASE: postgres
services:
postgres:
image: postgres
env:
POSTGRES_PASSWORD: ${{ env.POSTGRES_PASSWORD }}
ports:
- 5432:5432
# Set health checks to wait until postgres has started
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5

steps:
- uses: actions/checkout@v4
Expand All @@ -38,7 +20,3 @@ jobs:
- name: Run Tests
run: |
dotnet test
env:
PG_USERNAME: ${{ env.POSTGRES_USERNAME }}
PG_PASSWORD: ${{ env.POSTGRES_PASSWORD }}
PG_DATABASE: ${{ env.POSTGRES_DATABASE }}
15 changes: 11 additions & 4 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ struct Row : IFromRow<DbRow, Row>
{
return new Row
{
Id = dataRow.GetIntNotNull("id"),
Name = dataRow.GetStringNotNull("name"),
ChildId = dataRow.GetInt("child_id"),
Id = dataRow.GetField<int>("id"),
Name = dataRow.GetField<string>("name"),
ChildId = dataRow.GetField<int?>("child_id"),
};
}
}
Expand Down Expand Up @@ -57,7 +57,7 @@ struct Param : IBindMany<DbBindable>
await using var connection = pool.CreateConnection();
using var query = connection.CreateQuery(parameterizedQuery);
// Binds to first parameter present in query
query.Bind(1);
query.Bind(1);
var rows = await query.FetchAllAsync<Row>();

// With no query and param type
Expand Down Expand Up @@ -169,6 +169,13 @@ would be an easier option.
SSL connections are not implemented.... yet!

### FAQs
#### Did AI write any of this code?
No, but that doesn't mean it's better than code written with AI. I started this project before LLM
based AI was more accessible but even when I did get easier access to such tools, I decided to keep
it out of this project. That is because I found this more enjoyable and truly wanted to make sure I
understood what the code was doing at every step. I use AI for personal projects that don't need
quite as much scrutiny and at work where it does help my productivity which means something to me.

#### Is there support for non-async/blocking connections?
No. Database interactions generally involve some sort of IO so it would be best to put that into an
async operation. However, if there is enough desire to use this library for blocking connections
Expand Down
8 changes: 4 additions & 4 deletions sqlx-cs-core-test/Types/JsonHelperTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public async Task WriteToBuffer_Should_WriteJsonValueToBuffer_When_SourceGenerat
{
using var buffer = new ArrayBufferWriter();

JsonHelper.WriteToBuffer(buffer, _jsonType, SourceGenerationContext.Default.JsonType);
JsonHelper.WriteToBuffer(buffer, _jsonType, SourceGenerationContext.Default.Options);

await Assert.That(Encoding.UTF8.GetString(buffer.ReadableSpan)).IsEqualTo(JsonTypeStr);
}
Expand All @@ -31,7 +31,7 @@ public async Task WriteToBuffer_Should_WriteJsonValueToBuffer_When_ReflectionBas
{
using var buffer = new ArrayBufferWriter();

JsonHelper.WriteToBuffer(buffer, _jsonType, null);
JsonHelper.WriteToBuffer(buffer, _jsonType);

await Assert.That(Encoding.UTF8.GetString(buffer.ReadableSpan)).IsEqualTo(JsonTypeStr);
}
Expand All @@ -41,7 +41,7 @@ public async Task FromBytes_Should_ReadJsonValueFromBytes_When_SourceGeneratedTy
{
var bytes = Encoding.UTF8.GetBytes(JsonTypeStr);

var result = JsonHelper.FromBytes<JsonType>(bytes, SourceGenerationContext.Default.JsonType);
var result = JsonHelper.FromBytes<JsonType>(bytes, SourceGenerationContext.Default.Options);

await Assert.That(result).IsEqualTo(_jsonType);
}
Expand All @@ -61,7 +61,7 @@ public async Task FromChars_Should_ReadJsonValueFromBytes_When_SourceGeneratedTy
{
var result = JsonHelper.FromChars<JsonType>(
JsonTypeStr,
SourceGenerationContext.Default.JsonType);
SourceGenerationContext.Default.Options);

await Assert.That(result).IsEqualTo(_jsonType);
}
Expand Down
43 changes: 0 additions & 43 deletions sqlx-cs-core/Query/Bindable.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization.Metadata;

namespace Sqlx.Core.Query;

Expand All @@ -23,47 +22,5 @@ public void BindOutParameter<T>() where T : notnull
{
bindable.BindNull<T>();
}

/// <summary>
/// Bind a <typeparamref name="T"/> value as a JSON. Some databases have a JSON specific
/// field type but other database drivers will treat the JSON encoding as string or bytes.
/// When using this method, it's recommended to supply the <see cref="JsonTypeInfo"/>
/// parameter to aid serialization.
/// </summary>
/// <param name="value">Value to encode as JSON</param>
/// <param name="typeInfo">Optional type metadata for JSON serialization</param>
/// <typeparam name="T">CLR type to encode as JSON</typeparam>
public void BindJsonRef<T>(T? value, JsonTypeInfo<T>? typeInfo = null) where T : class
{
if (value is null)
{
bindable.BindNull<T>();
}
else
{
bindable.BindJson(value, typeInfo);
}
}

/// <summary>
/// Bind a <typeparamref name="T"/> value as a JSON. Some databases have a JSON specific
/// field type but other database drivers will treat the JSON encoding as string or bytes.
/// When using this method, it's recommended to supply the <see cref="JsonTypeInfo"/>
/// parameter to aid serialization.
/// </summary>
/// <param name="value">Value to encode as JSON</param>
/// <param name="typeInfo">Optional type metadata for JSON serialization</param>
/// <typeparam name="T">CLR type to encode as JSON</typeparam>
public void BindJsonVal<T>(T? value, JsonTypeInfo<T>? typeInfo = null) where T : struct
{
if (!value.HasValue)
{
bindable.BindNull<T>();
}
else
{
bindable.BindJson(value.Value, typeInfo);
}
}
}
}
15 changes: 0 additions & 15 deletions sqlx-cs-core/Query/IBindable.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
using System.Text.Json.Serialization.Metadata;

namespace Sqlx.Core.Query;

/// <summary>
Expand All @@ -10,19 +8,6 @@ namespace Sqlx.Core.Query;
/// </summary>
public interface IBindable : IDisposable
{
/// <summary>
/// Bind a <typeparamref name="T"/> value as a JSON. Some databases have a JSON specific field
/// type but other database drivers will treat the JSON encoding as string or bytes. When using
/// this method, it's recommended to supply the <see cref="JsonTypeInfo"/> parameter to aid
/// serialization. If you need to bind a possibly null value, use
/// <see cref="Bindable.BindJsonRef"/> and <see cref="Bindable.BindJsonVal"/> for class and
/// struct types respectively.
/// </summary>
/// <param name="value">Value to encode as JSON</param>
/// <param name="typeInfo">Optional type metadata for JSON serialization</param>
/// <typeparam name="T">CLR type to encode as JSON</typeparam>
void BindJson<T>(T value, JsonTypeInfo<T>? typeInfo = null) where T : notnull;

/// <summary>
/// Bind a null value to the query
/// </summary>
Expand Down
69 changes: 0 additions & 69 deletions sqlx-cs-core/Result/IDataRow.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Runtime.CompilerServices;
using System.Text.Json.Serialization.Metadata;
using Sqlx.Core.Column;

namespace Sqlx.Core.Result;
Expand Down Expand Up @@ -35,18 +34,6 @@ public interface IDataRow
/// <param name="index">0-based index of the column to check</param>
/// <returns>True if the value found at the index is a DB null</returns>
bool IsNull(int index);

/// <summary>
/// Extract a not-null <typeparamref name="T"/> value after deserializing the inner value
/// as JSON. Some databases have a JSON specific field type but other database drivers will
/// treat string or byte[] fields as JSON compatible. When using this method, it's recommended
/// to supply the <see cref="JsonTypeInfo{T}"/> parameter to aid deserialization.
/// </summary>
/// <param name="index">0-based index of the column to extract</param>
/// <param name="jsonTypeInfo">optional JSON source generated deserialization metadata</param>
/// <returns><c>T</c> value at the specified column</returns>
/// <exception cref="Sqlx.Core.Exceptions.SqlxException">if the column value is null</exception>
T GetJsonNotNull<T>(int index, JsonTypeInfo<T>? jsonTypeInfo = null) where T : notnull;
}

public static class DataRowExtensions
Expand All @@ -63,61 +50,5 @@ public IColumnMetadata GetColumnMetadata(string name)
{
return dataRow.GetColumnMetadata(dataRow.IndexOf(name));
}

/// <summary>
/// Extract a possibly null <typeparamref name="T"/> value after deserializing the inner value
/// as JSON. Some databases have a JSON specific field type but other database drivers will
/// treat string or byte[] fields as JSON compatible. When using this method, it's recommended
/// to supply the <see cref="JsonTypeInfo{T}"/> parameter to aid deserialization.
/// </summary>
/// <param name="index">0-based index of the column to extract</param>
/// <param name="jsonTypeInfo">optional JSON source generated deserialization metadata</param>
/// <returns>
/// <c>T</c> value at the specified column or default if the DB value was null
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T? GetJson<T>(
int index,
JsonTypeInfo<T>? jsonTypeInfo = null)
where T : notnull
{
return dataRow.IsNull(index) ? default : dataRow.GetJsonNotNull(index, jsonTypeInfo);
}

/// <summary>
/// Extract a possibly null <typeparamref name="T"/> value after deserializing the inner value
/// as JSON. Some databases have a JSON specific field type but other database drivers will
/// treat string or byte[] fields as JSON compatible. When using this method, it's recommended
/// to supply the <see cref="JsonTypeInfo{T}"/> parameter to aid deserialization.
/// </summary>
/// <param name="name">name of the column to extract</param>
/// <param name="jsonTypeInfo">optional JSON source generated deserialization metadata</param>
/// <returns><c>T</c> value at the specified column or null if the DB value was null</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T? GetJson<T>(
string name,
JsonTypeInfo<T>? jsonTypeInfo = null)
where T : notnull
{
return dataRow.GetJson(dataRow.IndexOf(name), jsonTypeInfo);
}

/// <summary>
/// Extract a not-null <typeparamref name="T"/> value after deserializing the inner value
/// as JSON. Some databases have a JSON specific field type but other database drivers will
/// treat string or byte[] fields as JSON compatible. When using this method, it's recommended
/// to supply the <see cref="JsonTypeInfo{T}"/> parameter to aid deserialization.
/// </summary>
/// <param name="name">name of the column to extract</param>
/// <param name="jsonTypeInfo">optional JSON source generated deserialization metadata</param>
/// <returns><c>T</c> value at the specified column</returns>
/// <exception cref="Sqlx.Core.Exceptions.SqlxException">if the column value is null</exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public T GetJsonNotNull<T>(
string name,
JsonTypeInfo<T>? jsonTypeInfo = null) where T : notnull
{
return dataRow.GetJsonNotNull(dataRow.IndexOf(name), jsonTypeInfo);
}
}
}
33 changes: 13 additions & 20 deletions sqlx-cs-core/Types/JsonHelper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using System.Buffers;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;

namespace Sqlx.Core.Types;

Expand All @@ -13,21 +12,15 @@ public static class JsonHelper
/// </summary>
/// <param name="buffer">buffer to write the JSON value to</param>
/// <param name="value">object to write as JSON</param>
/// <param name="typeInfo">optional type info</param>
/// <param name="serializerOptions">optional serializer options that contain type info</param>
/// <typeparam name="T">value type to be serialized</typeparam>
public static void WriteToBuffer<T>(
IBufferWriter<byte> buffer,
T value,
JsonTypeInfo<T>? typeInfo) where T : notnull
JsonSerializerOptions? serializerOptions = null) where T : notnull
{
using var writer = new Utf8JsonWriter(buffer);
if (typeInfo is not null)
{
JsonSerializer.Serialize(writer, value, typeInfo);
return;
}

JsonSerializer.Serialize(writer, value);
JsonSerializer.Serialize(writer, value, serializerOptions);
}

/// <summary>
Expand All @@ -36,16 +29,16 @@ public static void WriteToBuffer<T>(
/// code to deserialize the value from JSON bytes.
/// </summary>
/// <param name="bytes">JSON value as UTF-8 bytes</param>
/// <param name="typeInfo">optional type info</param>
/// <param name="serializerOptions">optional serializer options that contain type info</param>
/// <typeparam name="T">output type to deserialize to</typeparam>
/// <returns>a new instance of <typeparamref name="T"/> from deserializing JSON</returns>
/// <exception cref="ArgumentException">if the JSON deserializing return null</exception>
public static T FromBytes<T>(ReadOnlySpan<byte> bytes, JsonTypeInfo<T>? typeInfo = null)
public static T FromBytes<T>(
ReadOnlySpan<byte> bytes,
JsonSerializerOptions? serializerOptions = null)
where T : notnull
{
T? result = typeInfo is null
? JsonSerializer.Deserialize<T>(bytes)
: JsonSerializer.Deserialize(bytes, typeInfo);
var result = JsonSerializer.Deserialize<T>(bytes, serializerOptions);
if (result is null)
{
throw new ArgumentException(
Expand All @@ -62,16 +55,16 @@ public static T FromBytes<T>(ReadOnlySpan<byte> bytes, JsonTypeInfo<T>? typeInfo
/// code to deserialize the value from JSON chars.
/// </summary>
/// <param name="chars">JSON value as UTF-8 character</param>
/// <param name="typeInfo">optional type info</param>
/// <param name="serializerOptions">optional serializer options that contain type info</param>
/// <typeparam name="T">output type to deserialize to</typeparam>
/// <returns>a new instance of <typeparamref name="T"/> from deserializing JSON</returns>
/// <exception cref="ArgumentException">if the JSON deserializing return null</exception>
public static T FromChars<T>(ReadOnlySpan<char> chars, JsonTypeInfo<T>? typeInfo = null)
public static T FromChars<T>(
ReadOnlySpan<char> chars,
JsonSerializerOptions? serializerOptions = null)
where T : notnull
{
T? result = typeInfo is null
? JsonSerializer.Deserialize<T>(chars)
: JsonSerializer.Deserialize(chars, typeInfo);
var result = JsonSerializer.Deserialize<T>(chars, serializerOptions);
if (result is null)
{
throw new ArgumentException(
Expand Down
21 changes: 21 additions & 0 deletions sqlx-cs-core/Types/JsonValue.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
namespace Sqlx.Core.Types;

/// <summary>
/// Wrapper for a JSON value. Used to tell the compiler that the database value should be treated
/// as JSON. This means that:
/// <list type="bullet">
/// <item>
/// when binding this value, the driver will serialize to JSON and forward that result to
/// the database
/// </item>
/// <item>
/// when fetching this value, the driver will deserialize the field value into the type
/// <typeparamref name="T"/> and pack that as the <see cref="Inner"/> value.
/// </item>
/// </list>
/// </summary>
/// <typeparam name="T">Internal type to treat as JSON</typeparam>
public readonly record struct JsonValue<T> where T : notnull
{
public required T Inner { get; init; }
}
Loading
Loading