Skip to content

Commit 075c200

Browse files
More powerful queries.
1 parent ab64921 commit 075c200

File tree

9 files changed

+1211
-330
lines changed

9 files changed

+1211
-330
lines changed

backend/src/Squidex.Data.EntityFramework/Providers/MySql/MySqlDialect.cs

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -110,51 +110,66 @@ public override string Where(PropertyPath path, CompareOperator op, ClrValue val
110110
var sqlPath = path.JsonPath();
111111
var sqlOp = FormatOperator(op, value);
112112
var sqlRhs = FormatValues(op, value, queryParameters);
113-
var isBoolean = value.ValueType is ClrValueType.Boolean;
114113

115-
string ScalarCondition()
114+
string BuildCondition(string path)
116115
{
117-
if (isBoolean)
116+
var isNumeric = value.ValueType is
117+
ClrValueType.Single or
118+
ClrValueType.Double or
119+
ClrValueType.Int32 or
120+
ClrValueType.Int64;
121+
if (isNumeric)
118122
{
119-
return $"IF(JSON_VALUE({sqlPath}) = 'true', 1, 0) {sqlOp} {sqlRhs}";
123+
return $"""
124+
(CASE WHEN JSON_TYPE({path}) IN ('INTEGER', 'DOUBLE', 'DECIMAL')
125+
THEN CAST(JSON_UNQUOTE({path}) AS DECIMAL(65,10)) {sqlOp} {sqlRhs}
126+
ELSE FALSE
127+
END)
128+
""";
120129
}
121130

122-
return base.Where(path, op, value, queryParameters, isJson);
123-
}
131+
var isBoolean = value.ValueType is ClrValueType.Boolean;
132+
if (isBoolean)
133+
{
134+
return $"""
135+
(CASE WHEN JSON_TYPE({path}) = 'BOOLEAN'
136+
THEN IF(JSON_UNQUOTE({path}) = 'true', TRUE, FALSE) {sqlOp} {sqlRhs}
137+
ELSE FALSE
138+
END)
139+
""";
140+
}
124141

125-
if (value.IsList && op == CompareOperator.In)
126-
{
127-
return $"""
128-
IF(
129-
JSON_TYPE(JSON_EXTRACT({sqlPath})) = 'ARRAY',
130-
JSON_OVERLAPS(JSON_EXTRACT({sqlPath}), JSON_ARRAY{sqlRhs}),
131-
JSON_CONTAINS(JSON_ARRAY{sqlRhs}, JSON_EXTRACT({sqlPath}))
132-
)
133-
""";
134-
}
142+
var isString = value.ValueType is
143+
ClrValueType.Instant or
144+
ClrValueType.Guid or
145+
ClrValueType.String;
146+
if (isString)
147+
{
148+
return $"JSON_UNQUOTE({path}) {sqlOp} {sqlRhs}";
149+
}
135150

136-
if (op == CompareOperator.Equals)
137-
{
138-
var valueExpr = value.ValueType switch
151+
var isNull = value.ValueType is ClrValueType.Null;
152+
if (isNull)
139153
{
140-
ClrValueType.Boolean =>
141-
$"IF({sqlRhs} = 1, 'true', 'false')",
142-
ClrValueType.String =>
143-
$"JSON_QUOTE({sqlRhs})",
144-
_ =>
145-
$"CAST({sqlRhs} AS JSON)",
146-
};
147-
148-
return $"""
149-
IF(
150-
JSON_TYPE(JSON_EXTRACT({sqlPath})) = 'ARRAY',
151-
JSON_CONTAINS(JSON_EXTRACT({sqlPath}), {valueExpr}),
152-
{ScalarCondition()}
153-
)
154-
""";
154+
var nullOp = FormatOperator(op, "null");
155+
return $"COALESCE(JSON_TYPE({path}), 'NULL') {nullOp} 'NULL'";
156+
}
157+
158+
return base.Where(path, op, value, queryParameters, false);
155159
}
156160

