Skip to content

Commit

Permalink
Generate db schemas from solidity event definitions
Browse files Browse the repository at this point in the history
  • Loading branch information
jaensen committed May 9, 2024
1 parent a588f3f commit 17ce26d
Show file tree
Hide file tree
Showing 28 changed files with 775 additions and 731 deletions.
3 changes: 0 additions & 3 deletions Circles.Index.Common/BlockEvent.cs

This file was deleted.

25 changes: 14 additions & 11 deletions Circles.Index.Common/DatabaseSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ namespace Circles.Index.Common;

public class DatabaseSchema : IDatabaseSchema
{
public IDictionary<string, EventSchema> Tables { get; } = new Dictionary<string, EventSchema>
{
public ISchemaPropertyMap SchemaPropertyMap { get; } = new SchemaPropertyMap();
public IEventDtoTableMap EventDtoTableMap { get; } = new EventDtoTableMap();

public IDictionary<(string Namespace, string Table), EventSchema> Tables { get; } =
new Dictionary<(string Namespace, string Table), EventSchema>
{
"Block",
// Hash256 must be 32 bytes and was 0 bytes
new EventSchema("Block", new Hash256(new byte[32]), [
new("BlockNumber", ValueTypes.Int, true),
new("Timestamp", ValueTypes.Int, true),
new("BlockHash", ValueTypes.String, true)
])
}
};
{
("System", "Block"),
new EventSchema("System", "Block", new Hash256(new byte[32]), [
new("blockNumber", ValueTypes.Int, false),
new("timestamp", ValueTypes.Int, true),
new("blockHash", ValueTypes.String, false)
])
}
};
}
4 changes: 2 additions & 2 deletions Circles.Index.Common/Equals.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ namespace Circles.Index.Common;

public class Equals : QueryPredicate
{
internal Equals(IDatabase database, string table, string column, object? value)
internal Equals(IDatabase database, (string Namespace, string Table) table, string column, object? value)
: base(database, table, column, value)
{
}

public override string ToSql() => $"{Field} = {ParameterName}";
public override string ToSql() => $"\"{Field}\" = {ParameterName}";
}
50 changes: 34 additions & 16 deletions Circles.Index.Common/EventSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ namespace Circles.Index.Common;

public record EventFieldSchema(string Column, ValueTypes Type, bool IsIndexed);

public class EventSchema(string table, Hash256 topic, List<EventFieldSchema> columns)
public class EventSchema(string @namespace, string table, Hash256 topic, List<EventFieldSchema> columns)
{
public string Namespace { get; } = @namespace;
public Hash256 Topic { get; } = topic;
public string Table { get; } = table;
public List<EventFieldSchema> Columns { get; } = columns;
Expand All @@ -21,13 +22,14 @@ public class EventSchema(string table, Hash256 topic, List<EventFieldSchema> col
/// );
/// ```
/// </summary>
/// <param name="namespace"></param>
/// <param name="solidityEventDefinition"></param>
/// <returns></returns>
/// <exception cref="ArgumentException">Thrown when the event definition is invalid (according to this parser ;).</exception>
/// <remarks>
/// Doesn't support all Solidity types yet (most notably arrays). Please handle events with these types manually.
/// </remarks>
public static EventSchema FromSolidity(string solidityEventDefinition)
public static EventSchema FromSolidity(string @namespace, string solidityEventDefinition)
{
var trimmedDefinition = solidityEventDefinition.Trim();
const string prefix = "event ";
Expand Down Expand Up @@ -55,49 +57,64 @@ public static EventSchema FromSolidity(string solidityEventDefinition)
.Substring(openParenthesisIndex + 1, closeParenthesisIndex - openParenthesisIndex - 1).Trim();

var columnDefinitions = parameters.Split(',');
var columns = new List<EventFieldSchema>();
var columns = new List<EventFieldSchema>
{
new("blockNumber", ValueTypes.Int, false),
new("timestamp", ValueTypes.Int, true),
new("transactionIndex", ValueTypes.Int, false),
new("logIndex", ValueTypes.Int, false),
new("transactionHash", ValueTypes.String, true)
};

StringBuilder sb = new StringBuilder();
sb.Append(eventName);
sb.Append("(");
StringBuilder eventTopic = new StringBuilder();
eventTopic.Append(eventName);
eventTopic.Append('(');

foreach (var columnDefinition in columnDefinitions)
for (int i = 0; i < columnDefinitions.Length; i++)
{
var columnDefinition = columnDefinitions[i];
var parts = columnDefinition.Trim().Split(' ');
if (parts.Length < 2)
{
throw new ArgumentException(
$"Invalid column definition '${columnDefinition}'. Must contain a type and a name.");
}

if (i > 0)
{
eventTopic.Append(',');
}

if (parts.Length == 2)
{
var type = MapSolidityType(parts[0].Trim());
eventTopic.Append(parts[0].Trim());

var columnName = parts[1].Trim();
columns.Add(new EventFieldSchema(columnName, type, false));
sb.Append($"{type} {columnName}");
}

if (parts.Length == 3)
{
var isIndexed = parts[0].Trim() == "indexed";
var type = MapSolidityType(parts[0].Trim());
eventTopic.Append(parts[0].Trim());

var isIndexed = parts[1].Trim() == "indexed";
if (!isIndexed)
{
throw new ArgumentException(
$"Invalid column definition '${columnDefinition}'.");
}

var type = MapSolidityType(parts[1].Trim());
var columnName = parts[2].Trim();
columns.Add(new EventFieldSchema(columnName, type, false));
sb.Append($"{type} {columnName}");
columns.Add(new EventFieldSchema(columnName, type, isIndexed));
}
}

sb.Append(")");
eventTopic.Append(')');

Hash256 topic = Keccak.Compute(sb.ToString());
return new EventSchema(eventName, topic, columns);
Hash256 topic = Keccak.Compute(eventTopic.ToString());
return new EventSchema(@namespace, eventName, topic, columns);
}

private static ValueTypes MapSolidityType(string type)
Expand All @@ -112,8 +129,9 @@ private static ValueTypes MapSolidityType(string type)
"uint128" => ValueTypes.BigInt,
"uint256" => ValueTypes.BigInt,
"string" => ValueTypes.String,
"bool" => ValueTypes.Boolean,
_ => throw new ArgumentException(
$"'${type}' is not supported. Please handle this event manually.")
$"'{type}' is not supported. Please handle this event manually.")
};
}
}
4 changes: 2 additions & 2 deletions Circles.Index.Common/GreaterThan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ namespace Circles.Index.Common;

