Skip to content

Commit

Permalink
support for boolean value comparison
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelstonis committed Sep 1, 2021
1 parent a6150e8 commit 3beab1a
Show file tree
Hide file tree
Showing 6 changed files with 117 additions and 18 deletions.
29 changes: 29 additions & 0 deletions Tycho.UnitTests/TychoDbTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,35 @@ public async Task TychoDb_RegisterPatientAndInsertObject_ShouldBeSuccessful (IJs
result.Should ().BeTrue ();
}

[DataTestMethod]
[DynamicData(nameof(JsonSerializers))]
public async Task TychoDb_RegisterPatientAndInsertObjectAndQueryByIsDirty_ShouldBeSuccessful(IJsonSerializer jsonSerializer)
{
var db =
new TychoDb(Path.GetTempPath(), jsonSerializer, rebuildCache: true)
.AddTypeRegistration<Patient>(x => x.PatientId)
.Connect();

var testObj =
new Patient
{
PatientId = 12345,
FirstName = "TEST",
LastName = "PATIENT",
IsDirty = true,
};

await db.WriteObjectAsync(testObj);

var result =
await db.ReadObjectsAsync<Patient>(
filter: new FilterBuilder<Patient>()
.Filter(FilterType.Equals, x => x.IsDirty, true));

result.Should().NotBeNullOrEmpty();
result.Should().HaveCount(1);
}

[DataTestMethod]
[DynamicData (nameof (JsonSerializers))]
public async Task TychoDb_InsertAndReadManyObjects_ShouldBeSuccessful (IJsonSerializer jsonSerializer)
Expand Down
12 changes: 9 additions & 3 deletions Tycho/Filter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,26 +34,32 @@ public class Filter

public bool IsPropertyPathNumeric { get; private set; }

public bool IsPropertyPathBool { get; private set; }

public string PropertyValuePath { get; set; }

public bool IsPropertyValuePathNumeric { get; private set; }

public bool IsPropertyValuePathBool { get; private set; }

public object Value { get; private set; }

public Filter (FilterType filterType, string propertyPath, bool isPropertyPathNumeric, object value)
public Filter (FilterType filterType, string propertyPath, bool isPropertyPathNumeric, bool isPropertyPathBool, object value)
{
FilterType = filterType;
PropertyPath = propertyPath;
IsPropertyPathNumeric = isPropertyPathNumeric;
IsPropertyPathBool = isPropertyPathBool;
Value = value;
}

public Filter (FilterType filterType, string listPropertyPath, string propertyValuePath, bool isPropertyValuePathNumeric, object value)
public Filter (FilterType filterType, string listPropertyPath, string propertyValuePath, bool isPropertyValuePathNumeric, bool isPropertyValuePathBool, object value)
{
FilterType = filterType;
PropertyPath = listPropertyPath;
PropertyValuePath = propertyValuePath;
IsPropertyPathNumeric = IsPropertyPathNumeric;
IsPropertyValuePathNumeric = isPropertyValuePathNumeric;
IsPropertyValuePathBool = isPropertyValuePathBool;
Value = value;
}

