mirror of https://github.com/Squidex/squidex.git
Browse Source
* Expanded left panel. * Fix panel label. * a11y improvements * Fix test * Improve queries * Fix tests * More powerful queries. * Query improvements. * Formatting. * Add initializer. * Fix casting.pull/1296/head
committed by
GitHub
100 changed files with 3458 additions and 472 deletions
@ -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); |
|||
} |
|||
} |
|||
@ -0,0 +1,5 @@ |
|||
namespace Squidex.Providers.MySql.Content; |
|||
|
|||
public static class JsonFunction |
|||
{ |
|||
} |
|||
@ -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() |
|||
{ |
|||
[(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})"; |
|||
} |
|||
} |
|||
@ -0,0 +1,409 @@ |
|||
-- ============================================================================= |
|||
-- TYPE-AGNOSTIC |
|||
-- ============================================================================= |
|||
DROP FUNCTION IF EXISTS json_empty;; |
|||
CREATE FUNCTION json_empty(col JSON, path VARCHAR(500)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF val IS NULL THEN RETURN 1; END IF; |
|||
IF JSON_TYPE(val) = 'NULL' THEN RETURN 1; END IF; |
|||
IF JSON_TYPE(val) = 'ARRAY' AND JSON_LENGTH(val) = 0 THEN RETURN 1; END IF; |
|||
IF JSON_TYPE(val) = 'STRING' AND JSON_UNQUOTE(val) = '' THEN RETURN 1; END IF; |
|||
RETURN 0; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_exists;; |
|||
CREATE FUNCTION json_exists(col JSON, path VARCHAR(500)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
RETURN NOT json_empty(col, path); |
|||
END;; |
|||
|
|||
|
|||
-- ============================================================================= |
|||
-- NULL |
|||
-- ============================================================================= |
|||
DROP FUNCTION IF EXISTS json_null_equals;; |
|||
CREATE FUNCTION json_null_equals(col JSON, path VARCHAR(500)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE COALESCE(JSON_TYPE(jt.elem), 'NULL') = 'NULL' |
|||
); |
|||
END IF; |
|||
RETURN COALESCE(JSON_TYPE(val), 'NULL') = 'NULL'; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_null_notequals;; |
|||
CREATE FUNCTION json_null_notequals(col JSON, path VARCHAR(500)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
RETURN NOT json_null_equals(col, path); |
|||
END;; |
|||
|
|||
|
|||
-- ============================================================================= |
|||
-- TEXT |
|||
-- ============================================================================= |
|||
DROP FUNCTION IF EXISTS json_text_equals;; |
|||
CREATE FUNCTION json_text_equals(col JSON, path VARCHAR(500), target VARCHAR(2000)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE JSON_UNQUOTE(jt.elem) = target |
|||
); |
|||
END IF; |
|||
RETURN JSON_UNQUOTE(val) = target; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_text_notequals;; |
|||
CREATE FUNCTION json_text_notequals(col JSON, path VARCHAR(500), target VARCHAR(2000)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN NOT EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE JSON_UNQUOTE(jt.elem) = target |
|||
); |
|||
END IF; |
|||
RETURN JSON_UNQUOTE(val) != target; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_text_lessthan;; |
|||
CREATE FUNCTION json_text_lessthan(col JSON, path VARCHAR(500), target VARCHAR(2000)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE JSON_UNQUOTE(jt.elem) < target |
|||
); |
|||
END IF; |
|||
RETURN JSON_UNQUOTE(val) < target; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_text_lessthanorequal;; |
|||
CREATE FUNCTION json_text_lessthanorequal(col JSON, path VARCHAR(500), target VARCHAR(2000)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE JSON_UNQUOTE(jt.elem) <= target |
|||
); |
|||
END IF; |
|||
RETURN JSON_UNQUOTE(val) <= target; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_text_greaterthan;; |
|||
CREATE FUNCTION json_text_greaterthan(col JSON, path VARCHAR(500), target VARCHAR(2000)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE JSON_UNQUOTE(jt.elem) > target |
|||
); |
|||
END IF; |
|||
RETURN JSON_UNQUOTE(val) > target; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_text_greaterthanorequal;; |
|||
CREATE FUNCTION json_text_greaterthanorequal(col JSON, path VARCHAR(500), target VARCHAR(2000)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE JSON_UNQUOTE(jt.elem) >= target |
|||
); |
|||
END IF; |
|||
RETURN JSON_UNQUOTE(val) >= target; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_text_contains;; |
|||
CREATE FUNCTION json_text_contains(col JSON, path VARCHAR(500), target VARCHAR(2000)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE JSON_UNQUOTE(jt.elem) LIKE CONCAT('%', target, '%') |
|||
); |
|||
END IF; |
|||
RETURN JSON_UNQUOTE(val) LIKE CONCAT('%', target, '%'); |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_text_startswith;; |
|||
CREATE FUNCTION json_text_startswith(col JSON, path VARCHAR(500), target VARCHAR(2000)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE JSON_UNQUOTE(jt.elem) LIKE CONCAT(target, '%') |
|||
); |
|||
END IF; |
|||
RETURN JSON_UNQUOTE(val) LIKE CONCAT(target, '%'); |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_text_endswith;; |
|||
CREATE FUNCTION json_text_endswith(col JSON, path VARCHAR(500), target VARCHAR(2000)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE JSON_UNQUOTE(jt.elem) LIKE CONCAT('%', target) |
|||
); |
|||
END IF; |
|||
RETURN JSON_UNQUOTE(val) LIKE CONCAT('%', target); |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_text_matchs;; |
|||
CREATE FUNCTION json_text_matchs(col JSON, path VARCHAR(500), target VARCHAR(2000)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE JSON_UNQUOTE(jt.elem) REGEXP target |
|||
); |
|||
END IF; |
|||
RETURN JSON_UNQUOTE(val) REGEXP target; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_text_in;; |
|||
CREATE FUNCTION json_text_in(col JSON, path VARCHAR(500), targets JSON) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE JSON_CONTAINS(targets, jt.elem) |
|||
); |
|||
END IF; |
|||
RETURN JSON_CONTAINS(targets, val); |
|||
END;; |
|||
|
|||
|
|||
-- ============================================================================= |
|||
-- NUMBER |
|||
-- ============================================================================= |
|||
DROP FUNCTION IF EXISTS json_number_equals;; |
|||
CREATE FUNCTION json_number_equals(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE CASE WHEN JSON_TYPE(jt.elem) IN ('INTEGER', 'DOUBLE', 'DECIMAL') |
|||
THEN CAST(JSON_UNQUOTE(jt.elem) AS DECIMAL(65, 10)) |
|||
END = target |
|||
); |
|||
END IF; |
|||
IF JSON_TYPE(val) NOT IN ('INTEGER', 'DOUBLE', 'DECIMAL') THEN RETURN 0; END IF; |
|||
RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) = target; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_number_notequals;; |
|||
CREATE FUNCTION json_number_notequals(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN NOT EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE CASE WHEN JSON_TYPE(jt.elem) IN ('INTEGER', 'DOUBLE', 'DECIMAL') |
|||
THEN CAST(JSON_UNQUOTE(jt.elem) AS DECIMAL(65, 10)) |
|||
END = target |
|||
); |
|||
END IF; |
|||
IF JSON_TYPE(val) NOT IN ('INTEGER', 'DOUBLE', 'DECIMAL') THEN RETURN 0; END IF; |
|||
RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) != target; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_number_lessthan;; |
|||
CREATE FUNCTION json_number_lessthan(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE CASE WHEN JSON_TYPE(jt.elem) IN ('INTEGER', 'DOUBLE', 'DECIMAL') |
|||
THEN CAST(JSON_UNQUOTE(jt.elem) AS DECIMAL(65, 10)) |
|||
END < target |
|||
); |
|||
END IF; |
|||
IF JSON_TYPE(val) NOT IN ('INTEGER', 'DOUBLE', 'DECIMAL') THEN RETURN 0; END IF; |
|||
RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) < target; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_number_lessthanorequal;; |
|||
CREATE FUNCTION json_number_lessthanorequal(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE CASE WHEN JSON_TYPE(jt.elem) IN ('INTEGER', 'DOUBLE', 'DECIMAL') |
|||
THEN CAST(JSON_UNQUOTE(jt.elem) AS DECIMAL(65, 10)) |
|||
END <= target |
|||
); |
|||
END IF; |
|||
IF JSON_TYPE(val) NOT IN ('INTEGER', 'DOUBLE', 'DECIMAL') THEN RETURN 0; END IF; |
|||
RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) <= target; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_number_greaterthan;; |
|||
CREATE FUNCTION json_number_greaterthan(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE CASE WHEN JSON_TYPE(jt.elem) IN ('INTEGER', 'DOUBLE', 'DECIMAL') |
|||
THEN CAST(JSON_UNQUOTE(jt.elem) AS DECIMAL(65, 10)) |
|||
END > target |
|||
); |
|||
END IF; |
|||
IF JSON_TYPE(val) NOT IN ('INTEGER', 'DOUBLE', 'DECIMAL') THEN RETURN 0; END IF; |
|||
RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) > target; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_number_greaterthanorequal;; |
|||
CREATE FUNCTION json_number_greaterthanorequal(col JSON, path VARCHAR(500), target DECIMAL(65, 10)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE CASE WHEN JSON_TYPE(jt.elem) IN ('INTEGER', 'DOUBLE', 'DECIMAL') |
|||
THEN CAST(JSON_UNQUOTE(jt.elem) AS DECIMAL(65, 10)) |
|||
END >= target |
|||
); |
|||
END IF; |
|||
IF JSON_TYPE(val) NOT IN ('INTEGER', 'DOUBLE', 'DECIMAL') THEN RETURN 0; END IF; |
|||
RETURN CAST(JSON_UNQUOTE(val) AS DECIMAL(65, 10)) >= target; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_number_in;; |
|||
CREATE FUNCTION json_number_in(col JSON, path VARCHAR(500), targets JSON) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE JSON_TYPE(jt.elem) IN ('INTEGER', 'DOUBLE', 'DECIMAL') |
|||
AND JSON_CONTAINS(targets, jt.elem) |
|||
); |
|||
END IF; |
|||
IF JSON_TYPE(val) NOT IN ('INTEGER', 'DOUBLE', 'DECIMAL') THEN RETURN 0; END IF; |
|||
RETURN JSON_CONTAINS(targets, val); |
|||
END;; |
|||
|
|||
|
|||
-- ============================================================================= |
|||
-- BOOLEAN |
|||
-- ============================================================================= |
|||
DROP FUNCTION IF EXISTS json_boolean_equals;; |
|||
CREATE FUNCTION json_boolean_equals(col JSON, path VARCHAR(500), target TINYINT(1)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
DECLARE target_json JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
SET target_json = IF(target, CAST('true' AS JSON), CAST('false' AS JSON)); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE CASE WHEN JSON_TYPE(jt.elem) = 'BOOLEAN' THEN jt.elem END = target_json |
|||
); |
|||
END IF; |
|||
IF JSON_TYPE(val) != 'BOOLEAN' THEN RETURN 0; END IF; |
|||
RETURN val = target_json; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_boolean_notequals;; |
|||
CREATE FUNCTION json_boolean_notequals(col JSON, path VARCHAR(500), target TINYINT(1)) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
DECLARE target_json JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
SET target_json = IF(target, CAST('true' AS JSON), CAST('false' AS JSON)); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN NOT EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE CASE WHEN JSON_TYPE(jt.elem) = 'BOOLEAN' THEN jt.elem END = target_json |
|||
); |
|||
END IF; |
|||
IF JSON_TYPE(val) != 'BOOLEAN' THEN RETURN 0; END IF; |
|||
RETURN val != target_json; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS json_boolean_in;; |
|||
CREATE FUNCTION json_boolean_in(col JSON, path VARCHAR(500), targets JSON) |
|||
RETURNS TINYINT(1) DETERMINISTIC |
|||
BEGIN |
|||
DECLARE val JSON; |
|||
SET val = JSON_EXTRACT(col, path); |
|||
IF JSON_TYPE(val) = 'ARRAY' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(val, '$[*]' COLUMNS (elem JSON PATH '$')) AS jt |
|||
WHERE JSON_TYPE(jt.elem) = 'BOOLEAN' |
|||
AND EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(targets, '$[*]' COLUMNS (t JSON PATH '$')) AS tt |
|||
WHERE IF(jt.elem = CAST('true' AS JSON), 1, 0) = CAST(JSON_UNQUOTE(tt.t) AS UNSIGNED) |
|||
) |
|||
); |
|||
END IF; |
|||
IF JSON_TYPE(val) != 'BOOLEAN' THEN RETURN 0; END IF; |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM JSON_TABLE(targets, '$[*]' COLUMNS (t JSON PATH '$')) AS tt |
|||
WHERE IF(val = CAST('true' AS JSON), 1, 0) = CAST(JSON_UNQUOTE(tt.t) AS UNSIGNED) |
|||
); |
|||
END;; |
|||
@ -0,0 +1,120 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using Microsoft.EntityFrameworkCore; |
|||
using Squidex.Infrastructure.Queries; |
|||
using Squidex.Providers.Postgres.App; |
|||
|
|||
namespace Squidex.Providers.Postgres; |
|||
|
|||
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 () |
|||
{ |
|||
[(TypeAny, CompareOperator.Empty)] = "jsonb_empty", |
|||
[(TypeAny, CompareOperator.Exists)] = "jsonb_exists", |
|||
[(TypeNull, CompareOperator.Equals)] = "jsonb_null_equals", |
|||
[(TypeNull, CompareOperator.NotEquals)] = "jsonb_null_notequals", |
|||
[(TypeText, CompareOperator.Contains)] = "jsonb_text_contains", |
|||
[(TypeText, CompareOperator.EndsWith)] = "jsonb_text_endswith", |
|||
[(TypeText, CompareOperator.Equals)] = "jsonb_text_equals", |
|||
[(TypeText, CompareOperator.GreaterThan)] = "jsonb_text_greaterthan", |
|||
[(TypeText, CompareOperator.GreaterThanOrEqual)] = "jsonb_text_greaterthanorequal", |
|||
[(TypeText, CompareOperator.In)] = "jsonb_text_in", |
|||
[(TypeText, CompareOperator.LessThan)] = "jsonb_text_lessthan", |
|||
[(TypeText, CompareOperator.LessThanOrEqual)] = "jsonb_text_lessthanorequal", |
|||
[(TypeText, CompareOperator.Matchs)] = "jsonb_text_matchs", |
|||
[(TypeText, CompareOperator.NotEquals)] = "jsonb_text_notequals", |
|||
[(TypeText, CompareOperator.StartsWith)] = "jsonb_text_startswith", |
|||
[(TypeBoolean, CompareOperator.Equals)] = "jsonb_boolean_equals", |
|||
[(TypeBoolean, CompareOperator.In)] = "jsonb_boolean_in", |
|||
[(TypeBoolean, CompareOperator.NotEquals)] = "jsonb_boolean_notequals", |
|||
[(TypeNumber, CompareOperator.Equals)] = "jsonb_number_equals", |
|||
[(TypeNumber, CompareOperator.GreaterThan)] = "jsonb_number_greaterthan", |
|||
[(TypeNumber, CompareOperator.GreaterThanOrEqual)] = "jsonb_number_greaterthanorequal", |
|||
[(TypeNumber, CompareOperator.In)] = "jsonb_number_in", |
|||
[(TypeNumber, CompareOperator.LessThan)] = "jsonb_number_lessthan", |
|||
[(TypeNumber, CompareOperator.LessThanOrEqual)] = "jsonb_number_lessthanorequal", |
|||
[(TypeNumber, CompareOperator.NotEquals)] = "jsonb_number_notequals", |
|||
}; |
|||
|
|||
public static async Task InitializeAsync(DbContext dbContext, |
|||
CancellationToken ct) |
|||
{ |
|||
var sqlStream = typeof(PostgresDialect).Assembly.GetManifestResourceStream("Squidex.Providers.Postgres.json_function.sql"); |
|||
var sqlText = await new StreamReader(sqlStream!).ReadToEndAsync(ct); |
|||
|
|||
sqlText = sqlText.Replace("{", "{{", StringComparison.Ordinal); |
|||
sqlText = sqlText.Replace("}", "}}", StringComparison.Ordinal); |
|||
|
|||
await dbContext.Database.ExecuteSqlRawAsync(sqlText, 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.JsonPath(false)})"; |
|||
} |
|||
|
|||
var arg = formattedValue; |
|||
if (value.IsList && type == TypeNumber) |
|||
{ |
|||
arg = $"ARRAY[{formattedValue}]::numeric[]"; |
|||
} |
|||
else if (value.IsList) |
|||
{ |
|||
arg = $"ARRAY[{formattedValue}]"; |
|||
} |
|||
else if (type == TypeNumber) |
|||
{ |
|||
arg = $"{formattedValue}::numeric"; |
|||
} |
|||
|
|||
return $"{fn}({path.JsonPath(false)}, {arg})"; |
|||
} |
|||
} |
|||
@ -0,0 +1,330 @@ |
|||
-- ============================================================================= |
|||
-- TYPE-AGNOSTIC |
|||
-- ============================================================================= |
|||
CREATE OR REPLACE FUNCTION jsonb_empty(val jsonb) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
RETURN val IS NULL |
|||
OR (jsonb_typeof(val) = 'null') |
|||
OR (jsonb_typeof(val) = 'array' AND jsonb_array_length(val) = 0) |
|||
OR (jsonb_typeof(val) = 'string' AND val #>> '{}' = ''); |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_exists(val jsonb) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
RETURN NOT jsonb_empty(val); |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- ============================================================================= |
|||
-- TEXT |
|||
-- ============================================================================= |
|||
CREATE OR REPLACE FUNCTION jsonb_text_equals(val jsonb, target text) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e WHERE e #>> '{}' = target |
|||
); |
|||
END IF; |
|||
RETURN val #>> '{}' = target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_text_notequals(val jsonb, target text) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN NOT EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e WHERE e #>> '{}' = target |
|||
); |
|||
END IF; |
|||
RETURN val #>> '{}' != target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_text_lessthan(val jsonb, target text) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e WHERE e #>> '{}' < target |
|||
); |
|||
END IF; |
|||
RETURN val #>> '{}' < target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_text_lessthanorequal(val jsonb, target text) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e WHERE e #>> '{}' <= target |
|||
); |
|||
END IF; |
|||
RETURN val #>> '{}' <= target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_text_greaterthan(val jsonb, target text) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e WHERE e #>> '{}' > target |
|||
); |
|||
END IF; |
|||
RETURN val #>> '{}' > target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_text_greaterthanorequal(val jsonb, target text) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e WHERE e #>> '{}' >= target |
|||
); |
|||
END IF; |
|||
RETURN val #>> '{}' >= target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_text_contains(val jsonb, target text) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE e #>> '{}' LIKE '%' || target || '%' |
|||
); |
|||
END IF; |
|||
RETURN val #>> '{}' LIKE '%' || target || '%'; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_text_startswith(val jsonb, target text) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE e #>> '{}' LIKE target || '%' |
|||
); |
|||
END IF; |
|||
RETURN val #>> '{}' LIKE target || '%'; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_text_endswith(val jsonb, target text) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE e #>> '{}' LIKE '%' || target |
|||
); |
|||
END IF; |
|||
RETURN val #>> '{}' LIKE '%' || target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_text_matchs(val jsonb, target text) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE e #>> '{}' ~ target |
|||
); |
|||
END IF; |
|||
RETURN val #>> '{}' ~ target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_text_in(val jsonb, targets text[]) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE e #>> '{}' = ANY(targets) |
|||
); |
|||
END IF; |
|||
RETURN val #>> '{}' = ANY(targets); |
|||
END; |
|||
$$; |
|||
|
|||
|
|||
-- ============================================================================= |
|||
-- NUMBER |
|||
-- ============================================================================= |
|||
CREATE OR REPLACE FUNCTION jsonb_number_equals(val jsonb, target numeric) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE jsonb_typeof(e) = 'number' AND (e #>> '{}')::numeric = target |
|||
); |
|||
END IF; |
|||
IF jsonb_typeof(val) != 'number' THEN RETURN FALSE; END IF; |
|||
RETURN (val #>> '{}')::numeric = target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_number_notequals(val jsonb, target numeric) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN NOT EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE jsonb_typeof(e) = 'number' AND (e #>> '{}')::numeric = target |
|||
); |
|||
END IF; |
|||
IF jsonb_typeof(val) != 'number' THEN RETURN FALSE; END IF; |
|||
RETURN (val #>> '{}')::numeric != target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_number_lessthan(val jsonb, target numeric) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE jsonb_typeof(e) = 'number' AND (e #>> '{}')::numeric < target |
|||
); |
|||
END IF; |
|||
IF jsonb_typeof(val) != 'number' THEN RETURN FALSE; END IF; |
|||
RETURN (val #>> '{}')::numeric < target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_number_lessthanorequal(val jsonb, target numeric) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE jsonb_typeof(e) = 'number' AND (e #>> '{}')::numeric <= target |
|||
); |
|||
END IF; |
|||
IF jsonb_typeof(val) != 'number' THEN RETURN FALSE; END IF; |
|||
RETURN (val #>> '{}')::numeric <= target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_number_greaterthan(val jsonb, target numeric) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE jsonb_typeof(e) = 'number' AND (e #>> '{}')::numeric > target |
|||
); |
|||
END IF; |
|||
IF jsonb_typeof(val) != 'number' THEN RETURN FALSE; END IF; |
|||
RETURN (val #>> '{}')::numeric > target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_number_greaterthanorequal(val jsonb, target numeric) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE jsonb_typeof(e) = 'number' AND (e #>> '{}')::numeric >= target |
|||
); |
|||
END IF; |
|||
IF jsonb_typeof(val) != 'number' THEN RETURN FALSE; END IF; |
|||
RETURN (val #>> '{}')::numeric >= target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_number_in(val jsonb, targets numeric[]) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE jsonb_typeof(e) = 'number' AND (e #>> '{}')::numeric = ANY(targets) |
|||
); |
|||
END IF; |
|||
IF jsonb_typeof(val) != 'number' THEN RETURN FALSE; END IF; |
|||
RETURN (val #>> '{}')::numeric = ANY(targets); |
|||
END; |
|||
$$; |
|||
|
|||
-- ============================================================================= |
|||
-- NULL |
|||
-- ============================================================================= |
|||
CREATE OR REPLACE FUNCTION jsonb_null_equals(val jsonb) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE jsonb_typeof(e) = 'null' |
|||
); |
|||
END IF; |
|||
RETURN val IS NULL OR jsonb_typeof(val) = 'null'; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_null_notequals(val jsonb) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
RETURN NOT jsonb_null_equals(val); |
|||
END; |
|||
$$; |
|||
|
|||
-- ============================================================================= |
|||
-- BOOLEAN |
|||
-- ============================================================================= |
|||
CREATE OR REPLACE FUNCTION jsonb_boolean_equals(val jsonb, target boolean) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE jsonb_typeof(e) = 'boolean' AND (e #>> '{}')::boolean = target |
|||
); |
|||
END IF; |
|||
IF jsonb_typeof(val) != 'boolean' THEN RETURN FALSE; END IF; |
|||
RETURN (val #>> '{}')::boolean = target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_boolean_notequals(val jsonb, target boolean) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN NOT EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE jsonb_typeof(e) = 'boolean' AND (e #>> '{}')::boolean = target |
|||
); |
|||
END IF; |
|||
IF jsonb_typeof(val) != 'boolean' THEN RETURN FALSE; END IF; |
|||
RETURN (val #>> '{}')::boolean != target; |
|||
END; |
|||
$$; |
|||
|
|||
CREATE OR REPLACE FUNCTION jsonb_boolean_in(val jsonb, targets boolean[]) |
|||
RETURNS boolean LANGUAGE plpgsql IMMUTABLE AS $$ |
|||
BEGIN |
|||
IF jsonb_typeof(val) = 'array' THEN |
|||
RETURN EXISTS ( |
|||
SELECT 1 FROM jsonb_array_elements(val) AS e |
|||
WHERE jsonb_typeof(e) = 'boolean' AND (e #>> '{}')::boolean = ANY(targets) |
|||
); |
|||
END IF; |
|||
IF jsonb_typeof(val) != 'boolean' THEN RETURN FALSE; END IF; |
|||
RETURN (val #>> '{}')::boolean = ANY(targets); |
|||
END; |
|||
$$; |
|||
@ -0,0 +1,153 @@ |
|||
// ==========================================================================
|
|||
// 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.SqlServer; |
|||
|
|||
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 () |
|||
{ |
|||
[(TypeAny, CompareOperator.Empty)] = "dbo.json_empty", |
|||
[(TypeAny, CompareOperator.Exists)] = "dbo.json_exists", |
|||
[(TypeNull, CompareOperator.Equals)] = "dbo.json_null_equals", |
|||
[(TypeNull, CompareOperator.NotEquals)] = "dbo.json_null_notequals", |
|||
[(TypeText, CompareOperator.Contains)] = "dbo.json_text_contains", |
|||
[(TypeText, CompareOperator.EndsWith)] = "dbo.json_text_endswith", |
|||
[(TypeText, CompareOperator.Equals)] = "dbo.json_text_equals", |
|||
[(TypeText, CompareOperator.GreaterThan)] = "dbo.json_text_greaterthan", |
|||
[(TypeText, CompareOperator.GreaterThanOrEqual)] = "dbo.json_text_greaterthanorequal", |
|||
[(TypeText, CompareOperator.LessThan)] = "dbo.json_text_lessthan", |
|||
[(TypeText, CompareOperator.LessThanOrEqual)] = "dbo.json_text_lessthanorequal", |
|||
[(TypeText, CompareOperator.Matchs)] = "dbo.json_text_matchs", |
|||
[(TypeText, CompareOperator.NotEquals)] = "dbo.json_text_notequals", |
|||
[(TypeText, CompareOperator.StartsWith)] = "dbo.json_text_startswith", |
|||
[(TypeBoolean, CompareOperator.Equals)] = "dbo.json_boolean_equals", |
|||
[(TypeBoolean, CompareOperator.NotEquals)] = "dbo.json_boolean_notequals", |
|||
[(TypeNumber, CompareOperator.Equals)] = "dbo.json_number_equals", |
|||
[(TypeNumber, CompareOperator.GreaterThan)] = "dbo.json_number_greaterthan", |
|||
[(TypeNumber, CompareOperator.GreaterThanOrEqual)] = "dbo.json_number_greaterthanorequal", |
|||
[(TypeNumber, CompareOperator.LessThan)] = "dbo.json_number_lessthan", |
|||
[(TypeNumber, CompareOperator.LessThanOrEqual)] = "dbo.json_number_lessthanorequal", |
|||
[(TypeNumber, CompareOperator.NotEquals)] = "dbo.json_number_notequals", |
|||
}; |
|||
|
|||
public static async Task InitializeAsync(DbContext dbContext, |
|||
CancellationToken ct) |
|||
{ |
|||
var sqlStream = typeof(SqlServerDialect).Assembly.GetManifestResourceStream("Squidex.Providers.SqlServer.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; |
|||
} |
|||
} |
|||
|
|||
var jsonPath = path.JsonPath(); |
|||
|
|||
// SQL Server scalar functions cannot accept parameterized arrays, embedding
|
|||
// SQL parameter references (@p0, @p1) inside a JSON string literal
|
|||
// produces invalid JSON at execution time. IN is therefore handled inline so
|
|||
// parameters remain proper SQL parameters.
|
|||
if (value.IsList) |
|||
{ |
|||
return type switch |
|||
{ |
|||
TypeText => |
|||
$"""
|
|||
(CASE WHEN JSON_QUERY({jsonPath}) IS NOT NULL |
|||
THEN CASE WHEN EXISTS (SELECT 1 FROM OPENJSON(JSON_QUERY({jsonPath})) AS j WHERE j.[type] = 1 AND j.[value] IN ({formattedValue})) THEN 1 ELSE 0 END |
|||
ELSE CASE WHEN JSON_VALUE({jsonPath}) IN ({formattedValue}) THEN 1 ELSE 0 END |
|||
END) = 1 |
|||
""",
|
|||
TypeNumber => |
|||
$"""
|
|||
(CASE WHEN JSON_QUERY({jsonPath}) IS NOT NULL |
|||
THEN CASE WHEN EXISTS (SELECT 1 FROM OPENJSON(JSON_QUERY({jsonPath})) AS j WHERE j.[type] = 2 AND TRY_CAST(j.[value] AS DECIMAL(38, 10)) IN ({formattedValue})) THEN 1 ELSE 0 END |
|||
ELSE CASE WHEN TRY_CAST(JSON_VALUE({jsonPath}) AS DECIMAL(38, 10)) IN ({formattedValue}) THEN 1 ELSE 0 END |
|||
END) = 1 |
|||
""",
|
|||
TypeBoolean => |
|||
$"""
|
|||
(CASE WHEN JSON_QUERY({jsonPath}) IS NOT NULL |
|||
THEN CASE WHEN EXISTS (SELECT 1 FROM OPENJSON(JSON_QUERY({jsonPath})) AS j WHERE j.[type] = 3 AND IIF(j.[value] = 'true', 1, IIF(j.[value] = 'false', 0, NULL)) IN ({formattedValue})) THEN 1 ELSE 0 END |
|||
ELSE CASE WHEN IIF(JSON_VALUE({jsonPath}) = 'true', 1, IIF(JSON_VALUE({jsonPath}) = 'false', 0, NULL)) IN ({formattedValue}) THEN 1 ELSE 0 END |
|||
END) = 1 |
|||
""",
|
|||
_ => throw new NotSupportedException($"No json function for type={type}, operator={op}."), |
|||
}; |
|||
} |
|||
|
|||
if (!Functions.TryGetValue((type, op), out var fn)) |
|||
{ |
|||
throw new NotSupportedException($"No json function for type={type}, operator={op}."); |
|||
} |
|||
|
|||
if (type is TypeNull or TypeAny) |
|||
{ |
|||
return $"{fn}({jsonPath}) = 1"; |
|||
} |
|||
|
|||
if (value.IsList) |
|||
{ |
|||
return $"{fn}({jsonPath}, N'[{formattedValue}]') = 1"; |
|||
} |
|||
|
|||
return $"{fn}({jsonPath}, {formattedValue}) = 1"; |
|||
} |
|||
} |
|||
@ -0,0 +1,313 @@ |
|||
-- ============================================================================= |
|||
-- TYPE-AGNOSTIC |
|||
-- ============================================================================= |
|||
DROP FUNCTION IF EXISTS dbo.json_empty;; |
|||
CREATE FUNCTION dbo.json_empty(@col NVARCHAR(MAX), @path NVARCHAR(500)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
DECLARE @val NVARCHAR(MAX) = JSON_VALUE(@col, @path); |
|||
IF @query IS NOT NULL AND LEFT(LTRIM(@query), 1) = '[' |
|||
RETURN CASE WHEN (SELECT COUNT(*) FROM OPENJSON(@query)) = 0 THEN 1 ELSE 0 END; |
|||
IF @query IS NOT NULL AND LEFT(LTRIM(@query), 1) = '{' |
|||
RETURN 0; |
|||
IF @val IS NULL RETURN 1; |
|||
IF @val = '' RETURN 1; |
|||
RETURN 0; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_exists;; |
|||
CREATE FUNCTION dbo.json_exists(@col NVARCHAR(MAX), @path NVARCHAR(500)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
RETURN 1 - dbo.json_empty(@col, @path); |
|||
END;; |
|||
|
|||
|
|||
-- ============================================================================= |
|||
-- NULL |
|||
-- ============================================================================= |
|||
DROP FUNCTION IF EXISTS dbo.json_null_equals;; |
|||
CREATE FUNCTION dbo.json_null_equals(@col NVARCHAR(MAX), @path NVARCHAR(500)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j WHERE j.[type] = 0 |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN JSON_VALUE(@col, @path) IS NULL THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_null_notequals;; |
|||
CREATE FUNCTION dbo.json_null_notequals(@col NVARCHAR(MAX), @path NVARCHAR(500)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
RETURN 1 - dbo.json_null_equals(@col, @path); |
|||
END;; |
|||
|
|||
|
|||
-- ============================================================================= |
|||
-- TEXT |
|||
-- ============================================================================= |
|||
DROP FUNCTION IF EXISTS dbo.json_text_equals;; |
|||
CREATE FUNCTION dbo.json_text_equals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j WHERE j.[type] = 1 AND j.[value] = @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN JSON_VALUE(@col, @path) = @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_text_notequals;; |
|||
CREATE FUNCTION dbo.json_text_notequals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN NOT EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j WHERE j.[type] = 1 AND j.[value] = @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN JSON_VALUE(@col, @path) != @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_text_lessthan;; |
|||
CREATE FUNCTION dbo.json_text_lessthan(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j WHERE j.[type] = 1 AND j.[value] < @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN JSON_VALUE(@col, @path) < @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_text_lessthanorequal;; |
|||
CREATE FUNCTION dbo.json_text_lessthanorequal(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j WHERE j.[type] = 1 AND j.[value] <= @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN JSON_VALUE(@col, @path) <= @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_text_greaterthan;; |
|||
CREATE FUNCTION dbo.json_text_greaterthan(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j WHERE j.[type] = 1 AND j.[value] > @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN JSON_VALUE(@col, @path) > @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_text_greaterthanorequal;; |
|||
CREATE FUNCTION dbo.json_text_greaterthanorequal(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j WHERE j.[type] = 1 AND j.[value] >= @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN JSON_VALUE(@col, @path) >= @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_text_contains;; |
|||
CREATE FUNCTION dbo.json_text_contains(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j WHERE j.[type] = 1 AND j.[value] LIKE '%' + @target + '%' |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN JSON_VALUE(@col, @path) LIKE '%' + @target + '%' THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_text_startswith;; |
|||
CREATE FUNCTION dbo.json_text_startswith(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j WHERE j.[type] = 1 AND j.[value] LIKE @target + '%' |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN JSON_VALUE(@col, @path) LIKE @target + '%' THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_text_endswith;; |
|||
CREATE FUNCTION dbo.json_text_endswith(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j WHERE j.[type] = 1 AND j.[value] LIKE '%' + @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN JSON_VALUE(@col, @path) LIKE '%' + @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_text_matchs;; |
|||
CREATE FUNCTION dbo.json_text_matchs(@col NVARCHAR(MAX), @path NVARCHAR(500), @target NVARCHAR(2000)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j WHERE j.[type] = 1 AND j.[value] LIKE @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN JSON_VALUE(@col, @path) LIKE @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
|
|||
-- ============================================================================= |
|||
-- NUMBER |
|||
-- ============================================================================= |
|||
DROP FUNCTION IF EXISTS dbo.json_number_equals;; |
|||
CREATE FUNCTION dbo.json_number_equals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j |
|||
WHERE j.[type] = 2 |
|||
AND TRY_CAST(j.[value] AS DECIMAL(38, 10)) = @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN TRY_CAST(JSON_VALUE(@col, @path) AS DECIMAL(38, 10)) = @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_number_notequals;; |
|||
CREATE FUNCTION dbo.json_number_notequals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN NOT EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j |
|||
WHERE j.[type] = 2 |
|||
AND TRY_CAST(j.[value] AS DECIMAL(38, 10)) = @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN TRY_CAST(JSON_VALUE(@col, @path) AS DECIMAL(38, 10)) != @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_number_lessthan;; |
|||
CREATE FUNCTION dbo.json_number_lessthan(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j |
|||
WHERE j.[type] = 2 |
|||
AND TRY_CAST(j.[value] AS DECIMAL(38, 10)) < @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN TRY_CAST(JSON_VALUE(@col, @path) AS DECIMAL(38, 10)) < @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_number_lessthanorequal;; |
|||
CREATE FUNCTION dbo.json_number_lessthanorequal(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j |
|||
WHERE j.[type] = 2 |
|||
AND TRY_CAST(j.[value] AS DECIMAL(38, 10)) <= @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN TRY_CAST(JSON_VALUE(@col, @path) AS DECIMAL(38, 10)) <= @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_number_greaterthan;; |
|||
CREATE FUNCTION dbo.json_number_greaterthan(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j |
|||
WHERE j.[type] = 2 |
|||
AND TRY_CAST(j.[value] AS DECIMAL(38, 10)) > @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN TRY_CAST(JSON_VALUE(@col, @path) AS DECIMAL(38, 10)) > @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_number_greaterthanorequal;; |
|||
CREATE FUNCTION dbo.json_number_greaterthanorequal(@col NVARCHAR(MAX), @path NVARCHAR(500), @target DECIMAL(38, 10)) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j |
|||
WHERE j.[type] = 2 |
|||
AND TRY_CAST(j.[value] AS DECIMAL(38, 10)) >= @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN TRY_CAST(JSON_VALUE(@col, @path) AS DECIMAL(38, 10)) >= @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
|
|||
-- ============================================================================= |
|||
-- BOOLEAN |
|||
-- ============================================================================= |
|||
DROP FUNCTION IF EXISTS dbo.json_boolean_equals;; |
|||
CREATE FUNCTION dbo.json_boolean_equals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target BIT) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j |
|||
WHERE j.[type] = 3 |
|||
AND IIF(j.[value] = 'true', 1, IIF(j.[value] = 'false', 0, NULL)) = @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN IIF(JSON_VALUE(@col, @path) = 'true', 1, IIF(JSON_VALUE(@col, @path) = 'false', 0, NULL)) = @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
|
|||
DROP FUNCTION IF EXISTS dbo.json_boolean_notequals;; |
|||
CREATE FUNCTION dbo.json_boolean_notequals(@col NVARCHAR(MAX), @path NVARCHAR(500), @target BIT) |
|||
RETURNS BIT |
|||
AS |
|||
BEGIN |
|||
DECLARE @query NVARCHAR(MAX) = JSON_QUERY(@col, @path); |
|||
IF @query IS NOT NULL |
|||
RETURN CASE WHEN NOT EXISTS ( |
|||
SELECT 1 FROM OPENJSON(@query) AS j |
|||
WHERE j.[type] = 3 |
|||
AND IIF(j.[value] = 'true', 1, IIF(j.[value] = 'false', 0, NULL)) = @target |
|||
) THEN 1 ELSE 0 END; |
|||
RETURN CASE WHEN IIF(JSON_VALUE(@col, @path) = 'true', 1, IIF(JSON_VALUE(@col, @path) = 'false', 0, NULL)) != @target THEN 1 ELSE 0 END; |
|||
END;; |
|||
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -0,0 +1,35 @@ |
|||
#!/usr/bin/env node
|
|||
|
|||
const fs = require("fs"); |
|||
const path = require("path"); |
|||
|
|||
const ROOT_DIR = "."; |
|||
const SKIP_DIRS = ["node_modules", ".git", "dist", "build", ".cache"]; |
|||
const BUTTON_TAG_RE = /<button(\s[^>]*?)?>/gi; |
|||
|
|||
function fixButtons(html) { |
|||
return html.replace(BUTTON_TAG_RE, (match, attrs) => { |
|||
if (attrs && /\btype\s*=/i.test(attrs)) return match; |
|||
return attrs ? `<button type="button"${attrs}>` : `<button type="button">`; |
|||
}); |
|||
} |
|||
|
|||
function walk(dir) { |
|||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) { |
|||
const fullPath = path.join(dir, entry.name); |
|||
|
|||
if (entry.isDirectory()) { |
|||
if (!SKIP_DIRS.includes(entry.name)) walk(fullPath); |
|||
} else if (entry.isFile() && entry.name.endsWith(".component.html")) { |
|||
const original = fs.readFileSync(fullPath, "utf8"); |
|||
const fixed = fixButtons(original); |
|||
|
|||
if (fixed !== original) { |
|||
fs.writeFileSync(fullPath, fixed, "utf8"); |
|||
console.log(`Fixed: ${fullPath}`); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
walk(path.resolve(ROOT_DIR)); |
|||
Loading…
Reference in new issue