157-
return ScalarCondition();
161+
return $"""
162+
CASE WHEN JSON_TYPE(JSON_EXTRACT({sqlPath})) = 'ARRAY'
163+
THEN EXISTS (
164+
SELECT 1
165+
FROM JSON_TABLE(JSON_EXTRACT({sqlPath}), '$[*]' COLUMNS (
166+
__element JSON PATH '$'
167+
)) AS __jt
168+
WHERE {BuildCondition("__element")}
169+
)
170+
ELSE {BuildCondition($"JSON_EXTRACT({sqlPath})")}
171+
END
172+
""";
158173
}
159174

160175
return base.Where(path, op, value, queryParameters, isJson);

backend/src/Squidex.Data.EntityFramework/Providers/Postgres/PostgresDialect.cs

Lines changed: 36 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
// All rights reserved. Licensed under the MIT license.
66
// ==========================================================================
77

8-
using System.Text;
98
using Npgsql;
109
using Squidex.Infrastructure.Queries;
1110
using Squidex.Providers.Postgres.App;
@@ -77,69 +76,54 @@ public override string Where(PropertyPath path, CompareOperator op, ClrValue val
7776
var sqlOp = FormatOperator(op, value);
7877
var sqlRhs = FormatValues(op, value, queryParameters);
7978

80-
var isBoolean = value.ValueType is ClrValueType.Boolean;
81-
82-
var isNumeric = value.ValueType is
83-
ClrValueType.Single or
84-
ClrValueType.Double or
85-
ClrValueType.Int32 or
86-
ClrValueType.Int64;
87-
88-
string ScalarCondition()
79+
string BuildCondition(string path, string castPath)
8980
{
81+
var isNumeric = value.ValueType is
82+
ClrValueType.Single or
83+
ClrValueType.Double or
84+
ClrValueType.Int32 or
85+
ClrValueType.Int64;
86+
9087
if (isNumeric)
9188
{
92-
return $"(CASE WHEN jsonb_typeof({sqlPath}) = 'number' THEN ({sqlPathCast})::numeric {sqlOp} {sqlRhs} ELSE FALSE END)";
89+
return $"(CASE WHEN jsonb_typeof({path}) = 'number' THEN ({castPath})::numeric {sqlOp} {sqlRhs} ELSE FALSE END)";
9390
}
9491

92+
var isBoolean = value.ValueType is ClrValueType.Boolean;
9593
if (isBoolean)
9694
{
97-
return $"(CASE WHEN jsonb_typeof({sqlPath}) = 'boolean' THEN ({sqlPathCast})::boolean {sqlOp} {sqlRhs} ELSE FALSE END)";
95+
return $"(CASE WHEN jsonb_typeof({path}) = 'boolean' THEN ({castPath})::boolean {sqlOp} {sqlRhs} ELSE FALSE END)";
9896
}
9997

100-
return base.Where(path, op, value, queryParameters, true);
101-
}
102-
103-
string ToJsonbValue()
104-
{
105-
if (isNumeric)
98+
var isString = value.ValueType is
99+
ClrValueType.Instant or
100+
ClrValueType.Guid or
101+
ClrValueType.String;
102+
if (isString)
106103
{
107-
return $"to_jsonb({sqlRhs}::numeric)";
104+
return $"{path} #>> '{{{{}}}}' {sqlOp} {sqlRhs}";
108105
}
109106

110-
if (isBoolean)
107+
var isNull = value.ValueType is ClrValueType.Null;
108+
if (isNull)
111109
{
112-
return $"to_jsonb({sqlRhs}::boolean)";
110+
var nullOp = FormatOperator(op, "null");
111+
return $"jsonb_typeof({path}) {nullOp} 'null'";
113112
}
114113

115-
return $"to_jsonb({sqlRhs}::text)";
116-
}
117-
118-
if (value.IsList && op == CompareOperator.In)
119-
{
120-
return $"""
121-
CASE WHEN jsonb_typeof({sqlPath}) = 'array'
122-
THEN EXISTS (
123-
SELECT 1
124-
FROM jsonb_array_elements({sqlPath}) AS elem
125-
WHERE jsonb_build_array{sqlRhs} @> elem
126-
)
127-
ELSE {sqlPath} <@ jsonb_build_array{sqlRhs}
128-
END
129-
""";
130-
}
131-
132-
if (op == CompareOperator.Equals)
133-
{
134-
return $"""
135-
CASE WHEN jsonb_typeof({sqlPath}) = 'array'
136-
THEN {sqlPath} @> jsonb_build_array({ToJsonbValue()})
137-
ELSE {ScalarCondition()}
138-
END
139-
""";
114+
return base.Where(path, op, value, queryParameters, false);
140115
}
141116

142-
return ScalarCondition();
117+
return $"""
118+
CASE WHEN jsonb_typeof({sqlPath}) = 'array'
119+
THEN EXISTS (
120+
SELECT 1
121+
FROM jsonb_array_elements({sqlPath}) AS __element
122+
WHERE {BuildCondition("__element", "__element")}
123+
)
124+
ELSE {BuildCondition(sqlPath, sqlPathCast)}
125+
END
126+
""";
143127
}
144128