Expand Down
35 changes: 30 additions & 5 deletions Tycho/FilterBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@ public FilterBuilder<TObj> Filter<TProp> (FilterType filterType, Expression<Func
{
var propertyPathString = QueryPropertyPath.BuildPath (propertyPath);
var isPropertyPathNumeric = QueryPropertyPath.IsNumeric (propertyPath);
var isPropertyPathBool = QueryPropertyPath.IsBool(propertyPath);

_filters.Add (new Filter (filterType, propertyPathString, isPropertyPathNumeric, value));
_filters.Add (new Filter (filterType, propertyPathString, isPropertyPathNumeric, isPropertyPathBool, value));

return this;
}
Expand All @@ -31,15 +32,16 @@ public FilterBuilder<TObj> Filter<TItem, TItemProp> (FilterType filterType, Expr
var propertyPathString = QueryPropertyPath.BuildPath (propertyPath);
var propertyValuePathString = QueryPropertyPath.BuildPath (propertyValuePath);
var isPropertyValuePathNumeric = QueryPropertyPath.IsNumeric (propertyValuePath);
var isPropertyValuePathBool = QueryPropertyPath.IsBool(propertyValuePath);

_filters.Add (new Filter (filterType, propertyPathString, propertyValuePathString, isPropertyValuePathNumeric, value));
_filters.Add (new Filter (filterType, propertyPathString, propertyValuePathString, isPropertyValuePathNumeric, isPropertyValuePathBool, value));

return this;
}

public FilterBuilder<TObj> Filter (FilterType filterType, string propertyPath, bool isPropertyPathNumeric, object value)
public FilterBuilder<TObj> Filter (FilterType filterType, string propertyPath, bool isPropertyPathNumeric, bool isPropertyPathBool, object value)
{
_filters.Add (new Filter (filterType, propertyPath, isPropertyPathNumeric, value));
_filters.Add (new Filter (filterType, propertyPath, isPropertyPathNumeric, isPropertyPathBool, value));

return this;
}
Expand Down Expand Up @@ -108,12 +110,18 @@ public StringBuilder Build (StringBuilder commandBuilder)
commandBuilder.AppendLine ($"EXISTS(SELECT 1 FROM JSON_TREE(Data, \'{filter.PropertyPath}\') AS JT, JSON_EACH(JT.Value, \'{filter.PropertyValuePath}\') AS VAL WHERE VAL.value like \'%{filter.Value}\')");
break;
case FilterType.Equals:
if (filter.IsPropertyValuePathBool)
{
commandBuilder.AppendLine($"EXISTS(SELECT 1 FROM JSON_TREE(Data, \'{filter.PropertyPath}\') AS JT, JSON_EACH(JT.Value, \'{filter.PropertyValuePath}\') AS VAL WHERE VAL.value = {filter.Value})");
break;
}

if(filter.IsPropertyValuePathNumeric)
{
commandBuilder.AppendLine ($"EXISTS(SELECT 1 FROM JSON_TREE(Data, \'{filter.PropertyPath}\') AS JT, JSON_EACH(JT.Value, \'{filter.PropertyValuePath}\') AS VAL WHERE CAST(VAL.value as NUMERIC) = \'{filter.Value}\')");
break;
}

commandBuilder.AppendLine ($"EXISTS(SELECT 1 FROM JSON_TREE(Data, \'{filter.PropertyPath}\') AS JT, JSON_EACH(JT.Value, \'{filter.PropertyValuePath}\') AS VAL WHERE VAL.value = \'{filter.Value}\')");
break;
//TODO: This is an attack vector and should be parameterized
Expand All @@ -133,6 +141,11 @@ public StringBuilder Build (StringBuilder commandBuilder)
commandBuilder.AppendLine ($"EXISTS(SELECT 1 FROM JSON_TREE(Data, \'{filter.PropertyPath}\') AS JT, JSON_EACH(JT.Value, \'{filter.PropertyValuePath}\') AS VAL WHERE CAST(VAL.value as NUMERIC) <= {filter.Value})");
break;
case FilterType.NotEquals:
if (filter.IsPropertyValuePathBool)
{
commandBuilder.AppendLine($"EXISTS(SELECT 1 FROM JSON_TREE(Data, \'{filter.PropertyPath}\') AS JT, JSON_EACH(JT.Value, \'{filter.PropertyValuePath}\') AS VAL WHERE VAL.value <> {filter.Value})");
break;
}
commandBuilder.AppendLine ($"EXISTS(SELECT 1 FROM JSON_TREE(Data, \'{filter.PropertyPath}\') AS JT, JSON_EACH(JT.Value, \'{filter.PropertyValuePath}\') AS VAL WHERE VAL.value <> \'{filter.Value}\')");
break;
case FilterType.StartsWith:
Expand All @@ -154,6 +167,12 @@ public StringBuilder Build (StringBuilder commandBuilder)
commandBuilder.AppendLine ($"JSON_EXTRACT(Data, \'{filter.PropertyPath}\') like \'%{filter.Value}\'");
break;
case FilterType.Equals:
if(filter.IsPropertyPathBool)
{
commandBuilder.AppendLine($"JSON_EXTRACT(Data, \'{filter.PropertyPath}\') = {filter.Value}");
break;
}

if(filter.IsPropertyPathNumeric)
{
commandBuilder.AppendLine ($"CAST(JSON_EXTRACT(Data, \'{filter.PropertyPath}\') as NUMERIC) = \'{filter.Value}\'");
Expand All @@ -179,6 +198,12 @@ public StringBuilder Build (StringBuilder commandBuilder)
commandBuilder.AppendLine ($"CAST(JSON_EXTRACT(Data, \'{filter.PropertyPath}\') as NUMERIC) <= {filter.Value}");
break;
case FilterType.NotEquals:
if (filter.IsPropertyPathBool)
{
commandBuilder.AppendLine($"JSON_EXTRACT(Data, \'{filter.PropertyPath}\') <> {filter.Value}");
break;
}

commandBuilder.AppendLine ($"JSON_EXTRACT(Data, \'{filter.PropertyPath}\') <> \'{filter.Value}\'");
break;
case FilterType.StartsWith:
Expand Down
24 changes: 14 additions & 10 deletions Tycho/Queries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,9 @@ INSERT OR REPLACE INTO JsonValue(Key, FullTypeName, Data, Partition)
SELECT Data
FROM JsonValue
Where
Key = $key
AND
FullTypeName = $fullTypeName";
Key = $key
AND
FullTypeName = $fullTypeName";

public const string SelectPartitions =
@"
Expand All @@ -59,23 +59,25 @@ SELECT DISTINCT Partition
SELECT Data
FROM JsonValue
Where
FullTypeName = $fullTypeName";
FullTypeName = $fullTypeName";

public const string DeleteDataFromJsonValueWithKeyAndFullTypeName =
@"
DELETE
FROM JsonValue
Where
Key = $key
AND
FullTypeName = $fullTypeName";
Key = $key
AND
FullTypeName = $fullTypeName
";

public const string DeleteDataFromJsonValueWithFullTypeName =
@"
DELETE
FROM JsonValue
Where
FullTypeName = $fullTypeName";
FullTypeName = $fullTypeName
";

public const string AndPartitionHasValue =
@"
Expand Down Expand Up @@ -105,15 +107,17 @@ public static string CreateIndexForJsonValueAsNumeric(string fullIndexName, stri
return
@$"
CREATE INDEX IF NOT EXISTS {fullIndexName}
ON JsonValue(FullTypeName, CAST(JSON_EXTRACT(Data, '{propertyPathString}') as NUMERIC));";
ON JsonValue(FullTypeName, CAST(JSON_EXTRACT(Data, '{propertyPathString}') as NUMERIC));
";
}

public static string CreateIndexForJsonValue(string fullIndexName, string propertyPathString)
{
return
@$"
CREATE INDEX IF NOT EXISTS {fullIndexName}
ON JsonValue(FullTypeName, JSON_EXTRACT(Data, '{propertyPathString}'));";
ON JsonValue(FullTypeName, JSON_EXTRACT(Data, '{propertyPathString}'));
";
}

}
Expand Down
32 changes: 32 additions & 0 deletions Tycho/QueryPropertyPath.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,15 @@ public static bool IsNumeric<TPathObj, TProp> (Expression<Func<TPathObj, TProp>>
return visitor.IsNumeric ?? false;
}

public static bool IsBool<TPathObj, TProp>(Expression<Func<TPathObj, TProp>> expression)
{
var visitor = new PropertyIsBoolVisitor();

visitor.Visit(expression.Body);

return visitor.IsBool ?? false;
}

private class PropertyPathVisitor : ExpressionVisitor
{
internal readonly List<string> PathBuilder = new List<string>();
Expand Down Expand Up @@ -68,5 +77,28 @@ protected override Expression VisitMember (MemberExpression node)
return base.VisitMember (node);
}
}