public class GreaterThan : QueryPredicate
{
internal GreaterThan(IDatabase database, string table, string column, object value)
internal GreaterThan(IDatabase database, (string Namespace, string Table) table, string column, object value)
: base(database, table, column, value)
{
}

public override string ToSql() => $"{Field} > {ParameterName}";
public override string ToSql() => $"\"{Field}\" > {ParameterName}";
}
4 changes: 2 additions & 2 deletions Circles.Index.Common/GreaterThanOrEqual.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ namespace Circles.Index.Common;

public class GreaterThanOrEqual : QueryPredicate
{
internal GreaterThanOrEqual(IDatabase database, string table, string column, object value)
internal GreaterThanOrEqual(IDatabase database, (string Namespace, string Table) table, string column, object value)
: base(database, table, column, value)
{
}

public override string ToSql() => $"{Field} >= {ParameterName}";
public override string ToSql() => $"\"{Field}\" >= {ParameterName}";
}
2 changes: 1 addition & 1 deletion Circles.Index.Common/IDatabase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public interface IDatabase

void Migrate();
Task DeleteFromBlockOnwards(long reorgAt);
Task WriteBatch(string table, IEnumerable<object> data, ISchemaPropertyMap propertyMap);
Task WriteBatch(string @namespace, string table, IEnumerable<object> data, ISchemaPropertyMap propertyMap);
long? LatestBlock();
long? FirstGap();
IEnumerable<(long BlockNumber, Hash256 BlockHash)> LastPersistedBlocks(int count);
Expand Down
6 changes: 5 additions & 1 deletion Circles.Index.Common/IDatabaseSchema.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@ namespace Circles.Index.Common;

public interface IDatabaseSchema
{
public IDictionary<string, EventSchema> Tables { get; }
public ISchemaPropertyMap SchemaPropertyMap { get; }

public IEventDtoTableMap EventDtoTableMap { get; }

public IDictionary<(string Namespace, string Table), EventSchema> Tables { get; }
}
4 changes: 2 additions & 2 deletions Circles.Index.Common/LessThan.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ namespace Circles.Index.Common;

public class LessThan : QueryPredicate
{
internal LessThan(IDatabase database, string table, string column, object value)
internal LessThan(IDatabase database, (string Namespace, string Table) table, string column, object value)
: base(database, table, column, value)
{
}

public override string ToSql() => $"{Field} < {ParameterName}";
public override string ToSql() => $"\"{Field}\" < {ParameterName}";
}
11 changes: 6 additions & 5 deletions Circles.Index.Common/Query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,21 @@ private static IDatabase GetDatabase()
return _database;
}

public static Equals Equals(string table, string column, object? value) =>
public static Equals Equals((string Namespace, string Table) table, string column, object? value) =>
new(GetDatabase(), table, column, value);

public static GreaterThan GreaterThan(string table, string column, object value) =>
public static GreaterThan GreaterThan((string Namespace, string Table) table, string column, object value) =>
new(GetDatabase(), table, column, value);

public static GreaterThanOrEqual GreaterThanOrEqual(string table, string column, object value) =>
public static GreaterThanOrEqual GreaterThanOrEqual((string Namespace, string Table) table, string column,
object value) =>
new(GetDatabase(), table, column, value);

public static LessThan LessThan(string table, string column, object value) =>
public static LessThan LessThan((string Namespace, string Table) table, string column, object value) =>
new(GetDatabase(), table, column, value);

