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
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@

namespace Squidex.Domain.Apps.Entities.Contents;

public class ContentSqlQueryBuilder(SqlDialect dialect, string table, SqlParams? parameters = null) : SqlQueryBuilder(dialect, table, parameters)
public class ContentSqlQueryBuilder(SqlDialect dialect, string table, SqlParams? parameters = null)
: SqlQueryBuilder(dialect, table, parameters)
{
public override PropertyPath Visit(PropertyPath path)
{
var elements = path.ToList();

elements[0] = elements[0].ToPascalCase();

return new PropertyPath(elements);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,11 @@
// ==========================================================================

using System.Linq.Expressions;
using Google.Protobuf;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using PhenX.EntityFrameworkCore.BulkInsert.Extensions;
using PhenX.EntityFrameworkCore.BulkInsert.Options;
using Squidex.Domain.Apps.Entities;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

using System.Collections.Concurrent;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;

#pragma warning disable EF1001 // Internal EF Core API usage.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

using System.Collections;
using System.Text;
using Microsoft.EntityFrameworkCore;

namespace Squidex.Infrastructure.Queries;

Expand All @@ -19,6 +20,12 @@ public virtual bool IsDuplicateIndexException(Exception exception, string name)
return false;
}

public virtual Task InitializeAsync(DbContext dbContext,
CancellationToken ct)
{
return Task.CompletedTask;
}

public virtual string BuildSelectStatement(SqlQuery request)
{
var sb = new StringBuilder("SELECT");
Expand Down Expand Up @@ -144,7 +151,7 @@ public virtual string WhereMatch(PropertyPath path, string query, SqlParams quer
throw new NotSupportedException();
}

protected virtual string FormatValues(CompareOperator op, ClrValue value, SqlParams queryParameters)
protected virtual string FormatValues(CompareOperator op, ClrValue value, SqlParams queryParameters, bool withoutBraces = false)
{
if (!value.IsList && value.ValueType == ClrValueType.Null)
{
Expand All @@ -170,6 +177,11 @@ string AddParameter(object value)

if (op == CompareOperator.In)
{
if (withoutBraces)
{
return string.Join(", ", parameters);
}

return $"({string.Join(", ", parameters)})";
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================

using Microsoft.EntityFrameworkCore;
using Squidex.Hosting;

namespace Squidex.Infrastructure.Queries;

public sealed class SqlDialectInitializer<TContext>(IDbContextFactory<TContext> dbContextFactory)
: IInitializable where TContext : DbContext
{
public async Task InitializeAsync(CancellationToken ct)
{
await using var dbContext = await dbContextFactory.CreateDbContextAsync(ct);
if (dbContext is not IDbContextWithDialect withDialect)
{
return;
}

await withDialect.Dialect.InitializeAsync(dbContext, ct);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
namespace Squidex.Providers.MySql.Content;

Check warning on line 1 in backend/src/Squidex.Data.EntityFramework/Providers/MySql/Content/JsonFunction.cs

View workflow job for this annotation

GitHub Actions / test-containers

The file header is missing or not located at the top of the file. (https://github.com/DotNetAnalyzers/StyleCopAnalyzers/blob/master/documentation/SA1633.md)

public static class JsonFunction
{
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,14 @@ public static StringBuilder AppendJsonPath(this StringBuilder sb, PropertyPath p
{
sb.Append('`');
sb.Append(path[0]);
sb.Append("`, \'$");
sb.Append("`, ");
sb.AppendJsonPropertyPath(path);
return sb;
}

public static StringBuilder AppendJsonPropertyPath(this StringBuilder sb, PropertyPath path)
{
sb.Append("\'$");

foreach (var property in path.Skip(1))
{
Expand All @@ -38,6 +45,11 @@ public static StringBuilder AppendJsonPath(this StringBuilder sb, PropertyPath p
return sb;
}

public static string JsonSubPath(this PropertyPath path)
{
return new StringBuilder().AppendJsonPropertyPath(path).ToString();
}

public static string JsonPath(this PropertyPath path)
{
return new StringBuilder().AppendJsonPath(path).ToString();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================

using Microsoft.EntityFrameworkCore;
using Squidex.Infrastructure.Queries;

namespace Squidex.Providers.MySql;

public static class JsonFunction
{
private const int TypeAny = 0;
private const int TypeNull = 1;
private const int TypeText = 2;
private const int TypeBoolean = 3;
private const int TypeNumber = 4;

private static readonly Dictionary<(int Type, CompareOperator Operator), string> Functions = new()

Check warning on line 21 in backend/src/Squidex.Data.EntityFramework/Providers/MySql/JsonFunction.cs

View workflow job for this annotation

GitHub Actions / test-containers

{
[(TypeAny, CompareOperator.Empty)] = "json_empty",
[(TypeAny, CompareOperator.Exists)] = "json_exists",
[(TypeNull, CompareOperator.Equals)] = "json_null_equals",
[(TypeNull, CompareOperator.NotEquals)] = "json_null_notequals",
[(TypeText, CompareOperator.Contains)] = "json_text_contains",
[(TypeText, CompareOperator.EndsWith)] = "json_text_endswith",
[(TypeText, CompareOperator.Equals)] = "json_text_equals",
[(TypeText, CompareOperator.GreaterThan)] = "json_text_greaterthan",
[(TypeText, CompareOperator.GreaterThanOrEqual)] = "json_text_greaterthanorequal",
[(TypeText, CompareOperator.In)] = "json_text_in",
[(TypeText, CompareOperator.LessThan)] = "json_text_lessthan",
[(TypeText, CompareOperator.LessThanOrEqual)] = "json_text_lessthanorequal",
[(TypeText, CompareOperator.Matchs)] = "json_text_matchs",
[(TypeText, CompareOperator.NotEquals)] = "json_text_notequals",
[(TypeText, CompareOperator.StartsWith)] = "json_text_startswith",
[(TypeBoolean, CompareOperator.Equals)] = "json_boolean_equals",
[(TypeBoolean, CompareOperator.In)] = "json_boolean_in",
[(TypeBoolean, CompareOperator.NotEquals)] = "json_boolean_notequals",
[(TypeNumber, CompareOperator.Equals)] = "json_number_equals",
[(TypeNumber, CompareOperator.GreaterThan)] = "json_number_greaterthan",
[(TypeNumber, CompareOperator.GreaterThanOrEqual)] = "json_number_greaterthanorequal",
[(TypeNumber, CompareOperator.In)] = "json_number_in",
[(TypeNumber, CompareOperator.LessThan)] = "json_number_lessthan",
[(TypeNumber, CompareOperator.LessThanOrEqual)] = "json_number_lessthanorequal",
[(TypeNumber, CompareOperator.NotEquals)] = "json_number_notequals",
};

public static async Task InitializeAsync(DbContext dbContext,
CancellationToken ct)
{
var sqlStream = typeof(MySqlDialect).Assembly.GetManifestResourceStream("Squidex.Providers.MySql.json_function.sql");
var sqlText = await new StreamReader(sqlStream!).ReadToEndAsync(ct);

sqlText = sqlText.Replace("{", "{{", StringComparison.Ordinal);
sqlText = sqlText.Replace("}", "}}", StringComparison.Ordinal);

var statements = sqlText.Split(";;", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
// We want to filter out the drop statements and multiple function creations are not supported.
foreach (var statement in statements)
{
#if RELEASE
if (statement.StartsWith("DROP", StringComparison.Ordinal))
{
continue;
}
#endif
await dbContext.Database.ExecuteSqlRawAsync(statement, ct);
}
}

public static string Create(PropertyPath path, CompareOperator op, ClrValue value, string formattedValue)
{
var type = -1;
if (op is CompareOperator.Exists or CompareOperator.Empty)
{
type = TypeAny;
}
else
{
switch (value.ValueType)
{
case ClrValueType.Single:
case ClrValueType.Double:
case ClrValueType.Int32:
case ClrValueType.Int64:
type = TypeNumber;
break;
case ClrValueType.Instant:
case ClrValueType.Guid:
case ClrValueType.String:
type = TypeText;
break;
case ClrValueType.Boolean:
type = TypeBoolean;
break;
case ClrValueType.Null:
type = TypeNull;
break;
}
}

if (!Functions.TryGetValue((type, op), out var fn))
{
throw new NotSupportedException($"No jsonb function for type={value.ValueType}, operator={op}.");
}

if (type is TypeNull or TypeAny)
{
return $"{fn}(`{path[0]}`, {path.JsonSubPath()})";
}

var arg = formattedValue;
if (value.IsList)
{
arg = $"JSON_ARRAY({formattedValue})";
}

return $"{fn}(`{path[0]}`, {path.JsonSubPath()}, {arg})";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================

using Microsoft.EntityFrameworkCore;
using MySqlConnector;
using Squidex.Infrastructure.Queries;

Expand All @@ -18,6 +19,12 @@ private MySqlDialect()
{
}

public override Task InitializeAsync(DbContext dbContext,
CancellationToken ct)
{
return JsonFunction.InitializeAsync(dbContext, ct);
}

public override bool IsDuplicateIndexException(Exception exception, string name)
{
return exception is MySqlException ex && ex.Number == 1061;
Expand Down Expand Up @@ -85,7 +92,13 @@ public override string OrderBy(PropertyPath path, SortOrder order, bool isJson)
var sqlOrder = FormatOrder(order);
var sqlPath = path.JsonPath();

return $"IF(JSON_TYPE(JSON_EXTRACT({sqlPath})) IN ('INTEGER', 'DOUBLE', 'DECIMAL'), CAST(JSON_VALUE({sqlPath}) AS DOUBLE), NULL) {sqlOrder}, JSON_VALUE({sqlPath}) {sqlOrder}";
return $"""
IF (JSON_TYPE(JSON_EXTRACT({sqlPath})) IN ('INTEGER', 'DOUBLE', 'DECIMAL'),
CAST(JSON_VALUE({sqlPath}) AS DOUBLE),
NULL
) {sqlOrder},
JSON_VALUE({sqlPath}) {sqlOrder}
""";
}

return base.OrderBy(path, order, isJson);
Expand All @@ -100,15 +113,7 @@ public override string Where(PropertyPath path, CompareOperator op, ClrValue val
{
if (isJson)
{
var isBoolean = value.ValueType is ClrValueType.Boolean;
if (isBoolean)
{
var sqlPath = path.JsonPath();
var sqlOp = FormatOperator(op, value);
var sqlRhs = FormatValues(op, value, queryParameters);

return $"IF(JSON_VALUE({sqlPath}) = 'true', 1, 0) {sqlOp} {sqlRhs}";
}
return JsonFunction.Create(path, op, value, FormatValues(op, value, queryParameters, true));
}

return base.Where(path, op, value, queryParameters, isJson);
Expand Down
Loading
Loading