private class PropertyIsBoolVisitor : ExpressionVisitor
{
internal bool? IsBool;

protected override Expression VisitMember(MemberExpression node)
{
if (!(node.Member is PropertyInfo))
{
throw new ArgumentException("The path can only contain properties", nameof(node));
}

var propertyType = ((PropertyInfo)node.Member).PropertyType;

if (!IsBool.HasValue)
{
//TODO: Should we add comparisons for numerics of 0/1???
IsBool = propertyType == typeof(bool);
}

return base.VisitMember(node);
}
}
}
}
3 changes: 3 additions & 0 deletions Tycho/RegisteredTypeInformation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ internal struct RegisteredTypeInformation

public bool IsNumeric { get; set; }

public bool IsBool { get; set; }

public string TypeFullName { get; set; }

public string TypeName { get; set; }
Expand All @@ -38,6 +40,7 @@ public static RegisteredTypeInformation Create<T, TId> (Expression<Func<T, TId>>
IdProperty = GetExpressionMemberName (idProperty),
IdPropertyPath = QueryPropertyPath.BuildPath(idProperty),
IsNumeric = QueryPropertyPath.IsNumeric(idProperty),
IsBool = QueryPropertyPath.IsBool(idProperty),
TypeFullName = type.FullName,
TypeName = type.Name,
TypeNamespace = type.Namespace,
Expand Down

0 comments on commit 3beab1a

Please sign in to comment.