Skip to content

Commit 75e4530

Browse files
whole new optimization system and universal translation layer that is significantly more performant than framework wrapping layers.
1 parent a568757 commit 75e4530

File tree

8 files changed

+1029
-35
lines changed

8 files changed

+1029
-35
lines changed

Magic.IndexedDb/LinqTranslation/Extensions/UniversalExpressionBuilder.cs

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -184,14 +184,40 @@ private FilterNode ParseNotExpression(UnaryExpression notExpr)
184184
{
185185
var inner = notExpr.Operand;
186186

187-
return inner switch
187+
if (inner is BinaryExpression bin)
188188
{
189-
BinaryExpression bin => HandleNotBinary(bin),
190-
MethodCallExpression call => HandleNotMethod(call),
191-
_ => throw new InvalidOperationException($"Unsupported NOT expression: {inner}")
192-
};
189+
if (bin.NodeType == ExpressionType.OrElse || bin.NodeType == ExpressionType.AndAlso)
190+
{
191+
// De Morgan’s Law: !(A || B) => !A && !B, !(A && B) => !A || !B
192+
var invertedOperator = bin.NodeType == ExpressionType.OrElse
193+
? FilterLogicalOperator.And
194+
: FilterLogicalOperator.Or;
195+
196+
return new FilterNode
197+
{
198+
NodeType = FilterNodeType.Logical,
199+
Operator = invertedOperator,
200+
Children = new List<FilterNode>
201+
{
202+
ParseNotExpression(Expression.Not(bin.Left)),
203+
ParseNotExpression(Expression.Not(bin.Right))
204+
}
205+
};
206+
}
207+
else
208+
{
209+
return HandleNotBinary(bin);
210+
}
211+
}
212+
else if (inner is MethodCallExpression call)
213+
{
214+
return HandleNotMethod(call);
215+
}
216+
217+
throw new InvalidOperationException($"Unsupported NOT expression: {inner}");
193218
}
194219

