Skip to content
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.DS_Store
.DS_Store
.vs
6 changes: 6 additions & 0 deletions sandbox/ConsoleApp1/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,17 @@ INSERT INTO user (id, name, age)
(3, 'Charlie', 25);
""");

connection.ExecuteNonQuery(stackalloc char[1024], $"""
INSERT INTO user (id, name, age)
VALUES ({4}, {"Darwin"u8.ToArray():text}, {80});
""");

using var reader = connection.ExecuteReader("""
SELECT name
FROM user
""");


while (reader.Read())
{
Console.WriteLine($"{reader.GetString(0)}!");
Expand Down
Binary file modified src/CsSqlite.Unity/Assets/CsSqlite.Unity/Runtime/CsSqlite.dll
Binary file not shown.
135 changes: 135 additions & 0 deletions src/CsSqlite/ExecuteInterporatedStringHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace CsSqlite;

[InterpolatedStringHandler]
public ref struct ExecuteInterporatedStringHandler
{
char[]? chars;
Span<char> commandText;
public readonly ReadOnlySpan<char> CommandText => commandText[..textWritten];
int textWritten;
SqliteParam[] parameters;
int parameterWritten;
public readonly ReadOnlySpan<SqliteParam> Parameters => parameters[..parameterWritten];

public ExecuteInterporatedStringHandler(int literalLength, int formattedCount)
{
if (literalLength > 0)
{
this.commandText = this.chars = ArrayPool<char>.Shared.Rent(literalLength + formattedCount * 3);
}

this.parameters = formattedCount > 0 ? ArrayPool<SqliteParam>.Shared.Rent(formattedCount) : [];
}

public ExecuteInterporatedStringHandler(int literalLength, int formattedCount, Span<char> commandText)
{
this.commandText = literalLength + formattedCount * 3 > commandText.Length ? this.chars = ArrayPool<char>.Shared.Rent(literalLength + formattedCount * 3) : commandText;
this.parameters = formattedCount > 0 ? ArrayPool<SqliteParam>.Shared.Rent(formattedCount) : [];
}

public void AppendLiteral(string s)
{
s.AsSpan().CopyTo(commandText[textWritten..]);
textWritten += s.Length;
}

public void AppendFormatted(string? s)
{
parameters[parameterWritten++] = new(SqlitePramKind.String, new(s?.AsMemory() ?? "".AsMemory()));
WriteParameterPlaceholder();
}

public void AppendFormatted(ReadOnlyMemory<char> s)
{
parameters[parameterWritten++] = new(SqlitePramKind.String, new(s));
WriteParameterPlaceholder();
}

public void AppendFormatted(ReadOnlyMemory<byte> s) => AppendFormatted(s, default);

public void AppendFormatted(ReadOnlyMemory<byte> s, ReadOnlySpan<char> format)
{
parameters[parameterWritten++] =
format.Length == 0 || format.SequenceEqual("text") ? new(SqlitePramKind.Utf8String, new(s)) :
format.SequenceEqual("blob") ? new(SqlitePramKind.Blob, new(s)) :
throw new ArgumentException($"The {nameof(format)} must be text or blob.", nameof(format));
WriteParameterPlaceholder();
}

public void AppendFormatted(long value)
{
parameters[parameterWritten++] = new(SqlitePramKind.Integer, new(value));
WriteParameterPlaceholder();
}

public void AppendFormatted(double value)
{
parameters[parameterWritten++] = new(SqlitePramKind.Double, new(value));
WriteParameterPlaceholder();
}

void WriteParameterPlaceholder()
{
commandText[textWritten++] = ' ';
commandText[textWritten++] = '?';
commandText[textWritten++] = ' ';
}

public void Dispose()
{
if (chars != null)
{
ArrayPool<char>.Shared.Return(chars);
chars = null;
}

if (parameters != null)
{
// Clear because SqliteParam can contain managed objects
ArrayPool<SqliteParam>.Shared.Return(parameters, true);
parameters = null!;
}
}
}

public readonly struct SqliteParam(SqlitePramKind kind, SqlitePramPayload payload)
{
public readonly SqlitePramKind Kind = kind;
public readonly SqlitePramPayload Payload = payload;

}

public enum SqlitePramKind : byte
{
Integer,
Double,
String,
Utf8String,
Blob,
}

[StructLayout(LayoutKind.Explicit)]
public readonly struct SqlitePramPayload
{
[FieldOffset(0)] readonly MemoryLike<long> memoryLikeLong;
public readonly long Long => memoryLikeLong.Long;
[FieldOffset(0)] readonly MemoryLike<double> memoryLikeDouble;
public readonly double Double => memoryLikeDouble.Long;
[FieldOffset(0)] public readonly ReadOnlyMemory<char> String;
[FieldOffset(0)] public readonly ReadOnlyMemory<byte> BlobOrUtf8String;

public SqlitePramPayload(long l) { this.memoryLikeLong = new(l); }
public SqlitePramPayload(double d) { this.memoryLikeDouble = new(d); }
public SqlitePramPayload(ReadOnlyMemory<char> t) { this.String = t; }
public SqlitePramPayload(ReadOnlyMemory<byte> b) { this.BlobOrUtf8String = b; }

private readonly struct MemoryLike<T>(T l)
{
public readonly T Long = l;
private readonly object? dummy = null;
}
}
11 changes: 11 additions & 0 deletions src/CsSqlite/InterpolatedStringHandlerArgumentAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace CsSqlite;

#if NETSTANDARD2_1
[AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
internal sealed class InterpolatedStringHandlerArgumentAttribute : Attribute
{
public InterpolatedStringHandlerArgumentAttribute(string argument) => Arguments = [argument];
public InterpolatedStringHandlerArgumentAttribute(params string[] arguments) => Arguments = arguments;
public string[] Arguments { get; }
}
#endif
6 changes: 6 additions & 0 deletions src/CsSqlite/InterpolatedStringHandlerAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace CsSqlite;

#if NETSTANDARD2_1
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]
internal sealed class InterpolatedStringHandlerAttribute : Attribute { }
#endif
11 changes: 11 additions & 0 deletions src/CsSqlite/SpliteParameters.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ public void Add(int index, long value)
BindParameter(index, value);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(int index, double value)
{
BindParameter(index, value);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(int index, ReadOnlySpan<char> text)
{
Expand Down Expand Up @@ -76,6 +82,11 @@ public void AddLiteral(int index,
BindText(index, utf8Text, true);
}

public void AddBytes(int index, ReadOnlySpan<byte> blob)
{
BindBlob(index, blob);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(ReadOnlySpan<char> name, int value)
{
Expand Down
75 changes: 74 additions & 1 deletion src/CsSqlite/SqliteConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ enum State : byte
public void Open()
{
ThrowIfDisposed();
if (state == State.Open) return;
if (state == State.Open)
return;

var buffer = ArrayPool<byte>.Shared.Rent(path.Length * 3);
try
Expand Down Expand Up @@ -111,6 +112,41 @@ public int ExecuteNonQuery(ReadOnlySpan<char> commandText)
return command.ExecuteNonQuery();
}

public int ExecuteNonQuery(ref ExecuteInterporatedStringHandler commandHandler)
{
using var command = CreateCommand(commandHandler.CommandText);
var handlerParameters = commandHandler.Parameters;
var commandParameters = command.Parameters;
for (int i = 0; i < handlerParameters.Length; i++)
{
var p = handlerParameters[i];
switch (p.Kind)
{
case SqlitePramKind.Integer:
commandParameters.Add(i + 1, p.Payload.Long);
break;
case SqlitePramKind.Double:
commandParameters.Add(i + 1, p.Payload.Double);
break;
case SqlitePramKind.String:
commandParameters.Add(i + 1, p.Payload.String.Span);
break;
case SqlitePramKind.Utf8String:
commandParameters.Add(i + 1, p.Payload.BlobOrUtf8String.Span);
break;
case SqlitePramKind.Blob:
commandParameters.AddBytes(i + 1, p.Payload.BlobOrUtf8String.Span);
break;
}
}
return command.ExecuteNonQuery();
}

public int ExecuteNonQuery(Span<char> commandText, [InterpolatedStringHandlerArgument("commandText")] ref ExecuteInterporatedStringHandler commandHandler)
{
return ExecuteNonQuery(ref commandHandler);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SqliteReader ExecuteReader(ReadOnlySpan<byte> utf8CommandText)
{
Expand All @@ -127,6 +163,43 @@ public SqliteReader ExecuteReader(ReadOnlySpan<char> commandText)
return new(this, Prepare(commandText), true);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SqliteReader ExecuteReader(ref ExecuteInterporatedStringHandler commandHandler)
{
using var command = CreateCommand(commandHandler.CommandText);
var handlerParameters = commandHandler.Parameters;
var commandParameters = command.Parameters;
for (int i = 0; i < handlerParameters.Length; i++)
{
var p = handlerParameters[i];
switch (p.Kind)
{
case SqlitePramKind.String:
commandParameters.Add(i + 1, p.Payload.String.Span);
break;
case SqlitePramKind.Utf8String:
commandParameters.Add(i + 1, p.Payload.BlobOrUtf8String.Span);
break;
case SqlitePramKind.Integer:
commandParameters.Add(i + 1, p.Payload.Long);
break;
case SqlitePramKind.Double:
commandParameters.Add(i + 1, p.Payload.Double);
break;
case SqlitePramKind.Blob:
commandParameters.AddBytes(i + 1, p.Payload.BlobOrUtf8String.Span);
break;
}
}
return command.ExecuteReader();
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SqliteReader ExecuteReader(Span<char> commandText, [InterpolatedStringHandlerArgument("commandText")] ref ExecuteInterporatedStringHandler commandHandler)
{
return ExecuteReader(ref commandHandler);
}

internal void ThrowIfDisposed()
{
if (IsDisposed)
Expand Down