public static LogicalAnd And(params IQuery[] subElements) => new(subElements);
public static LogicalOr Or(params IQuery[] subElements) => new(subElements);

public static Select Select(string table, IEnumerable<string> columns) => new(table, columns);
public static Select Select((string Namespace, string Table) table, IEnumerable<string> columns) => new(table, columns);
}
4 changes: 2 additions & 2 deletions Circles.Index.Common/QueryPredicate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@ namespace Circles.Index.Common;
public abstract class QueryPredicate : IQuery
{
protected readonly string Field;
public readonly string Table;
public readonly (string Namespace, string Table) Table;
public readonly string Column;
protected readonly string ParameterName;
public readonly object Value;
protected readonly IDatabase Database;

internal QueryPredicate(IDatabase database, string table, string column, object value)
internal QueryPredicate(IDatabase database, (string Namespace, string Table) table, string column, object value)
{
Database = database;
Table = table;
Expand Down
42 changes: 31 additions & 11 deletions Circles.Index.Common/SchemaPropertyMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@ namespace Circles.Index.Common;

public class CompositeDatabaseSchema : IDatabaseSchema
{
public IDictionary<string, EventSchema> Tables { get; }
public ISchemaPropertyMap SchemaPropertyMap { get; }
public IEventDtoTableMap EventDtoTableMap { get; }

public IDictionary<(string Namespace, string Table), EventSchema> Tables { get; }

public CompositeDatabaseSchema(IDatabaseSchema[] components)
{
Expand All @@ -12,19 +15,24 @@ public CompositeDatabaseSchema(IDatabaseSchema[] components)
kvp => kvp.Key
, kvp => kvp.Value
);

SchemaPropertyMap = new CompositeSchemaPropertyMap(components.Select(o => o.SchemaPropertyMap).ToArray());
EventDtoTableMap = new CompositeEventDtoTableMap(components.Select(o => o.EventDtoTableMap).ToArray());
}
}

public interface ISchemaPropertyMap
{
Dictionary<string, Dictionary<string, Func<object, object?>>> Map { get; }
Dictionary<(string Namespace, string Table), Dictionary<string, Func<object, object?>>> Map { get; }

public void Add<TEvent>((string Namespace, string Table) table, Dictionary<string, Func<TEvent, object?>> map);
}

public class SchemaPropertyMap : ISchemaPropertyMap
{
public Dictionary<string, Dictionary<string, Func<object, object?>>> Map { get; } = new();
public Dictionary<(string Namespace, string Table), Dictionary<string, Func<object, object?>>> Map { get; } = new();

public void Add<TEvent>(string table, Dictionary<string, Func<TEvent, object?>> map)
public void Add<TEvent>((string Namespace, string Table) table, Dictionary<string, Func<TEvent, object?>> map)
{
Map[table] = map.ToDictionary(
pair => pair.Key,
Expand All @@ -35,9 +43,13 @@ public void Add<TEvent>(string table, Dictionary<string, Func<TEvent, object?>>

public class CompositeSchemaPropertyMap : ISchemaPropertyMap
{
public Dictionary<string, Dictionary<string, Func<object, object?>>> Map { get; }
public Dictionary<(string Namespace, string Table), Dictionary<string, Func<object, object?>>> Map { get; }
public void Add<TEvent>((string Namespace, string Table) table, Dictionary<string, Func<TEvent, object?>> map)
{
throw new NotImplementedException();
}

public CompositeSchemaPropertyMap(SchemaPropertyMap[] components)
public CompositeSchemaPropertyMap(ISchemaPropertyMap[] components)
{
Map = components
.SelectMany(c => c.Map)
Expand All @@ -50,14 +62,17 @@ public CompositeSchemaPropertyMap(SchemaPropertyMap[] components)

public interface IEventDtoTableMap
{
Dictionary<Type, string> Map { get; }
Dictionary<Type, (string Namespace, string Table)> Map { get; }

public void Add<TEvent>((string Namespace, string Table) table)
where TEvent : IIndexEvent;
}

public class EventDtoTableMap : IEventDtoTableMap
{
public Dictionary<Type, string> Map { get; } = new();
public Dictionary<Type, (string Namespace, string Table)> Map { get; } = new();

public void Add<TEvent>(string table)
public void Add<TEvent>((string Namespace, string Table) table)
where TEvent : IIndexEvent
{
Map[typeof(TEvent)] = table;
Expand All @@ -66,9 +81,14 @@ public void Add<TEvent>(string table)

public class CompositeEventDtoTableMap : IEventDtoTableMap
{
public Dictionary<Type, string> Map { get; }
public Dictionary<Type, (string Namespace, string Table)> Map { get; }

public void Add<TEvent>((string Namespace, string Table) table) where TEvent : IIndexEvent
{
throw new NotImplementedException();
}

public CompositeEventDtoTableMap(EventDtoTableMap[] components)
public CompositeEventDtoTableMap(IEventDtoTableMap[] components)
{
Map = components
.SelectMany(c => c.Map)
Expand Down
Loading

0 comments on commit 17ce26d

Please sign in to comment.