220+
195221
private FilterNode HandleNotBinary(BinaryExpression bin)
196222
{
197223
string invertedOp = bin.NodeType switch

Magic.IndexedDb/wwwroot/magicLinqToIndexedDb.js

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,17 @@ export async function* magicQueryYield(db, table, universalSerializedPredicate,
3333
throw new Error("A valid Dexie table instance must be provided.");
3434
}
3535

36-
console.log('universal serialized predicate');
37-
console.log(universalSerializedPredicate);
36+
debugLog('universal serialized predicate');
37+
debugLog(universalSerializedPredicate);
3838
const { nestedOrFilterUnclean, isUniversalTrue, isUniversalFalse } = flattenUniversalPredicate(universalSerializedPredicate);
3939

4040
if (isUniversalFalse === true) {
4141
debugLog("Universal False, sending back no data");
4242
return;
4343
}
4444

45-
console.log('flattened serialized predicate');
46-
console.log(nestedOrFilterUnclean);
45+
debugLog('flattened serialized predicate');
46+
debugLog(nestedOrFilterUnclean);
4747

4848
debugLog("Starting where function", { nestedOrFilterUnclean, queryAdditions });
4949

@@ -69,6 +69,9 @@ export async function* magicQueryYield(db, table, universalSerializedPredicate,
6969
return;
7070
}
7171

72+
debugLog('Flattened or groups');
73+
debugLog(nestedOrFilter);
74+
7275
let { indexedQueries, compoundIndexQueries, cursorConditions } =
7376
partitionQueryConditions(nestedOrFilter, queryAdditions, indexCache, forceCursor);
7477

Magic.IndexedDb/wwwroot/utilities/FlattenFilterNode.js

Lines changed: 46 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,10 @@ import {
44
resolveNodeType,
55
resolveLogicalOperator
66
} from "./predicateEnumHelpers.js";
7-
7+
import { QUERY_OPERATIONS } from "./queryConstants.js";
8+
import { areConditionsCompatible } from "./areConditionsCompatible.js";
9+
import { advancedOptimizeNestedOrFilter } from "./optimizeConditions.js";
10+
import { optimizeOrGroupStructure } from "./optimizeOrGroupStructure.js";
811
/**
912
* Flattens a Universal Predicate Tree into a nestedOrFilter structure.
1013
* This creates `orGroups -> andGroups -> conditions[]`
@@ -34,9 +37,10 @@ export function flattenUniversalPredicate(rootNode) {
3437
]
3538
}));
3639

40+
let orGroupsOptimized = optimizeOrGroupStructure(advancedOptimizeNestedOrFilter(orGroups));
3741
const result = {
3842
nestedOrFilterUnclean: {
39-
orGroups
43+
orGroups: orGroupsOptimized
4044
},
4145
isUniversalTrue: detection.isUniversalTrue,
4246
isUniversalFalse: detection.isUniversalFalse
@@ -48,60 +52,54 @@ export function flattenUniversalPredicate(rootNode) {
4852

4953
function flattenNodeToAndGroups(node) {
5054
const nodeType = resolveNodeType(node.nodeType);
51-
52-
if (!nodeType) {
53-
debugLog("Skipping node with null/invalid nodeType", node);
54-
return [];
55-
}
55+
if (!nodeType) return [];
5656

5757
if (nodeType === "Condition") {
58-
if (!node.condition) {
59-
debugLog("Skipping condition node with null condition", node);
60-
return [];
61-
}
62-
return [[node.condition]];
58+
if (!node.condition) return [];
59+
const normalized = normalizeCondition(node.condition);
60+
return [[normalized]];
6361
}
6462

6563
if (nodeType === "Logical") {
6664
const operator = resolveLogicalOperator(node.operator);
6765
const { children } = node;
6866

69-
if (!operator || !Array.isArray(children)) {
70-
debugLog("Skipping logical node with null operator or children", node);
71-
return [];
72-
}
67+
if (!operator || !Array.isArray(children)) return [];
7368

7469
if (operator === "And") {
7570
let result = [[]];
71+
7672
for (const child of children) {
77-
const childFlattened = flattenNodeToAndGroups(child);
73+
const childGroups = flattenNodeToAndGroups(child);
7874
const newResult = [];
7975

80-
for (const resGroup of result) {
81-
for (const childGroup of childFlattened) {
82-
newResult.push([...resGroup, ...childGroup]);
76+
for (const groupA of result) {
77+
for (const groupB of childGroups) {
78+
if (areConditionsCompatible(groupA, groupB)) {
79+
newResult.push([...groupA, ...groupB]);
80+
} else {
81+
debugLog("Skipping incompatible group merge", { groupA, groupB });
82+
}
8383
}
8484
}
8585

8686
result = newResult;
8787
}
88+
8889
return result;
8990
}
9091

9192
if (operator === "Or") {
9293
let result = [];
9394
for (const child of children) {
94-
const childFlattened = flattenNodeToAndGroups(child);
95-
result = result.concat(childFlattened);
95+
result = result.concat(flattenNodeToAndGroups(child));
9696
}
9797
return result;
9898
}
9999

100-
debugLog("Unknown operator encountered during flattening", operator);
101100
return [];
102101
}
103102

104-
debugLog("Unknown node type encountered during flattening", nodeType);
105103
return [];
106104
}
107105

@@ -126,3 +124,27 @@ function detectUniversalTruth(node) {
126124
isUniversalFalse: value === false
127125
};
128126
}
127+
128+
129+
/**
130+
* Normalizes a condition before execution.
131+
* Handles special logic like null-equality conversion, case handling, etc.
132+
*/
133+
function normalizeCondition(condition) {
134+
const normalized = { ...condition };
135+
136+
if (
137+
(condition.operation === QUERY_OPERATIONS.EQUAL || condition.operation === QUERY_OPERATIONS.NOT_EQUAL) &&
138+
(condition.value === null || condition.value === undefined)
139+
) {
140+
normalized.operation = condition.operation === QUERY_OPERATIONS.EQUAL
141+
? QUERY_OPERATIONS.IS_NULL
142+
: QUERY_OPERATIONS.IS_NOT_NULL;
143+
144+
// Leave value in place to pass validation
145+
normalized.value = null;
146+
}
147+
148+
return normalized;
149+
}
150+

0 commit comments

Comments
 (0)