145129
return base.Where(path, op, value, queryParameters, isJson);
@@ -148,12 +132,16 @@ CASE WHEN jsonb_typeof({sqlPath}) = 'array'
148132
protected override string FormatField(PropertyPath path, bool isJson)
149133
{
150134
var baseField = path[0];
151-
152135
if (isJson && path.Count > 1)
153136
{
154137
return path.JsonPath(true);
155138
}
156139

140+
if (baseField == "__element" || baseField.Contains("->", StringComparison.Ordinal))
141+
{
142+
return baseField;
143+
}
144+
157145
return $"\"{baseField}\"";
158146
}
159147
}

backend/src/Squidex.Data.EntityFramework/Providers/SqlServer/SqlServerDialect.cs

Lines changed: 39 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -92,92 +92,62 @@ public override string Where(PropertyPath path, CompareOperator op, ClrValue val
9292
var sqlOp = FormatOperator(op, value);
9393
var sqlRhs = FormatValues(op, value, queryParameters);
9494

95-
var isNull = value.ValueType is ClrValueType.Null;
96-
var isBoolean = value.ValueType is ClrValueType.Boolean;
97-
var isNumeric = value.ValueType is
98-
ClrValueType.Single or
99-
ClrValueType.Double or
100-
ClrValueType.Int32 or
101-
ClrValueType.Int64;
102-
103-
string ScalarCondition()
95+
string BuildCondition(string path, string queryPath)
10496
{
105-
if (isNull)
106-
{
107-
if (op == CompareOperator.Equals)
108-
{
109-
return $"JSON_QUERY({sqlPath}) IS NULL AND JSON_VALUE({sqlPath}) IS NULL";
110-
}
111-
112-
if (op == CompareOperator.NotEquals)
113-
{
114-
return $"JSON_QUERY({sqlPath}) IS NOT NULL OR JSON_VALUE({sqlPath}) IS NOT NULL";
115-
}
116-
}
117-
97+
var isNumeric = value.ValueType is
98+
ClrValueType.Single or
99+
ClrValueType.Double or
100+
ClrValueType.Int32 or
101+
ClrValueType.Int64;
118102
if (isNumeric)
119103
{
120-
return $"TRY_CAST(JSON_VALUE({sqlPath}) AS NUMERIC) {sqlOp} {sqlRhs}";
104+
return $"TRY_CAST({path} AS NUMERIC) {sqlOp} {sqlRhs}";
121105
}
122106

107+
var isBoolean = value.ValueType is ClrValueType.Boolean;
123108
if (isBoolean)
124109
{
125-
return $"IIF(JSON_VALUE({sqlPath}) = 'true', 1, 0) {sqlOp} {sqlRhs}";
110+
return $"IIF({path} = 'true', 1, IIF({path} = 'false', 0, NULL)) {sqlOp} {sqlRhs}";
126111
}
127112

128-
return base.Where(path, op, value, queryParameters, isJson);
129-
}
130-
131-
string ArrayCondition(string field, string op)
132-
{
133-
if (isNumeric)
113+
var isString = value.ValueType is
114+
ClrValueType.Instant or
115+
ClrValueType.Guid or
116+
ClrValueType.String;
117+
if (isString)
134118
{
135-
return $"TRY_CAST({field} AS NUMERIC) {op} {sqlRhs}";
119+
return $"{path} {sqlOp} {sqlRhs}";
136120
}
137121

138-
if (isBoolean)
122+
var isNull = value.ValueType is ClrValueType.Null;
123+
if (isNull)
139124
{
140-
return $"IIF({field} = 'true', 1, 0) {op} {sqlRhs}";
141-
}
142-
143-
return $"{field} = {sqlRhs}";
144-
}
125+
if (op == CompareOperator.Equals)
126+
{
127+
return $"{queryPath} IS NULL AND {path} IS NULL";
128+
}
145129

146-
if (value.IsList && op == CompareOperator.In)
147-
{
148-
return $"""
149-
CASE WHEN LEFT(JSON_QUERY({sqlPath}), 1) = '['
150-
THEN (
151-
SELECT COUNT(*)
152-
FROM OPENJSON({sqlPath}) AS field_arr
153-
WHERE {ArrayCondition("field_arr.value", "IN")}
154-
)
155-
ELSE (
156-
SELECT COUNT(*)
157-
WHERE {ScalarCondition()}
158-
)
159-
END > 0
160-
""";
161-
}
130+
if (op == CompareOperator.NotEquals)
131+
{
132+
return $"{queryPath} IS NOT NULL OR {path} IS NOT NULL";
133+
}
134+
}
162135

