// ignore_for_file: invalid_use_of_protected_member, public_member_api_docs import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:isar/isar.dart'; import 'package:isar/src/native/bindings.dart'; import 'package:isar/src/native/encode_string.dart'; import 'package:isar/src/native/index_key.dart'; import 'package:isar/src/native/isar_collection_impl.dart'; import 'package:isar/src/native/isar_core.dart'; import 'package:isar/src/native/isar_writer_impl.dart'; import 'package:isar/src/native/query_impl.dart'; final Pointer maxStr = '\u{FFFFF}'.toNativeUtf8().cast(); Query buildNativeQuery( IsarCollectionImpl col, List whereClauses, bool whereDistinct, Sort whereSort, FilterOperation? filter, List sortBy, List distinctBy, int? offset, int? limit, String? property, ) { final qbPtr = IC.isar_qb_create(col.ptr); for (final wc in whereClauses) { if (wc is IdWhereClause) { _addIdWhereClause(qbPtr, wc, whereSort); } else if (wc is IndexWhereClause) { _addIndexWhereClause( col.schema, qbPtr, wc, whereDistinct, whereSort, ); } else { _addLinkWhereClause(col.isar, qbPtr, wc as LinkWhereClause); } } if (filter != null) { final alloc = Arena(malloc); try { final filterPtr = _buildFilter(col, null, filter, alloc); if (filterPtr != null) { IC.isar_qb_set_filter(qbPtr, filterPtr); } } finally { alloc.releaseAll(); } } for (final sortProperty in sortBy) { final property = col.schema.property(sortProperty.property); nCall( IC.isar_qb_add_sort_by( qbPtr, property.id, sortProperty.sort == Sort.asc, ), ); } if (offset != null || limit != null) { IC.isar_qb_set_offset_limit(qbPtr, offset ?? -1, limit ?? -1); } for (final distinctByProperty in distinctBy) { final property = col.schema.property(distinctByProperty.property); nCall( IC.isar_qb_add_distinct_by( qbPtr, property.id, distinctByProperty.caseSensitive ?? true, ), ); } QueryDeserialize deserialize; int? propertyId; if (property == null) { deserialize = (col as IsarCollectionImpl).deserializeObjects; } else { propertyId = property != col.schema.idName ? col.schema.property(property).id : null; deserialize = (CObjectSet cObjSet) => col.deserializeProperty(cObjSet, propertyId); } final queryPtr = IC.isar_qb_build(qbPtr); return QueryImpl(col, queryPtr, deserialize, propertyId); } void _addIdWhereClause( Pointer qbPtr, IdWhereClause wc, Sort sort, ) { final lower = (wc.lower ?? minLong) + (wc.includeLower ? 0 : 1); final upper = (wc.upper ?? maxLong) - (wc.includeUpper ? 0 : 1); nCall( IC.isar_qb_add_id_where_clause( qbPtr, sort == Sort.asc ? lower : upper, sort == Sort.asc ? upper : lower, ), ); } Pointer? _buildLowerIndexBound( CollectionSchema schema, IndexSchema index, IndexWhereClause wc, ) { if (wc.lower == null) { return buildLowerUnboundedIndexKey(); } final firstVal = wc.lower!.length == 1 ? wc.lower!.first : null; if (firstVal is double) { final adjusted = adjustFloatBound( value: firstVal, lowerBound: true, include: wc.includeLower, epsilon: wc.epsilon, ); if (adjusted == null) { return null; } return buildIndexKey(schema, index, [adjusted]); } else { final lowerPtr = buildIndexKey(schema, index, wc.lower!); if (!wc.includeLower) { if (!IC.isar_key_increase(lowerPtr)) { return null; } } return lowerPtr; } } Pointer? _buildUpperIndexBound( CollectionSchema schema, IndexSchema index, IndexWhereClause wc, ) { if (wc.upper == null) { return buildUpperUnboundedIndexKey(); } final firstVal = wc.upper!.length == 1 ? wc.upper!.first : null; if (firstVal is double) { final adjusted = adjustFloatBound( value: firstVal, lowerBound: false, include: wc.includeUpper, epsilon: wc.epsilon, ); if (adjusted == null) { return null; } else { return buildIndexKey(schema, index, [adjusted]); } } else { final upperPtr = buildIndexKey(schema, index, wc.upper!); if (!wc.includeUpper) { if (!IC.isar_key_decrease(upperPtr)) { return null; } } // Also include composite indexes for upper keys if (index.properties.length > wc.upper!.length) { IC.isar_key_add_long(upperPtr, maxLong); } return upperPtr; } } void _addIndexWhereClause( CollectionSchema schema, Pointer qbPtr, IndexWhereClause wc, bool distinct, Sort sort, ) { final index = schema.index(wc.indexName); final lowerPtr = _buildLowerIndexBound(schema, index, wc); final upperPtr = _buildUpperIndexBound(schema, index, wc); if (lowerPtr != null && upperPtr != null) { nCall( IC.isar_qb_add_index_where_clause( qbPtr, schema.index(wc.indexName).id, lowerPtr, upperPtr, sort == Sort.asc, distinct, ), ); } else { // this where clause does not match any objects nCall( IC.isar_qb_add_id_where_clause( qbPtr, Isar.autoIncrement, Isar.autoIncrement, ), ); } } void _addLinkWhereClause( Isar isar, Pointer qbPtr, LinkWhereClause wc, ) { final linkCol = isar.getCollectionByNameInternal(wc.linkCollection)!; linkCol as IsarCollectionImpl; final linkId = linkCol.schema.link(wc.linkName).id; nCall(IC.isar_qb_add_link_where_clause(qbPtr, linkCol.ptr, linkId, wc.id)); } Pointer? _buildFilter( IsarCollectionImpl col, Schema? embeddedCol, FilterOperation filter, Allocator alloc, ) { if (filter is FilterGroup) { return _buildFilterGroup(col, embeddedCol, filter, alloc); } else if (filter is LinkFilter) { return _buildLink(col, filter, alloc); } else if (filter is ObjectFilter) { return _buildObject(col, embeddedCol, filter, alloc); } else if (filter is FilterCondition) { return _buildCondition(col, embeddedCol, filter, alloc); } else { return null; } } Pointer? _buildFilterGroup( IsarCollectionImpl col, Schema? embeddedCol, FilterGroup group, Allocator alloc, ) { final builtConditions = group.filters .map((FilterOperation op) => _buildFilter(col, embeddedCol, op, alloc)) .where((Pointer? it) => it != null) .toList(); if (builtConditions.isEmpty) { return null; } final filterPtrPtr = alloc>(); if (group.type == FilterGroupType.not) { IC.isar_filter_not( filterPtrPtr, builtConditions.first!, ); } else if (builtConditions.length == 1) { return builtConditions[0]; } else { final conditionsPtrPtr = alloc>(builtConditions.length); for (var i = 0; i < builtConditions.length; i++) { conditionsPtrPtr[i] = builtConditions[i]!; } IC.isar_filter_and_or_xor( filterPtrPtr, group.type == FilterGroupType.and, group.type == FilterGroupType.xor, conditionsPtrPtr, builtConditions.length, ); } return filterPtrPtr.value; } Pointer? _buildLink( IsarCollectionImpl col, LinkFilter link, Allocator alloc, ) { final linkSchema = col.schema.link(link.linkName); final linkTargetCol = col.isar.getCollectionByNameInternal(linkSchema.target)!; final linkId = col.schema.link(link.linkName).id; final filterPtrPtr = alloc>(); if (link.filter != null) { final condition = _buildFilter( linkTargetCol as IsarCollectionImpl, null, link.filter!, alloc, ); if (condition == null) { return null; } nCall( IC.isar_filter_link( col.ptr, filterPtrPtr, condition, linkId, ), ); } else { nCall( IC.isar_filter_link_length( col.ptr, filterPtrPtr, link.lower!, link.upper!, linkId, ), ); } return filterPtrPtr.value; } Pointer? _buildObject( IsarCollectionImpl col, Schema? embeddedCol, ObjectFilter objectFilter, Allocator alloc, ) { final property = (embeddedCol ?? col.schema).property(objectFilter.property); final condition = _buildFilter( col, col.schema.embeddedSchemas[property.target], objectFilter.filter, alloc, ); if (condition == null) { return null; } final filterPtrPtr = alloc>(); nCall( IC.isar_filter_object( col.ptr, filterPtrPtr, condition, embeddedCol?.id ?? 0, property.id, ), ); return filterPtrPtr.value; } Object _prepareValue( Object? value, Allocator alloc, IsarType type, Map? enumMap, ) { if (value is bool) { return value.byteValue; } else if (value is DateTime) { return value.longValue; } else if (value is Enum) { return _prepareValue(enumMap![value.name], alloc, type, null); } else if (value is String) { return value.toCString(alloc); } else if (value == null) { switch (type) { case IsarType.bool: case IsarType.byte: case IsarType.boolList: case IsarType.byteList: return minByte; case IsarType.int: case IsarType.intList: return minInt; case IsarType.long: case IsarType.longList: case IsarType.dateTime: case IsarType.dateTimeList: return minLong; case IsarType.float: case IsarType.double: case IsarType.floatList: case IsarType.doubleList: return minDouble; case IsarType.string: case IsarType.stringList: case IsarType.object: case IsarType.objectList: return nullptr; } } else { return value; } } Pointer _buildCondition( IsarCollectionImpl col, Schema? embeddedCol, FilterCondition condition, Allocator alloc, ) { final property = condition.property != col.schema.idName ? (embeddedCol ?? col.schema).property(condition.property) : null; final value1 = _prepareValue( condition.value1, alloc, property?.type ?? IsarType.long, property?.enumMap, ); final value2 = _prepareValue( condition.value2, alloc, property?.type ?? IsarType.long, property?.enumMap, ); final filterPtr = alloc>(); switch (condition.type) { case FilterConditionType.equalTo: _buildConditionEqual( colPtr: col.ptr, filterPtr: filterPtr, embeddedColId: embeddedCol?.id, propertyId: property?.id, val: value1, caseSensitive: condition.caseSensitive, epsilon: condition.epsilon, ); break; case FilterConditionType.between: _buildConditionBetween( colPtr: col.ptr, filterPtr: filterPtr, embeddedColId: embeddedCol?.id, propertyId: property?.id, lower: value1, includeLower: condition.include1, upper: value2, includeUpper: condition.include2, caseSensitive: condition.caseSensitive, epsilon: condition.epsilon, ); break; case FilterConditionType.lessThan: _buildConditionLessThan( colPtr: col.ptr, filterPtr: filterPtr, embeddedColId: embeddedCol?.id, propertyId: property?.id, val: value1, include: condition.include1, caseSensitive: condition.caseSensitive, epsilon: condition.epsilon, ); break; case FilterConditionType.greaterThan: _buildConditionGreaterThan( colPtr: col.ptr, filterPtr: filterPtr, embeddedColId: embeddedCol?.id, propertyId: property?.id, val: value1, include: condition.include1, caseSensitive: condition.caseSensitive, epsilon: condition.epsilon, ); break; case FilterConditionType.startsWith: case FilterConditionType.endsWith: case FilterConditionType.contains: case FilterConditionType.matches: _buildConditionStringOp( colPtr: col.ptr, filterPtr: filterPtr, conditionType: condition.type, embeddedColId: embeddedCol?.id, propertyId: property?.id, val: value1, include: condition.include1, caseSensitive: condition.caseSensitive, ); break; case FilterConditionType.isNull: _buildConditionIsNull( colPtr: col.ptr, filterPtr: filterPtr, embeddedColId: embeddedCol?.id, propertyId: property?.id, ); break; case FilterConditionType.isNotNull: _buildConditionIsNotNull( colPtr: col.ptr, filterPtr: filterPtr, embeddedColId: embeddedCol?.id, propertyId: property?.id, alloc: alloc, ); break; case FilterConditionType.elementIsNull: _buildConditionElementIsNull( colPtr: col.ptr, filterPtr: filterPtr, embeddedColId: embeddedCol?.id, propertyId: property?.id, isObjectList: property?.type == IsarType.objectList, nullValue: value1, ); break; case FilterConditionType.elementIsNotNull: _buildConditionElementIsNotNull( colPtr: col.ptr, filterPtr: filterPtr, embeddedColId: embeddedCol?.id, propertyId: property?.id, isObjectList: property?.type == IsarType.objectList, nullValue: value1, alloc: alloc, ); break; case FilterConditionType.listLength: _buildListLength( colPtr: col.ptr, filterPtr: filterPtr, embeddedColId: embeddedCol?.id, propertyId: property?.id, lower: value1, upper: value2, ); break; } return filterPtr.value; } void _buildConditionIsNull({ required Pointer colPtr, required Pointer> filterPtr, required int? embeddedColId, required int? propertyId, }) { if (propertyId != null) { nCall( IC.isar_filter_null( colPtr, filterPtr, embeddedColId ?? 0, propertyId, ), ); } else { IC.isar_filter_static(filterPtr, false); } } void _buildConditionIsNotNull({ required Pointer colPtr, required Pointer> filterPtr, required int? embeddedColId, required int? propertyId, required Allocator alloc, }) { if (propertyId != null) { final conditionPtr = alloc>(); nCall( IC.isar_filter_null( colPtr, conditionPtr, embeddedColId ?? 0, propertyId, ), ); IC.isar_filter_not(filterPtr, conditionPtr.value); } else { IC.isar_filter_static(filterPtr, true); } } void _buildConditionElementIsNull({ required Pointer colPtr, required Pointer> filterPtr, required int? embeddedColId, required int? propertyId, required bool isObjectList, required Object nullValue, }) { if (isObjectList) { IC.isar_filter_object( colPtr, filterPtr, nullptr, embeddedColId ?? 0, propertyId ?? 0, ); } else { _buildConditionEqual( colPtr: colPtr, filterPtr: filterPtr, embeddedColId: embeddedColId, propertyId: propertyId, val: nullValue, epsilon: 0, caseSensitive: true, ); } } void _buildConditionElementIsNotNull({ required Pointer colPtr, required Pointer> filterPtr, required int? embeddedColId, required int? propertyId, required bool isObjectList, required Object nullValue, required Allocator alloc, }) { if (isObjectList) { final objFilterPtrPtr = alloc>(); IC.isar_filter_static(objFilterPtrPtr, true); IC.isar_filter_object( colPtr, filterPtr, objFilterPtrPtr.value, embeddedColId ?? 0, propertyId ?? 0, ); } else { _buildConditionGreaterThan( colPtr: colPtr, filterPtr: filterPtr, embeddedColId: embeddedColId, propertyId: propertyId, val: nullValue, include: false, epsilon: 0, caseSensitive: true, ); } } void _buildConditionEqual({ required Pointer colPtr, required Pointer> filterPtr, required int? embeddedColId, required int? propertyId, required Object val, required bool caseSensitive, required double epsilon, }) { if (val is int) { if (propertyId == null) { IC.isar_filter_id(filterPtr, val, true, val, true); } else { nCall( IC.isar_filter_long( colPtr, filterPtr, val, true, val, true, embeddedColId ?? 0, propertyId, ), ); } } else if (val is double) { final lower = adjustFloatBound( value: val, lowerBound: true, include: true, epsilon: epsilon, ); final upper = adjustFloatBound( value: val, lowerBound: false, include: true, epsilon: epsilon, ); if (lower == null || upper == null) { IC.isar_filter_static(filterPtr, false); } else { nCall( IC.isar_filter_double( colPtr, filterPtr, lower, upper, embeddedColId ?? 0, propertyId!, ), ); } } else if (val is Pointer) { nCall( IC.isar_filter_string( colPtr, filterPtr, val, true, val, true, caseSensitive, embeddedColId ?? 0, propertyId!, ), ); } else { throw IsarError('Unsupported type for condition'); } } void _buildConditionBetween({ required Pointer colPtr, required Pointer> filterPtr, required int? embeddedColId, required int? propertyId, required Object lower, required bool includeLower, required Object upper, required bool includeUpper, required bool caseSensitive, required double epsilon, }) { if (lower is int && upper is int) { if (propertyId == null) { IC.isar_filter_id(filterPtr, lower, includeLower, upper, includeUpper); } else { nCall( IC.isar_filter_long( colPtr, filterPtr, lower, includeLower, upper, includeUpper, embeddedColId ?? 0, propertyId, ), ); } } else if (lower is double && upper is double) { final adjustedLower = adjustFloatBound( value: lower, lowerBound: true, include: includeLower, epsilon: epsilon, ); final adjustedUpper = adjustFloatBound( value: upper, lowerBound: false, include: includeUpper, epsilon: epsilon, ); if (adjustedLower == null || adjustedUpper == null) { IC.isar_filter_static(filterPtr, false); } else { nCall( IC.isar_filter_double( colPtr, filterPtr, adjustedLower, adjustedUpper, embeddedColId ?? 0, propertyId!, ), ); } } else if (lower is Pointer && upper is Pointer) { nCall( IC.isar_filter_string( colPtr, filterPtr, lower, includeLower, upper, includeUpper, caseSensitive, embeddedColId ?? 0, propertyId!, ), ); } else { throw IsarError('Unsupported type for condition'); } } void _buildConditionLessThan({ required Pointer colPtr, required Pointer> filterPtr, required int? embeddedColId, required int? propertyId, required Object val, required bool include, required bool caseSensitive, required double epsilon, }) { if (val is int) { if (propertyId == null) { IC.isar_filter_id(filterPtr, minLong, true, val, include); } else { nCall( IC.isar_filter_long( colPtr, filterPtr, minLong, true, val, include, embeddedColId ?? 0, propertyId, ), ); } } else if (val is double) { final upper = adjustFloatBound( value: val, lowerBound: false, include: include, epsilon: epsilon, ); if (upper == null) { IC.isar_filter_static(filterPtr, false); } else { nCall( IC.isar_filter_double( colPtr, filterPtr, minDouble, upper, embeddedColId ?? 0, propertyId!, ), ); } } else if (val is Pointer) { nCall( IC.isar_filter_string( colPtr, filterPtr, nullptr, true, val, include, caseSensitive, embeddedColId ?? 0, propertyId!, ), ); } else { throw IsarError('Unsupported type for condition'); } } void _buildConditionGreaterThan({ required Pointer colPtr, required Pointer> filterPtr, required int? embeddedColId, required int? propertyId, required Object val, required bool include, required bool caseSensitive, required double epsilon, }) { if (val is int) { if (propertyId == null) { IC.isar_filter_id(filterPtr, val, include, maxLong, true); } else { nCall( IC.isar_filter_long( colPtr, filterPtr, val, include, maxLong, true, embeddedColId ?? 0, propertyId, ), ); } } else if (val is double) { final lower = adjustFloatBound( value: val, lowerBound: true, include: include, epsilon: epsilon, ); if (lower == null) { IC.isar_filter_static(filterPtr, false); } else { nCall( IC.isar_filter_double( colPtr, filterPtr, lower, maxDouble, embeddedColId ?? 0, propertyId!, ), ); } } else if (val is Pointer) { nCall( IC.isar_filter_string( colPtr, filterPtr, val, include, maxStr, true, caseSensitive, embeddedColId ?? 0, propertyId!, ), ); } else { throw IsarError('Unsupported type for condition'); } } void _buildConditionStringOp({ required Pointer colPtr, required Pointer> filterPtr, required FilterConditionType conditionType, required int? embeddedColId, required int? propertyId, required Object val, required bool include, required bool caseSensitive, }) { if (val is Pointer) { if (val.isNull) { throw IsarError('String operation value must not be null'); } // ignore: missing_enum_constant_in_switch switch (conditionType) { case FilterConditionType.startsWith: nCall( IC.isar_filter_string_starts_with( colPtr, filterPtr, val, caseSensitive, embeddedColId ?? 0, propertyId!, ), ); break; case FilterConditionType.endsWith: nCall( IC.isar_filter_string_ends_with( colPtr, filterPtr, val, caseSensitive, embeddedColId ?? 0, propertyId!, ), ); break; case FilterConditionType.contains: nCall( IC.isar_filter_string_contains( colPtr, filterPtr, val, caseSensitive, embeddedColId ?? 0, propertyId!, ), ); break; case FilterConditionType.matches: nCall( IC.isar_filter_string_matches( colPtr, filterPtr, val, caseSensitive, embeddedColId ?? 0, propertyId!, ), ); break; } } else { throw IsarError('Unsupported type for condition'); } } void _buildListLength({ required Pointer colPtr, required Pointer> filterPtr, required int? embeddedColId, required int? propertyId, required Object? lower, required Object? upper, }) { if (lower is int && upper is int) { nCall( IC.isar_filter_list_length( colPtr, filterPtr, lower, upper, embeddedColId ?? 0, propertyId!, ), ); } else { throw IsarError('Unsupported type for condition'); } }