163-
if (op == CompareOperator.Equals)
164-
{
165-
return $"""
166-
CASE WHEN LEFT(JSON_QUERY({sqlPath}), 1) = '['
167-
THEN (
168-
SELECT COUNT(*)
169-
FROM OPENJSON({sqlPath}) AS field_arr
170-
WHERE {ArrayCondition("field_arr.value", "=")}
171-
)
172-
ELSE (
173-
SELECT COUNT(*)
174-
WHERE {ScalarCondition()}
175-
)
176-
END > 0
177-
""";
136+
return base.Where(path, op, value, queryParameters, false);
178137
}
179138

180-
return ScalarCondition();
139+
return $"""
140+
(
141+
CASE WHEN LEFT(LTRIM(JSON_QUERY({sqlPath})), 1) = '['
142+
THEN CASE WHEN EXISTS (
143+
SELECT 1
144+
FROM OPENJSON({sqlPath}) AS __element
145+
WHERE {BuildCondition("__element.[value]", "__element.[value]")}
146+
) THEN 1 ELSE 0 END
147+
ELSE CASE WHEN {BuildCondition($"JSON_VALUE({sqlPath})", $"JSON_QUERY({sqlPath})")} THEN 1 ELSE 0 END
148+
END
149+
) = 1
150+
""";
181151
}
182152

183153
return base.Where(path, op, value, queryParameters, isJson);

backend/src/Squidex.Infrastructure/Queries/QueryModel.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,12 @@ public sealed class QueryModel
117117
public QueryModel Flatten(int maxDepth = 7, bool onlyWithOperators = true)
118118
{
119119
var predicate = (Predicate<FilterSchema>?)null;
120-
121120
if (onlyWithOperators)
122121
{
123122
predicate = x => Operators.TryGetValue(x.Type, out var operators) && operators.Count > 0;
124123
}
125124

126125
var flatten = Schema.Flatten(maxDepth, predicate);
127-
128126
if (ReferenceEquals(flatten, Schema))
129127
{
130128
return this;

0 commit comments

Comments
 (0)