This commit is contained in:
4831c0 2025-03-19 19:41:35 +01:00
commit 47c37c5e74
72 changed files with 3805 additions and 0 deletions
CHANGELOG.mdLICENSEREADME.mdanalysis_options.yamlbuild.yaml
lib
pubspec.yaml
test

1
CHANGELOG.md Normal file

@ -0,0 +1 @@
See [Isar Changelog](https://pub.dev/packages/isar/changelog)

202
LICENSE Normal file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

1
README.md Normal file

@ -0,0 +1 @@
### Code generator for the [Isar Database](https://github.com/isar/isar) please go there for documentation.

9
analysis_options.yaml Normal file

@ -0,0 +1,9 @@
include: package:very_good_analysis/analysis_options.yaml
analyzer:
errors:
cascade_invocations: ignore
avoid_positional_boolean_parameters: ignore
parameter_assignments: ignore
public_member_api_docs: ignore
use_string_buffers: ignore

8
build.yaml Normal file

@ -0,0 +1,8 @@
builders:
isar_generator:
import: "package:isar_generator/isar_generator.dart"
builder_factories: ["getIsarGenerator"]
build_extensions: { ".dart": ["isar_generator.g.part"] }
auto_apply: dependents
build_to: cache
applies_builders: ["source_gen|combining_builder"]

11
lib/isar_generator.dart Normal file

@ -0,0 +1,11 @@
import 'package:build/build.dart';
import 'package:isar_generator/src/collection_generator.dart';
import 'package:source_gen/source_gen.dart';
Builder getIsarGenerator(BuilderOptions options) => SharedPartBuilder(
[
IsarCollectionGenerator(),
IsarEmbeddedGenerator(),
],
'isar_generator',
);

@ -0,0 +1,119 @@
import 'package:dartx/dartx.dart';
import 'package:isar_generator/src/object_info.dart';
String generateByIndexExtension(ObjectInfo oi) {
final uniqueIndexes = oi.indexes.where((e) => e.unique).toList();
if (uniqueIndexes.isEmpty) {
return '';
}
var code =
'extension ${oi.dartName}ByIndex on IsarCollection<${oi.dartName}> {';
for (final index in uniqueIndexes) {
code += generateSingleByIndex(oi, index);
code += generateAllByIndex(oi, index);
if (!index.properties.first.isMultiEntry) {
code += generatePutByIndex(oi, index);
}
}
return '''
$code
}''';
}
extension on ObjectIndex {
String get dartName {
return properties.map((e) => e.property.dartName.capitalize()).join();
}
}
String generateSingleByIndex(ObjectInfo oi, ObjectIndex index) {
final params = index.properties
.map((i) => '${i.property.dartType} ${i.property.dartName}')
.join(',');
final paramsList = index.properties.map((i) => i.property.dartName).join(',');
return '''
Future<${oi.dartName}?> getBy${index.dartName}($params) {
return getByIndex(r'${index.name}', [$paramsList]);
}
${oi.dartName}? getBy${index.dartName}Sync($params) {
return getByIndexSync(r'${index.name}', [$paramsList]);
}
Future<bool> deleteBy${index.dartName}($params) {
return deleteByIndex(r'${index.name}', [$paramsList]);
}
bool deleteBy${index.dartName}Sync($params) {
return deleteByIndexSync(r'${index.name}', [$paramsList]);
}
''';
}
String generateAllByIndex(ObjectInfo oi, ObjectIndex index) {
String valsName(ObjectProperty p) => '${p.dartName}Values';
final props = index.properties;
final params = props
.map((ip) => 'List<${ip.property.dartType}> ${valsName(ip.property)}')
.join(',');
String createValues;
if (props.length == 1) {
final p = props.first.property;
createValues = 'final values = ${valsName(p)}.map((e) => [e]).toList();';
} else {
final lenAssert = props
.sublist(1)
.map((i) => '${valsName(i.property)}.length == len')
.join('&&');
createValues = '''
final len = ${valsName(props.first.property)}.length;
assert($lenAssert, 'All index values must have the same length');
final values = <List<dynamic>>[];
for (var i = 0; i < len; i++) {
values.add([${props.map((ip) => '${valsName(ip.property)}[i]').join(',')}]);
}
''';
}
return '''
Future<List<${oi.dartName}?>> getAllBy${index.dartName}($params) {
$createValues
return getAllByIndex(r'${index.name}', values);
}
List<${oi.dartName}?> getAllBy${index.dartName}Sync($params) {
$createValues
return getAllByIndexSync(r'${index.name}', values);
}
Future<int> deleteAllBy${index.dartName}($params) {
$createValues
return deleteAllByIndex(r'${index.name}', values);
}
int deleteAllBy${index.dartName}Sync($params) {
$createValues
return deleteAllByIndexSync(r'${index.name}', values);
}
''';
}
String generatePutByIndex(ObjectInfo oi, ObjectIndex index) {
return '''
Future<Id> putBy${index.dartName}(${oi.dartName} object) {
return putByIndex(r'${index.name}', object);
}
Id putBy${index.dartName}Sync(${oi.dartName} object, {bool saveLinks = true}) {
return putByIndexSync(r'${index.name}', object, saveLinks: saveLinks);
}
Future<List<Id>> putAllBy${index.dartName}(List<${oi.dartName}> objects) {
return putAllByIndex(r'${index.name}', objects);
}
List<Id> putAllBy${index.dartName}Sync(List<${oi.dartName}> objects, {bool saveLinks = true}) {
return putAllByIndexSync(r'${index.name}', objects, saveLinks: saveLinks);
}
''';
}

@ -0,0 +1,112 @@
import 'package:dartx/dartx.dart';
import 'package:isar/isar.dart';
import 'package:isar_generator/src/isar_type.dart';
import 'package:isar_generator/src/object_info.dart';
String generateSchema(ObjectInfo object) {
var code = 'const ${object.dartName.capitalize()}Schema = ';
if (!object.isEmbedded) {
code += 'CollectionSchema(';
} else {
code += 'Schema(';
}
final properties = object.objectProperties
.mapIndexed(
(i, e) => "r'${e.isarName}': ${_generatePropertySchema(object, i)}",
)
.join(',');
code += '''
name: r'${object.isarName}',
id: ${object.id},
properties: {$properties},
estimateSize: ${object.estimateSizeName},
serialize: ${object.serializeName},
deserialize: ${object.deserializeName},
deserializeProp: ${object.deserializePropName},''';
if (!object.isEmbedded) {
final indexes = object.indexes
.map((e) => "r'${e.name}': ${_generateIndexSchema(e)}")
.join(',');
final links = object.links
.map((e) => "r'${e.isarName}': ${_generateLinkSchema(object, e)}")
.join(',');
final embeddedSchemas = object.embeddedDartNames.entries
.map((e) => "r'${e.key}': ${e.value.capitalize()}Schema")
.join(',');
code += '''
idName: r'${object.idProperty.isarName}',
indexes: {$indexes},
links: {$links},
embeddedSchemas: {$embeddedSchemas},
getId: ${object.getIdName},
getLinks: ${object.getLinksName},
attach: ${object.attachName},
version: '${Isar.version}',
''';
}
return '$code);';
}
String _generatePropertySchema(ObjectInfo object, int index) {
final property = object.objectProperties[index];
var enumMap = '';
if (property.isEnum) {
enumMap = 'enumMap: ${property.enumValueMapName(object)},';
}
var target = '';
if (property.targetIsarName != null) {
target = "target: r'${property.targetIsarName}',";
}
return '''
PropertySchema(
id: $index,
name: r'${property.isarName}',
type: IsarType.${property.isarType.name},
$enumMap
$target
)
''';
}
String _generateIndexSchema(ObjectIndex index) {
final properties = index.properties.map((e) {
return '''
IndexPropertySchema(
name: r'${e.property.isarName}',
type: IndexType.${e.type.name},
caseSensitive: ${e.caseSensitive},
)''';
}).join(',');
return '''
IndexSchema(
id: ${index.id},
name: r'${index.name}',
unique: ${index.unique},
replace: ${index.replace},
properties: [$properties],
)''';
}
String _generateLinkSchema(ObjectInfo object, ObjectLink link) {
var linkName = '';
if (link.isBacklink) {
linkName = "linkName: r'${link.targetLinkIsarName}',";
}
return '''
LinkSchema(
id: ${link.id(object.isarName)},
name: r'${link.isarName}',
target: r'${link.targetCollectionIsarName}',
single: ${link.isSingle},
$linkName
)''';
}

@ -0,0 +1,27 @@
import 'package:dartx/dartx.dart';
import 'package:isar/isar.dart';
import 'package:isar_generator/src/isar_type.dart';
import 'package:isar_generator/src/object_info.dart';
String generateDistinctBy(ObjectInfo oi) {
var code = '''
extension ${oi.dartName}QueryWhereDistinct on QueryBuilder<${oi.dartName}, ${oi.dartName}, QDistinct> {''';
for (final property in oi.objectProperties) {
if (property.isarType == IsarType.string) {
code += '''
QueryBuilder<${oi.dartName}, ${oi.dartName}, QDistinct>distinctBy${property.dartName.capitalize()}({bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'${property.isarName}', caseSensitive: caseSensitive);
});
}''';
} else if (!property.isarType.containsObject) {
code += '''
QueryBuilder<${oi.dartName}, ${oi.dartName}, QDistinct>distinctBy${property.dartName.capitalize()}() {
return QueryBuilder.apply(this, (query) {
return query.addDistinctBy(r'${property.isarName}');
});
}''';
}
}
return '$code}';
}

@ -0,0 +1,278 @@
import 'package:dartx/dartx.dart';
import 'package:isar/isar.dart';
import 'package:isar_generator/src/code_gen/query_filter_length.dart';
import 'package:isar_generator/src/isar_type.dart';
import 'package:isar_generator/src/object_info.dart';
class FilterGenerator {
FilterGenerator(this.object) : objName = object.dartName;
final ObjectInfo object;
final String objName;
String generate() {
var code =
'extension ${objName}QueryFilter on QueryBuilder<$objName, $objName, '
'QFilterCondition> {';
for (final property in object.properties) {
if (property.nullable) {
code += generateIsNull(property);
code += generateIsNotNull(property);
}
if (property.elementNullable) {
code += generateElementIsNull(property);
code += generateElementIsNotNull(property);
}
if (!property.isarType.containsObject) {
code += generateEqualTo(property);
if (!property.isarType.containsBool) {
code += generateGreaterThan(property);
code += generateLessThan(property);
code += generateBetween(property);
}
}
if (property.isarType.containsString) {
code += generateStringStartsWith(property);
code += generateStringEndsWith(property);
code += generateStringContains(property);
code += generateStringMatches(property);
code += generateStringIsEmpty(property);
code += generateStringIsNotEmpty(property);
}
if (property.isarType.isList) {
code += generateListLength(property);
}
}
return '''
$code
}''';
}
String mPrefix(ObjectProperty p, [bool listElement = true]) {
final any = listElement && p.isarType.isList ? 'Element' : '';
return 'QueryBuilder<$objName, $objName, QAfterFilterCondition> '
'${p.dartName.decapitalize()}$any';
}
String generateEqualTo(ObjectProperty p) {
final optional = [
if (p.isarType.containsString) 'bool caseSensitive = true',
if (p.isarType.containsFloat) 'double epsilon = Query.epsilon',
].join(',');
return '''
${mPrefix(p)}EqualTo(${p.nScalarDartType} value ${optional.isNotBlank ? ', {$optional,}' : ''}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'${p.isarName}',
value: value,
${p.isarType.containsString ? 'caseSensitive: caseSensitive,' : ''}
${p.isarType.containsFloat ? 'epsilon: epsilon,' : ''}
));
});
}''';
}
String generateGreaterThan(ObjectProperty p) {
final optional = [
'bool include = false',
if (p.isarType.containsString) 'bool caseSensitive = true',
if (p.isarType.containsFloat) 'double epsilon = Query.epsilon',
].join(',');
return '''
${mPrefix(p)}GreaterThan(${p.nScalarDartType} value, {$optional,}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
include: include,
property: r'${p.isarName}',
value: value,
${p.isarType.containsString ? 'caseSensitive: caseSensitive,' : ''}
${p.isarType.containsFloat ? 'epsilon: epsilon,' : ''}
));
});
}''';
}
String generateLessThan(ObjectProperty p) {
final optional = [
'bool include = false',
if (p.isarType.containsString) 'bool caseSensitive = true',
if (p.isarType.containsFloat) 'double epsilon = Query.epsilon',
].join(',');
return '''
${mPrefix(p)}LessThan(${p.nScalarDartType} value, {$optional,}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.lessThan(
include: include,
property: r'${p.isarName}',
value: value,
${p.isarType.containsString ? 'caseSensitive: caseSensitive,' : ''}
${p.isarType.containsFloat ? 'epsilon: epsilon,' : ''}
));
});
}''';
}
String generateBetween(ObjectProperty p) {
final optional = [
'bool includeLower = true',
'bool includeUpper = true',
if (p.isarType.containsString) 'bool caseSensitive = true',
if (p.isarType.containsFloat) 'double epsilon = Query.epsilon',
].join(',');
return '''
${mPrefix(p)}Between(${p.nScalarDartType} lower, ${p.nScalarDartType} upper, {$optional,}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.between(
property: r'${p.isarName}',
lower: lower,
includeLower: includeLower,
upper: upper,
includeUpper: includeUpper,
${p.isarType.containsString ? 'caseSensitive: caseSensitive,' : ''}
${p.isarType.containsFloat ? 'epsilon: epsilon,' : ''}
));
});
}''';
}
String generateIsNull(ObjectProperty p) {
return '''
${mPrefix(p, false)}IsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.isNull(
property: r'${p.isarName}',
));
});
}''';
}
String generateElementIsNull(ObjectProperty p) {
return '''
${mPrefix(p)}IsNull() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(const FilterCondition.elementIsNull(
property: r'${p.isarName}',
));
});
}''';
}
String generateIsNotNull(ObjectProperty p) {
return '''
${mPrefix(p, false)}IsNotNull() {
return QueryBuilder.apply(this, (query) {
return query
.addFilterCondition(const FilterCondition.isNotNull(
property: r'${p.isarName}',
));
});
}''';
}
String generateElementIsNotNull(ObjectProperty p) {
return '''
${mPrefix(p)}IsNotNull() {
return QueryBuilder.apply(this, (query) {
return query
.addFilterCondition(const FilterCondition.elementIsNotNull(
property: r'${p.isarName}',
));
});
}''';
}
String generateStringStartsWith(ObjectProperty p) {
return '''
${mPrefix(p)}StartsWith(String value, {bool caseSensitive = true,}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.startsWith(
property: r'${p.isarName}',
value: value,
caseSensitive: caseSensitive,
));
});
}''';
}
String generateStringEndsWith(ObjectProperty p) {
return '''
${mPrefix(p)}EndsWith(String value, {bool caseSensitive = true,}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.endsWith(
property: r'${p.isarName}',
value: value,
caseSensitive: caseSensitive,
));
});
}''';
}
String generateStringContains(ObjectProperty p) {
return '''
${mPrefix(p)}Contains(String value, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.contains(
property: r'${p.isarName}',
value: value,
caseSensitive: caseSensitive,
));
});
}''';
}
String generateStringMatches(ObjectProperty p) {
return '''
${mPrefix(p)}Matches(String pattern, {bool caseSensitive = true}) {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.matches(
property: r'${p.isarName}',
wildcard: pattern,
caseSensitive: caseSensitive,
));
});
}''';
}
String generateStringIsEmpty(ObjectProperty p) {
return '''
${mPrefix(p)}IsEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.equalTo(
property: r'${p.isarName}',
value: '',
));
});
}''';
}
String generateStringIsNotEmpty(ObjectProperty p) {
return '''
${mPrefix(p)}IsNotEmpty() {
return QueryBuilder.apply(this, (query) {
return query.addFilterCondition(FilterCondition.greaterThan(
property: r'${p.isarName}',
value: '',
));
});
}''';
}
String generateListLength(ObjectProperty p) {
return generateLength(objName, p.dartName,
(lower, includeLower, upper, includeUpper) {
return '''
QueryBuilder.apply(this, (query) {
return query.listLength(
r'${p.isarName}',
$lower,
$includeLower,
$upper,
$includeUpper,
);
})''';
});
}
}

@ -0,0 +1,50 @@
import 'package:dartx/dartx.dart';
String generateLength(
String objectName,
String propertyName,
String Function(
String lower,
String includeLower,
String upper,
String includeUpper,
)
codeGen,
) {
return '''
QueryBuilder<$objectName, $objectName, QAfterFilterCondition> ${propertyName.decapitalize()}LengthEqualTo(int length) {
return ${codeGen('length', 'true', 'length', 'true')};
}
QueryBuilder<$objectName, $objectName, QAfterFilterCondition> ${propertyName.decapitalize()}IsEmpty() {
return ${codeGen('0', 'true', '0', 'true')};
}
QueryBuilder<$objectName, $objectName, QAfterFilterCondition> ${propertyName.decapitalize()}IsNotEmpty() {
return ${codeGen('0', 'false', '999999', 'true')};
}
QueryBuilder<$objectName, $objectName, QAfterFilterCondition> ${propertyName.decapitalize()}LengthLessThan(
int length, {
bool include = false,
}) {
return ${codeGen('0', 'true', 'length', 'include')};
}
QueryBuilder<$objectName, $objectName, QAfterFilterCondition> ${propertyName.decapitalize()}LengthGreaterThan(
int length, {
bool include = false,
}) {
return ${codeGen('length', 'include', '999999', 'true')};
}
QueryBuilder<$objectName, $objectName, QAfterFilterCondition> ${propertyName.decapitalize()}LengthBetween(
int lower,
int upper, {
bool includeLower = true,
bool includeUpper = true,
}) {
return ${codeGen('lower', 'includeLower', 'upper', 'includeUpper')};
}
''';
}

@ -0,0 +1,38 @@
import 'package:dartx/dartx.dart';
import 'package:isar_generator/src/code_gen/query_filter_length.dart';
import 'package:isar_generator/src/object_info.dart';
String generateQueryLinks(ObjectInfo oi) {
var code =
'extension ${oi.dartName}QueryLinks on QueryBuilder<${oi.dartName}, '
'${oi.dartName}, QFilterCondition> {';
for (final link in oi.links) {
code += '''
QueryBuilder<${oi.dartName}, ${oi.dartName}, QAfterFilterCondition> ${link.dartName.decapitalize()}(FilterQuery<${link.targetCollectionDartName}> q) {
return QueryBuilder.apply(this, (query) {
return query.link(q, r'${link.isarName}');
});
}''';
if (link.isSingle) {
code += '''
QueryBuilder<${oi.dartName}, ${oi.dartName}, QAfterFilterCondition> ${link.dartName.decapitalize()}IsNull() {
return QueryBuilder.apply(this, (query) {
return query.linkLength(r'${link.isarName}', 0, true, 0, true);
});
}''';
} else {
code += generateLength(oi.dartName, link.dartName,
(lower, includeLower, upper, includeUpper) {
return '''
QueryBuilder.apply(this, (query) {
return query.linkLength(r'${link.isarName}', $lower, $includeLower, $upper, $includeUpper);
})''';
});
}
}
return '''
$code
}''';
}

@ -0,0 +1,29 @@
import 'package:dartx/dartx.dart';
import 'package:isar/isar.dart';
import 'package:isar_generator/src/isar_type.dart';
import 'package:isar_generator/src/object_info.dart';
String generateQueryObjects(ObjectInfo oi) {
var code =
'extension ${oi.dartName}QueryObject on QueryBuilder<${oi.dartName}, '
'${oi.dartName}, QFilterCondition> {';
for (final property in oi.objectProperties) {
if (!property.isarType.containsObject) {
continue;
}
var name = property.dartName.decapitalize();
if (property.isarType.isList) {
name += 'Element';
}
code += '''
QueryBuilder<${oi.dartName}, ${oi.dartName}, QAfterFilterCondition> $name(FilterQuery<${property.typeClassName}> q) {
return QueryBuilder.apply(this, (query) {
return query.object(q, r'${property.isarName}');
});
}''';
}
return '''
$code
}''';
}

@ -0,0 +1,25 @@
import 'package:isar_generator/src/object_info.dart';
String generatePropertyQuery(ObjectInfo oi) {
var code = '''
extension ${oi.dartName}QueryProperty on QueryBuilder<${oi.dartName}, ${oi.dartName}, QQueryProperty> {''';
// Ids are always non-nullable regardless of their specified nullability
code += '''
QueryBuilder<${oi.dartName}, int, QQueryOperations>${oi.idProperty.dartName}Property() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'${oi.idProperty.isarName}');
});
}''';
for (final property in oi.objectProperties) {
code += '''
QueryBuilder<${oi.dartName}, ${property.dartType}, QQueryOperations>${property.dartName}Property() {
return QueryBuilder.apply(this, (query) {
return query.addPropertyName(r'${property.isarName}');
});
}''';
}
return '$code}';
}

@ -0,0 +1,55 @@
import 'package:dartx/dartx.dart';
import 'package:isar/isar.dart';
import 'package:isar_generator/src/isar_type.dart';
import 'package:isar_generator/src/object_info.dart';
String generateSortBy(ObjectInfo oi) {
var code = '''
extension ${oi.dartName}QuerySortBy on QueryBuilder<${oi.dartName}, ${oi.dartName}, QSortBy> {''';
for (final property in oi.objectProperties) {
if (property.isarType.isList || property.isarType.containsObject) {
continue;
}
code += '''
QueryBuilder<${oi.dartName}, ${oi.dartName}, QAfterSortBy>sortBy${property.dartName.capitalize()}() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'${property.isarName}', Sort.asc);
});
}
QueryBuilder<${oi.dartName}, ${oi.dartName}, QAfterSortBy>sortBy${property.dartName.capitalize()}Desc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'${property.isarName}', Sort.desc);
});
}''';
}
code += '''
}
extension ${oi.dartName}QuerySortThenBy on QueryBuilder<${oi.dartName}, ${oi.dartName}, QSortThenBy> {''';
for (final property in oi.properties) {
if (property.isarType.isList || property.isarType.containsObject) {
continue;
}
code += '''
QueryBuilder<${oi.dartName}, ${oi.dartName}, QAfterSortBy>thenBy${property.dartName.capitalize()}() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'${property.isarName}', Sort.asc);
});
}
QueryBuilder<${oi.dartName}, ${oi.dartName}, QAfterSortBy>thenBy${property.dartName.capitalize()}Desc() {
return QueryBuilder.apply(this, (query) {
return query.addSortBy(r'${property.isarName}', Sort.desc);
});
}''';
}
return '$code}';
}

@ -0,0 +1,568 @@
import 'package:dartx/dartx.dart';
import 'package:isar/isar.dart';
import 'package:isar_generator/src/object_info.dart';
class WhereGenerator {
WhereGenerator(this.object)
: objName = object.dartName,
id = object.idProperty;
final ObjectInfo object;
final String objName;
final ObjectProperty id;
final existing = <String>{};
String generate() {
var code = 'extension ${objName}QueryWhereSort on QueryBuilder<$objName, '
'$objName, QWhere> {';
code += generateAnyId();
for (final index in object.indexes) {
if (index.properties.all((element) => element.type == IndexType.value)) {
code += generateAny(index);
}
}
code += '''
}
extension ${objName}QueryWhere on QueryBuilder<$objName, $objName, QWhereClause> {
''';
code += generateWhereIdEqualTo();
code += generateWhereIdNotEqualTo();
code += generateWhereIdGreaterThan();
code += generateWhereIdLessThan();
code += generateWhereIdBetween();
for (final index in object.indexes) {
for (var n = 0; n < index.properties.length; n++) {
final indexProperty = index.properties[n];
final property = indexProperty.property;
if ((property.nullable && !indexProperty.isMultiEntry) ||
(property.elementNullable && indexProperty.isMultiEntry)) {
code += generateWhereIsNull(index, n + 1);
code += generateWhereIsNotNull(index, n + 1);
}
code += generateWhereEqualTo(index, n + 1);
code += generateWhereNotEqualTo(index, n + 1);
if (indexProperty.type == IndexType.value) {
if (property.isarType != IsarType.bool &&
property.isarType != IsarType.boolList) {
code += generateWhereGreaterThan(index, n + 1);
code += generateWhereLessThan(index, n + 1);
code += generateWhereBetween(index, n + 1);
}
if (property.isarType == IsarType.string ||
property.isarType == IsarType.stringList) {
code += generateWhereStartsWith(index, n + 1);
code += generateStringIsEmpty(index, n + 1);
code += generateStringIsNotEmpty(index, n + 1);
}
}
}
}
return '$code}';
}
String getMethodName(ObjectIndex index, int propertyCount, [String? method]) {
String propertyName(ObjectIndexProperty p) {
var name = p.property.dartName.capitalize();
if (p.isMultiEntry) {
name += 'Element';
}
return name;
}
var name = '';
final eqProperties =
index.properties.sublist(0, propertyCount - (method != null ? 1 : 0));
if (eqProperties.isNotEmpty) {
name += eqProperties.map(propertyName).join();
name += 'EqualTo';
}
if (method != null) {
name += propertyName(index.properties[propertyCount - 1]);
name += method;
}
final remainingProperties = propertyCount < index.properties.length
? index.properties.sublist(propertyCount)
: null;
if (remainingProperties != null) {
name += 'Any';
name += remainingProperties.map(propertyName).join();
}
return name.decapitalize();
}
String paramType(ObjectIndexProperty p) {
if (p.property.isarType.isList && p.type == IndexType.hash) {
return p.property.dartType;
} else {
return p.property.nScalarDartType;
}
}
String paramName(ObjectIndexProperty p) {
if (p.property.isarType.isList && p.type != IndexType.hash) {
return '${p.property.dartName}Element';
} else {
return p.property.dartName;
}
}
String joinToParams(List<ObjectIndexProperty> properties) {
return properties
.map((it) => '${paramType(it)} ${paramName(it)}')
.join(',');
}
String joinToValues(List<ObjectIndexProperty> properties) {
return properties.map((it) {
if (it.property.isarType.isList && it.type != IndexType.hash) {
return '${it.property.dartName}Element';
} else {
return paramName(it);
}
}).join(', ');
}
String generateAnyId() {
return '''
QueryBuilder<$objName, $objName, QAfterWhere> any${id.dartName.capitalize()}() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(const IdWhereClause.any());
});
}
''';
}
String generateAny(ObjectIndex index) {
final name = getMethodName(index, 0);
if (!existing.add(name)) {
return '';
}
return '''
QueryBuilder<$objName, $objName, QAfterWhere> $name() {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
const IndexWhereClause.any(indexName: r'${index.name}'),
);
});
}
''';
}
String get mPrefix => 'QueryBuilder<$objName, $objName, QAfterWhereClause>';
String generateWhereIdEqualTo() {
final idName = id.dartName.decapitalize();
return '''
$mPrefix ${idName}EqualTo(Id $idName) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: $idName,
upper: $idName,
));
});
}
''';
}
String generateWhereEqualTo(ObjectIndex index, int propertyCount) {
final name = getMethodName(index, propertyCount);
if (!existing.add(name)) {
return '';
}
final properties = index.properties.takeFirst(propertyCount);
final values = joinToValues(properties);
final params = joinToParams(properties);
return '''
$mPrefix $name($params ${properties.containsFloat ? ', {double epsilon = Query.epsilon,}' : ''}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
indexName: r'${index.name}',
value: [$values],
${properties.containsFloat ? 'epsilon: epsilon,' : ''}
));
});
}
''';
}
String generateWhereIdNotEqualTo() {
final idName = id.dartName.decapitalize();
return '''
$mPrefix ${idName}NotEqualTo(Id $idName) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: $idName, includeUpper: false),
).addWhereClause(
IdWhereClause.greaterThan(lower: $idName, includeLower: false),
);
} else {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: $idName, includeLower: false),
).addWhereClause(
IdWhereClause.lessThan(upper: $idName, includeUpper: false),
);
}
});
}
''';
}
String generateWhereNotEqualTo(ObjectIndex index, int propertyCount) {
final name = getMethodName(index, propertyCount, 'NotEqualTo');
if (!existing.add(name)) {
return '';
}
final properties = index.properties.takeFirst(propertyCount);
final params = joinToParams(properties);
final equalProperties = properties.dropLast(1);
final notEqualProperty = properties.last;
final equalValues = joinToValues(equalProperties);
var notEqualValue = joinToValues([notEqualProperty]);
if (equalValues.isNotEmpty) {
notEqualValue = ',$notEqualValue';
}
return '''
$mPrefix $name($params ${properties.containsFloat ? ', {double epsilon = Query.epsilon,}' : ''}) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query.addWhereClause(IndexWhereClause.between(
indexName: r'${index.name}',
lower: [$equalValues],
upper: [$equalValues $notEqualValue],
includeUpper: false,
${properties.containsFloat ? 'epsilon: epsilon,' : ''}
)).addWhereClause(IndexWhereClause.between(
indexName: r'${index.name}',
lower: [$equalValues $notEqualValue],
includeLower: false,
upper: [$equalValues],
${properties.containsFloat ? 'epsilon: epsilon,' : ''}
));
} else {
return query.addWhereClause(IndexWhereClause.between(
indexName: r'${index.name}',
lower: [$equalValues $notEqualValue],
includeLower: false,
upper: [$equalValues],
${properties.containsFloat ? 'epsilon: epsilon,' : ''}
)).addWhereClause(IndexWhereClause.between(
indexName: r'${index.name}',
lower: [$equalValues],
upper: [$equalValues $notEqualValue],
includeUpper: false,
${properties.containsFloat ? 'epsilon: epsilon,' : ''}
));
}
});
}
''';
}
String generateWhereIdGreaterThan() {
final idName = id.dartName.decapitalize();
return '''
$mPrefix ${idName}GreaterThan(Id $idName, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.greaterThan(lower: $idName, includeLower: include),
);
});
}
''';
}
String generateWhereGreaterThan(ObjectIndex index, int propertyCount) {
final name = getMethodName(index, propertyCount, 'GreaterThan');
if (!existing.add(name)) {
return '';
}
final properties = index.properties.takeFirst(propertyCount);
final optional = [
'bool include = false',
if (properties.containsFloat) 'double epsilon = Query.epsilon',
].join(',');
return '''
$mPrefix $name(${joinToParams(properties)}, {$optional,}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.between(
indexName: r'${index.name}',
lower: [${joinToValues(properties)}],
includeLower: include,
upper: [${joinToValues(properties.dropLast(1))}],
${properties.containsFloat ? 'epsilon: epsilon,' : ''}
));
});
}
''';
}
String generateWhereIdLessThan() {
final idName = id.dartName.decapitalize();
return '''
$mPrefix ${idName}LessThan(Id $idName, {bool include = false}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(
IdWhereClause.lessThan(upper: $idName, includeUpper: include),
);
});
}
''';
}
String generateWhereLessThan(ObjectIndex index, int propertyCount) {
final name = getMethodName(index, propertyCount, 'LessThan');
if (!existing.add(name)) {
return '';
}
final properties = index.properties.takeFirst(propertyCount);
final optional = [
'bool include = false',
if (properties.containsFloat) 'double epsilon = Query.epsilon',
].join(',');
return '''
$mPrefix $name(${joinToParams(properties)}, {$optional,}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.between(
indexName: r'${index.name}',
lower: [${joinToValues(properties.dropLast(1))}],
upper: [${joinToValues(properties)}],
includeUpper: include,
${properties.containsFloat ? 'epsilon: epsilon,' : ''}
));
});
}
''';
}
String generateWhereIdBetween() {
final idName = id.dartName.decapitalize();
final lowerName = 'lower${id.dartName.capitalize()}';
final upperName = 'upper${id.dartName.capitalize()}';
return '''
$mPrefix ${idName}Between(Id $lowerName, Id $upperName, {bool includeLower = true, bool includeUpper = true,}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IdWhereClause.between(
lower: $lowerName,
includeLower: includeLower,
upper: $upperName,
includeUpper: includeUpper,
));
});
}
''';
}
String generateWhereBetween(ObjectIndex index, int propertyCount) {
final name = getMethodName(index, propertyCount, 'Between');
if (!existing.add(name)) {
return '';
}
final properties = index.properties.takeFirst(propertyCount);
final equalProperties = properties.dropLast(1);
final betweenProperty = properties.last;
var params = joinToParams(equalProperties);
if (params.isNotEmpty) {
params += ',';
}
final betweenType = paramType(betweenProperty);
final lowerName = 'lower${paramName(betweenProperty).capitalize()}';
final upperName = 'upper${paramName(betweenProperty).capitalize()}';
params += '$betweenType $lowerName, $betweenType $upperName';
var values = joinToValues(equalProperties);
if (values.isNotEmpty) {
values += ',';
}
final optional = [
'bool includeLower = true',
'bool includeUpper = true',
if (properties.containsFloat) 'double epsilon = Query.epsilon',
].join(',');
return '''
$mPrefix $name($params, {$optional,}) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.between(
indexName: r'${index.name}',
lower: [$values $lowerName],
includeLower: includeLower,
upper: [$values $upperName],
includeUpper: includeUpper,
${properties.containsFloat ? 'epsilon: epsilon,' : ''}
));
});
}
''';
}
String generateWhereIsNull(ObjectIndex index, int propertyCount) {
final name = getMethodName(index, propertyCount, 'IsNull');
if (!existing.add(name)) {
return '';
}
final properties = index.properties.takeFirst(propertyCount - 1);
var values = joinToValues(properties);
if (values.isNotEmpty) {
values += ',';
}
final params = joinToParams(properties);
return '''
$mPrefix $name($params) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
indexName: r'${index.name}',
value: [$values null],
));
});
}
''';
}
String generateWhereIsNotNull(ObjectIndex index, int propertyCount) {
final name = getMethodName(index, propertyCount, 'IsNotNull');
if (!existing.add(name)) {
return '';
}
final properties = index.properties.takeFirst(propertyCount - 1);
var values = joinToValues(properties);
if (values.isNotEmpty) {
values += ',';
}
final params = joinToParams(properties);
return '''
$mPrefix $name($params) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.between(
indexName: r'${index.name}',
lower: [$values null],
includeLower: false,
upper: [$values],
));
});
}
''';
}
String generateWhereStartsWith(ObjectIndex index, int propertyCount) {
final name = getMethodName(index, propertyCount, 'StartsWith');
if (!existing.add(name)) {
return '';
}
final equalProperties = index.properties.dropLast(1);
var params = joinToParams(equalProperties);
if (params.isNotEmpty) {
params += ',';
}
final prefixProperty = index.properties.last;
final prefixName = '${paramName(prefixProperty).capitalize()}Prefix';
params += 'String $prefixName';
var values = joinToValues(equalProperties);
if (values.isNotEmpty) {
values += ',';
}
return '''
$mPrefix $name($params) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.between(
indexName: r'${index.name}',
lower: [$values $prefixName],
upper: [$values '\$$prefixName\\u{FFFFF}'],
));
});
}
''';
}
String generateStringIsEmpty(ObjectIndex index, int propertyCount) {
final name = getMethodName(index, propertyCount, 'IsEmpty');
if (!existing.add(name)) {
return '';
}
final properties = index.properties.dropLast(1);
var values = joinToValues(properties);
if (values.isNotEmpty) {
values += ',';
}
final params = joinToParams(properties);
return '''
$mPrefix $name($params) {
return QueryBuilder.apply(this, (query) {
return query.addWhereClause(IndexWhereClause.equalTo(
indexName: r'${index.name}',
value: [$values ''],
));
});
}''';
}
String generateStringIsNotEmpty(ObjectIndex index, int propertyCount) {
final name = getMethodName(index, propertyCount, 'IsNotEmpty');
if (!existing.add(name)) {
return '';
}
final properties = index.properties.dropLast(1);
var values = joinToValues(properties);
if (values.isNotEmpty) {
values += ',';
}
final params = joinToParams(properties);
return '''
$mPrefix $name($params) {
return QueryBuilder.apply(this, (query) {
if (query.whereSort == Sort.asc) {
return query.addWhereClause(IndexWhereClause.lessThan(
indexName: r'${index.name}',
upper: [''],
)).addWhereClause(IndexWhereClause.greaterThan(
indexName: r'${index.name}',
lower: [''],
));
} else {
return query.addWhereClause(IndexWhereClause.greaterThan(
indexName: r'${index.name}',
lower: [''],
)).addWhereClause(IndexWhereClause.lessThan(
indexName: r'${index.name}',
upper: [''],
));
}
});
}''';
}
}
extension on List<ObjectIndexProperty> {
bool get containsFloat =>
last.isarType == IsarType.float || last.isarType == IsarType.floatList;
}

@ -0,0 +1,476 @@
import 'package:dartx/dartx.dart';
import 'package:isar/isar.dart';
import 'package:isar_generator/src/object_info.dart';
String _prepareSerialize(
bool nullable,
String value,
String Function(String) size,
) {
var code = '';
if (nullable) {
code += '''
{
final value = $value;
if (value != null) {''';
value = 'value';
}
code += 'bytesCount += ${size(value)};';
if (nullable) {
code += '}}';
}
return code;
}
String _prepareSerializeList(
bool nullable,
bool elementNullable,
String value,
String size, [
String? prepare,
]) {
var code = '';
if (nullable) {
code += '''
{
final list = $value;
if (list != null) {''';
value = 'list';
}
code += '''
bytesCount += 3 + $value.length * 3;
{
${prepare ?? ''}
for (var i = 0; i < $value.length; i++) {
final value = $value[i];''';
if (elementNullable) {
code += 'if (value != null) {';
}
code += 'bytesCount += $size;';
if (elementNullable) {
code += '}';
}
code += '}}';
if (nullable) {
code += '}}';
}
return code;
}
String generateEstimateSerialize(ObjectInfo object) {
var code = '''
int ${object.estimateSizeName}(
${object.dartName} object,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
var bytesCount = offsets.last;''';
for (final property in object.properties) {
final value = 'object.${property.dartName}';
switch (property.isarType) {
case IsarType.string:
final enumValue = property.isEnum ? '.${property.enumProperty}' : '';
code += _prepareSerialize(
property.nullable,
value,
(value) => '3 + $value$enumValue.length * 3',
);
break;
case IsarType.stringList:
final enumValue = property.isEnum ? '.${property.enumProperty}' : '';
code += _prepareSerializeList(
property.nullable,
property.elementNullable,
value,
'value$enumValue.length * 3',
);
break;
case IsarType.object:
code += _prepareSerialize(
property.nullable,
value,
(value) {
return '3 + ${property.targetSchema}.estimateSize($value, '
'allOffsets[${property.scalarDartType}]!, allOffsets)';
},
);
break;
case IsarType.objectList:
code += _prepareSerializeList(
property.nullable,
property.elementNullable,
value,
'${property.targetSchema}.estimateSize(value, offsets, allOffsets)',
'final offsets = allOffsets[${property.scalarDartType}]!;',
);
break;
case IsarType.byteList:
case IsarType.boolList:
code += _prepareSerialize(
property.nullable,
value,
(value) => '3 + $value.length',
);
break;
case IsarType.intList:
case IsarType.floatList:
code += _prepareSerialize(
property.nullable,
value,
(value) => '3 + $value.length * 4',
);
break;
case IsarType.longList:
case IsarType.doubleList:
case IsarType.dateTimeList:
code += _prepareSerialize(
property.nullable,
value,
(value) => '3 + $value.length * 8',
);
break;
// ignore: no_default_cases
default:
break;
}
}
return '''
$code
return bytesCount;
}''';
}
String generateSerialize(ObjectInfo object) {
var code = '''
void ${object.serializeName}(
${object.dartName} object,
IsarWriter writer,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {''';
for (var i = 0; i < object.objectProperties.length; i++) {
final property = object.objectProperties[i];
var value = 'object.${property.dartName}';
if (property.isEnum) {
final nOp = property.nullable ? '?' : '';
final elNOp = property.elementNullable ? '?' : '';
value = property.isarType.isList
? '$value$nOp.map((e) => e$elNOp.${property.enumProperty}).toList()'
: '$value$nOp.${property.enumProperty}';
}
switch (property.isarType) {
case IsarType.bool:
code += 'writer.writeBool(offsets[$i], $value);';
break;
case IsarType.byte:
code += 'writer.writeByte(offsets[$i], $value);';
break;
case IsarType.int:
code += 'writer.writeInt(offsets[$i], $value);';
break;
case IsarType.float:
code += 'writer.writeFloat(offsets[$i], $value);';
break;
case IsarType.long:
code += 'writer.writeLong(offsets[$i], $value);';
break;
case IsarType.double:
code += 'writer.writeDouble(offsets[$i], $value);';
break;
case IsarType.dateTime:
code += 'writer.writeDateTime(offsets[$i], $value);';
break;
case IsarType.string:
code += 'writer.writeString(offsets[$i], $value);';
break;
case IsarType.object:
code += '''
writer.writeObject<${property.typeClassName}>(
offsets[$i],
allOffsets,
${property.targetSchema}.serialize,
$value,
);''';
break;
case IsarType.byteList:
code += 'writer.writeByteList(offsets[$i], $value);';
break;
case IsarType.boolList:
code += 'writer.writeBoolList(offsets[$i], $value);';
break;
case IsarType.intList:
code += 'writer.writeIntList(offsets[$i], $value);';
break;
case IsarType.longList:
code += 'writer.writeLongList(offsets[$i], $value);';
break;
case IsarType.floatList:
code += 'writer.writeFloatList(offsets[$i], $value);';
break;
case IsarType.doubleList:
code += 'writer.writeDoubleList(offsets[$i], $value);';
break;
case IsarType.dateTimeList:
code += 'writer.writeDateTimeList(offsets[$i], $value);';
break;
case IsarType.stringList:
code += 'writer.writeStringList(offsets[$i], $value);';
break;
case IsarType.objectList:
code += '''
writer.writeObjectList<${property.typeClassName}>(
offsets[$i],
allOffsets,
${property.targetSchema}.serialize,
$value,
);''';
break;
}
}
return '$code}';
}
String generateDeserialize(ObjectInfo object) {
var code = '''
${object.dartName} ${object.deserializeName}(
Id id,
IsarReader reader,
List<int> offsets,
Map<Type, List<int>> allOffsets,
) {
final object = ${object.dartName}(''';
final propertiesByMode =
object.properties.groupBy((ObjectProperty p) => p.deserialize);
final positional = propertiesByMode[PropertyDeser.positionalParam] ?? [];
final sortedPositional =
positional.sortedBy((ObjectProperty p) => p.constructorPosition!);
for (final p in sortedPositional) {
final index = object.objectProperties.indexOf(p);
final deser = _deserializeProperty(object, p, 'offsets[$index]');
code += '$deser,';
}
final named = propertiesByMode[PropertyDeser.namedParam] ?? [];
for (final p in named) {
final index = object.objectProperties.indexOf(p);
final deser = _deserializeProperty(object, p, 'offsets[$index]');
code += '${p.dartName}: $deser,';
}
code += ');';
final assign = propertiesByMode[PropertyDeser.assign] ?? [];
for (final p in assign) {
final index = object.objectProperties.indexOf(p);
final deser = _deserializeProperty(object, p, 'offsets[$index]');
code += 'object.${p.dartName} = $deser;';
}
return '''
$code
return object;
}''';
}
String generateDeserializeProp(ObjectInfo object) {
var code = '''
P ${object.deserializePropName}<P>(
IsarReader reader,
int propertyId,
int offset,
Map<Type,
List<int>> allOffsets,
) {
switch (propertyId) {''';
for (var i = 0; i < object.objectProperties.length; i++) {
final property = object.objectProperties[i];
final deser = _deserializeProperty(object, property, 'offset');
code += 'case $i: return ($deser) as P;';
}
return '''
$code
default:
throw IsarError('Unknown property with id \$propertyId');
}
}
''';
}
String _deserializeProperty(
ObjectInfo object,
ObjectProperty property,
String propertyOffset,
) {
if (property.isId) {
return 'id';
}
final deser = _deserialize(property, propertyOffset);
var defaultValue = '';
if (!property.nullable) {
if (property.userDefaultValue != null) {
defaultValue = '?? ${property.userDefaultValue}';
} else if (property.isarType == IsarType.object) {
defaultValue = '?? ${property.typeClassName}()';
} else if (property.isarType.isList) {
defaultValue = '?? []';
} else if (property.isEnum) {
defaultValue = '?? ${property.defaultEnumElement}';
}
}
if (property.isEnum) {
if (property.isarType.isList) {
final elDefault =
!property.elementNullable ? '?? ${property.defaultEnumElement}' : '';
return '$deser?.map((e) => ${property.valueEnumMapName(object)}[e] '
'$elDefault).toList() $defaultValue';
} else {
return '${property.valueEnumMapName(object)}[$deser] $defaultValue';
}
} else {
return '$deser $defaultValue';
}
}
String _deserialize(ObjectProperty property, String propertyOffset) {
final orNull =
property.nullable || property.userDefaultValue != null || property.isEnum
? 'OrNull'
: '';
final orElNull = property.elementNullable ? 'OrNull' : '';
switch (property.isarType) {
case IsarType.bool:
return 'reader.readBool$orNull($propertyOffset)';
case IsarType.byte:
return 'reader.readByte$orNull($propertyOffset)';
case IsarType.int:
return 'reader.readInt$orNull($propertyOffset)';
case IsarType.float:
return 'reader.readFloat$orNull($propertyOffset)';
case IsarType.long:
return 'reader.readLong$orNull($propertyOffset)';
case IsarType.double:
return 'reader.readDouble$orNull($propertyOffset)';
case IsarType.dateTime:
return 'reader.readDateTime$orNull($propertyOffset)';
case IsarType.string:
return 'reader.readString$orNull($propertyOffset)';
case IsarType.object:
return '''
reader.readObjectOrNull<${property.typeClassName}>(
$propertyOffset,
${property.targetSchema}.deserialize,
allOffsets,
)''';
case IsarType.boolList:
return 'reader.readBool${orElNull}List($propertyOffset)';
case IsarType.byteList:
return 'reader.readByteList($propertyOffset)';
case IsarType.intList:
return 'reader.readInt${orElNull}List($propertyOffset)';
case IsarType.floatList:
return 'reader.readFloat${orElNull}List($propertyOffset)';
case IsarType.longList:
return 'reader.readLong${orElNull}List($propertyOffset)';
case IsarType.doubleList:
return 'reader.readDouble${orElNull}List($propertyOffset)';
case IsarType.dateTimeList:
return 'reader.readDateTime${orElNull}List($propertyOffset)';
case IsarType.stringList:
return 'reader.readString${orElNull}List($propertyOffset)';
case IsarType.objectList:
return '''
reader.readObject${orElNull}List<${property.typeClassName}>(
$propertyOffset,
${property.targetSchema}.deserialize,
allOffsets,
${!property.elementNullable ? '${property.typeClassName}(),' : ''}
)''';
}
}
String generateGetId(ObjectInfo object) {
final defaultVal = object.idProperty.nullable ? '?? Isar.autoIncrement' : '';
return '''
Id ${object.getIdName}(${object.dartName} object) {
return object.${object.idProperty.dartName} $defaultVal;
}
''';
}
String generateGetLinks(ObjectInfo object) {
return '''
List<IsarLinkBase<dynamic>> ${object.getLinksName}(${object.dartName} object) {
return [${object.links.map((e) => 'object.${e.dartName}').join(',')}];
}
''';
}
String generateAttach(ObjectInfo object) {
var code = '''
void ${object.attachName}(IsarCollection<dynamic> col, Id id, ${object.dartName} object) {''';
if (object.idProperty.assignable) {
code += 'object.${object.idProperty.dartName} = id;';
}
for (final link in object.links) {
// ignore: leading_newlines_in_multiline_strings
code += '''object.${link.dartName}.attach(
col,
col.isar.collection<${link.targetCollectionDartName}>(),
r'${link.isarName}',
id
);''';
}
return '$code}';
}
String generateEnumMaps(ObjectInfo object) {
var code = '';
for (final property in object.properties) {
final enumName = property.typeClassName;
if (property.isEnum) {
code += 'const ${property.enumValueMapName(object)} = {';
for (final enumElementName in property.enumMap!.keys) {
final value = property.enumMap![enumElementName];
if (value is String) {
code += "r'$enumElementName': r'$value',";
} else {
code += "'$enumElementName': $value,";
}
}
code += '};';
code += 'const ${property.valueEnumMapName(object)} = {';
for (final enumElementName in property.enumMap!.keys) {
final value = property.enumMap![enumElementName];
if (value is String) {
code += "r'$value': $enumName.$enumElementName,";
} else {
code += '$value: $enumName.$enumElementName,';
}
}
code += '};';
}
}
return code;
}

@ -0,0 +1,105 @@
import 'dart:async';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:isar/isar.dart';
import 'package:isar_generator/src/code_gen/by_index_generator.dart';
import 'package:isar_generator/src/code_gen/collection_schema_generator.dart';
import 'package:isar_generator/src/code_gen/query_distinct_by_generator.dart';
import 'package:isar_generator/src/code_gen/query_filter_generator.dart';
import 'package:isar_generator/src/code_gen/query_link_generator.dart';
import 'package:isar_generator/src/code_gen/query_object_generator.dart';
import 'package:isar_generator/src/code_gen/query_property_generator.dart';
import 'package:isar_generator/src/code_gen/query_sort_by_generator.dart';
import 'package:isar_generator/src/code_gen/query_where_generator.dart';
import 'package:isar_generator/src/code_gen/type_adapter_generator.dart';
import 'package:isar_generator/src/isar_analyzer.dart';
import 'package:source_gen/source_gen.dart';
const ignoreLints = [
'duplicate_ignore',
'non_constant_identifier_names',
'constant_identifier_names',
'invalid_use_of_protected_member',
'unnecessary_cast',
'prefer_const_constructors',
'lines_longer_than_80_chars',
'require_trailing_commas',
'inference_failure_on_function_invocation',
'unnecessary_parenthesis',
'unnecessary_raw_strings',
'unnecessary_null_checks',
'join_return_with_assignment',
'prefer_final_locals',
'avoid_js_rounded_ints',
'avoid_positional_boolean_parameters',
'always_specify_types',
];
class IsarCollectionGenerator extends GeneratorForAnnotation<Collection> {
@override
Future<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,
) async {
final object = IsarAnalyzer().analyzeCollection(element);
return '''
// coverage:ignore-file
// ignore_for_file: ${ignoreLints.join(', ')}
extension Get${object.dartName}Collection on Isar {
IsarCollection<${object.dartName}> get ${object.accessor} => this.collection();
}
${generateSchema(object)}
${generateEstimateSerialize(object)}
${generateSerialize(object)}
${generateDeserialize(object)}
${generateDeserializeProp(object)}
${generateEnumMaps(object)}
${generateGetId(object)}
${generateGetLinks(object)}
${generateAttach(object)}
${generateByIndexExtension(object)}
${WhereGenerator(object).generate()}
${FilterGenerator(object).generate()}
${generateQueryObjects(object)}
${generateQueryLinks(object)}
${generateSortBy(object)}
${generateDistinctBy(object)}
${generatePropertyQuery(object)}
''';
}
}
class IsarEmbeddedGenerator extends GeneratorForAnnotation<Embedded> {
@override
Future<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,
) async {
final object = IsarAnalyzer().analyzeEmbedded(element);
return '''
// coverage:ignore-file
// ignore_for_file: ${ignoreLints.join(', ')}
${generateSchema(object)}
${generateEstimateSerialize(object)}
${generateSerialize(object)}
${generateDeserialize(object)}
${generateDeserializeProp(object)}
${generateEnumMaps(object)}
${FilterGenerator(object).generate()}
${generateQueryObjects(object)}
''';
}
}

184
lib/src/helper.dart Normal file

@ -0,0 +1,184 @@
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:dartx/dartx.dart';
import 'package:isar/isar.dart';
import 'package:source_gen/source_gen.dart';
const TypeChecker _collectionChecker = TypeChecker.fromRuntime(Collection);
const TypeChecker _enumeratedChecker = TypeChecker.fromRuntime(Enumerated);
const TypeChecker _embeddedChecker = TypeChecker.fromRuntime(Embedded);
const TypeChecker _ignoreChecker = TypeChecker.fromRuntime(Ignore);
const TypeChecker _nameChecker = TypeChecker.fromRuntime(Name);
const TypeChecker _indexChecker = TypeChecker.fromRuntime(Index);
const TypeChecker _backlinkChecker = TypeChecker.fromRuntime(Backlink);
extension ClassElementX on ClassElement {
bool get hasZeroArgsConstructor {
return constructors.any(
(ConstructorElement c) =>
c.isPublic &&
!c.parameters.any((ParameterElement p) => !p.isOptional),
);
}
List<PropertyInducingElement> get allAccessors {
final ignoreFields =
collectionAnnotation?.ignore ?? embeddedAnnotation!.ignore;
return [
...accessors.mapNotNull((e) => e.variable),
if (collectionAnnotation?.inheritance ?? embeddedAnnotation!.inheritance)
for (InterfaceType supertype in allSupertypes) ...[
if (!supertype.isDartCoreObject)
...supertype.accessors.mapNotNull((e) => e.variable)
]
]
.where(
(PropertyInducingElement e) =>
e.isPublic &&
!e.isStatic &&
!_ignoreChecker.hasAnnotationOf(e.nonSynthetic) &&
!ignoreFields.contains(e.name),
)
.distinctBy((e) => e.name)
.toList();
}
List<String> get enumConsts {
return fields.where((e) => e.isEnumConstant).map((e) => e.name).toList();
}
}
extension PropertyElementX on PropertyInducingElement {
bool get isLink => type.element2!.name == 'IsarLink';
bool get isLinks => type.element2!.name == 'IsarLinks';
Enumerated? get enumeratedAnnotation {
final ann = _enumeratedChecker.firstAnnotationOfExact(nonSynthetic);
if (ann == null) {
return null;
}
final typeIndex = ann.getField('type')!.getField('index')!.toIntValue()!;
return Enumerated(
EnumType.values[typeIndex],
ann.getField('property')?.toStringValue(),
);
}
Backlink? get backlinkAnnotation {
final ann = _backlinkChecker.firstAnnotationOfExact(nonSynthetic);
if (ann == null) {
return null;
}
return Backlink(to: ann.getField('to')!.toStringValue()!);
}
List<Index> get indexAnnotations {
return _indexChecker.annotationsOfExact(nonSynthetic).map((DartObject ann) {
final rawComposite = ann.getField('composite')!.toListValue();
final composite = <CompositeIndex>[];
if (rawComposite != null) {
for (final c in rawComposite) {
final indexTypeField = c.getField('type')!;
IndexType? indexType;
if (!indexTypeField.isNull) {
final indexTypeIndex =
indexTypeField.getField('index')!.toIntValue()!;
indexType = IndexType.values[indexTypeIndex];
}
composite.add(
CompositeIndex(
c.getField('property')!.toStringValue()!,
type: indexType,
caseSensitive: c.getField('caseSensitive')!.toBoolValue(),
),
);
}
}
final indexTypeField = ann.getField('type')!;
IndexType? indexType;
if (!indexTypeField.isNull) {
final indexTypeIndex = indexTypeField.getField('index')!.toIntValue()!;
indexType = IndexType.values[indexTypeIndex];
}
return Index(
name: ann.getField('name')!.toStringValue(),
composite: composite,
unique: ann.getField('unique')!.toBoolValue()!,
replace: ann.getField('replace')!.toBoolValue()!,
type: indexType,
caseSensitive: ann.getField('caseSensitive')!.toBoolValue(),
);
}).toList();
}
}
extension ElementX on Element {
String get isarName {
final ann = _nameChecker.firstAnnotationOfExact(nonSynthetic);
late String name;
if (ann == null) {
name = displayName;
} else {
name = ann.getField('name')!.toStringValue()!;
}
checkIsarName(name, this);
return name;
}
Collection? get collectionAnnotation {
final ann = _collectionChecker.firstAnnotationOfExact(nonSynthetic);
if (ann == null) {
return null;
}
return Collection(
inheritance: ann.getField('inheritance')!.toBoolValue()!,
accessor: ann.getField('accessor')!.toStringValue(),
ignore: ann
.getField('ignore')!
.toSetValue()!
.map((e) => e.toStringValue()!)
.toSet(),
);
}
String get collectionAccessor {
var accessor = collectionAnnotation?.accessor;
if (accessor != null) {
return accessor;
}
accessor = displayName.decapitalize();
if (!accessor.endsWith('s')) {
accessor += 's';
}
return accessor;
}
Embedded? get embeddedAnnotation {
final ann = _embeddedChecker.firstAnnotationOfExact(nonSynthetic);
if (ann == null) {
return null;
}
return Embedded(
inheritance: ann.getField('inheritance')!.toBoolValue()!,
ignore: ann
.getField('ignore')!
.toSetValue()!
.map((e) => e.toStringValue()!)
.toSet(),
);
}
}
void checkIsarName(String name, Element element) {
if (name.isBlank || name.startsWith('_')) {
err('Names must not be blank or start with "_".', element);
}
}
Never err(String msg, [Element? element]) {
throw InvalidGenerationSourceError(msg, element: element);
}

502
lib/src/isar_analyzer.dart Normal file

@ -0,0 +1,502 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:dartx/dartx.dart';
import 'package:isar/isar.dart';
import 'package:isar_generator/src/helper.dart';
import 'package:isar_generator/src/isar_type.dart';
import 'package:isar_generator/src/object_info.dart';
class IsarAnalyzer {
ObjectInfo analyzeCollection(Element element) {
final constructor = _checkValidClass(element);
final modelClass = element as ClassElement;
final properties = <ObjectProperty>[];
final links = <ObjectLink>[];
for (final propertyElement in modelClass.allAccessors) {
if (propertyElement.isLink || propertyElement.isLinks) {
final link = analyzeObjectLink(propertyElement);
links.add(link);
} else {
final property = analyzeObjectProperty(propertyElement, constructor);
properties.add(property);
}
}
_checkValidPropertiesConstructor(properties, constructor);
if (links.map((e) => e.isarName).distinct().length != links.length) {
err('Two or more links have the same name.', modelClass);
}
final indexes = <ObjectIndex>[];
for (final propertyElement in modelClass.allAccessors) {
indexes.addAll(analyzeObjectIndex(properties, propertyElement));
}
if (indexes.map((e) => e.name).distinct().length != indexes.length) {
err('Two or more indexes have the same name.', modelClass);
}
final idProperties = properties.where((it) => it.isId);
if (idProperties.isEmpty) {
err(
'No id property defined. Use the "Id" type for your id property.',
modelClass,
);
} else if (idProperties.length > 1) {
err('Two or more properties with type "Id" defined.', modelClass);
}
return ObjectInfo(
dartName: modelClass.displayName,
isarName: modelClass.isarName,
accessor: modelClass.collectionAccessor,
properties: properties,
embeddedDartNames: _getEmbeddedDartNames(element),
indexes: indexes,
links: links,
);
}
ObjectInfo analyzeEmbedded(Element element) {
final constructor = _checkValidClass(element);
final modelClass = element as ClassElement;
if (constructor.parameters.any((e) => e.isRequired)) {
err(
'Constructors of embedded objects must not have required parameters.',
constructor,
);
}
final properties = <ObjectProperty>[];
for (final propertyElement in modelClass.allAccessors) {
if (propertyElement.isLink || propertyElement.isLinks) {
err('Embedded objects must not contain links', propertyElement);
} else {
final property = analyzeObjectProperty(propertyElement, constructor);
properties.add(property);
}
}
_checkValidPropertiesConstructor(properties, constructor);
final hasIndex = modelClass.allAccessors.any(
(it) => it.indexAnnotations.isNotEmpty,
);
if (hasIndex) {
err('Embedded objects must not have indexes.', modelClass);
}
final hasIdProperty = properties.any((it) => it.isId);
if (hasIdProperty) {
err('Embedded objects must not define an id.', modelClass);
}
return ObjectInfo(
dartName: modelClass.displayName,
isarName: modelClass.isarName,
properties: properties,
);
}
ConstructorElement _checkValidClass(Element modelClass) {
if (modelClass is! ClassElement ||
modelClass is EnumElement ||
modelClass is MixinElement) {
err(
'Only classes may be annotated with @Collection or @Embedded.',
modelClass,
);
}
if (modelClass.isAbstract) {
err('Class must not be abstract.', modelClass);
}
if (!modelClass.isPublic) {
err('Class must be public.', modelClass);
}
final constructor = modelClass.constructors
.firstOrNullWhere((ConstructorElement c) => c.periodOffset == null);
if (constructor == null) {
err('Class needs an unnamed constructor.', modelClass);
}
final hasCollectionSupertype = modelClass.allSupertypes.any((type) {
return type.element.collectionAnnotation != null ||
type.element.embeddedAnnotation != null;
});
if (hasCollectionSupertype) {
err(
'Class must not have a supertype annotated with @Collection or '
'@Embedded.',
modelClass,
);
}
return constructor;
}
void _checkValidPropertiesConstructor(
List<ObjectProperty> properties,
ConstructorElement constructor,
) {
if (properties.map((e) => e.isarName).distinct().length !=
properties.length) {
err(
'Two or more properties have the same name.',
constructor.enclosingElement,
);
}
final unknownConstructorParameter = constructor.parameters.firstOrNullWhere(
(p) => p.isRequired && properties.none((e) => e.dartName == p.name),
);
if (unknownConstructorParameter != null) {
err(
'Constructor parameter does not match a property.',
unknownConstructorParameter,
);
}
}
Map<String, String> _getEmbeddedDartNames(ClassElement element) {
void _fillNames(Map<String, String> names, ClassElement element) {
for (final property in element.allAccessors) {
final type = property.type.scalarType.element;
if (type is ClassElement && type.embeddedAnnotation != null) {
final isarName = type.isarName;
if (!names.containsKey(isarName)) {
names[type.isarName] = type.displayName;
_fillNames(names, type);
}
}
}
}
final names = <String, String>{};
_fillNames(names, element);
return names;
}
ObjectProperty analyzeObjectProperty(
PropertyInducingElement property,
ConstructorElement constructor,
) {
final dartType = property.type;
final scalarDartType = dartType.scalarType;
Map<String, dynamic>? enumMap;
String? enumPropertyName;
String? defaultEnumElement;
late final IsarType isarType;
if (scalarDartType.element is EnumElement) {
final enumeratedAnn = property.enumeratedAnnotation;
if (enumeratedAnn == null) {
err('Enum property must be annotated with @enumerated.', property);
}
final enumClass = scalarDartType.element! as EnumElement;
final enumElements =
enumClass.fields.where((f) => f.isEnumConstant).toList();
defaultEnumElement = '${enumClass.name}.${enumElements.first.name}';
if (enumeratedAnn.type == EnumType.ordinal) {
isarType = dartType.isDartCoreList ? IsarType.byteList : IsarType.byte;
enumMap = {
for (var i = 0; i < enumElements.length; i++) enumElements[i].name: i,
};
enumPropertyName = 'index';
} else if (enumeratedAnn.type == EnumType.ordinal32) {
isarType = dartType.isDartCoreList ? IsarType.intList : IsarType.int;
enumMap = {
for (var i = 0; i < enumElements.length; i++) enumElements[i].name: i,
};
enumPropertyName = 'index';
} else if (enumeratedAnn.type == EnumType.name) {
isarType =
dartType.isDartCoreList ? IsarType.stringList : IsarType.string;
enumMap = {
for (final value in enumElements) value.name: value.name,
};
enumPropertyName = 'name';
} else {
enumPropertyName = enumeratedAnn.property;
if (enumPropertyName == null) {
err(
'Enums with type EnumType.value must specify which property '
'should be used.',
property,
);
}
final enumProperty = enumClass.getField(enumPropertyName);
if (enumProperty == null || enumProperty.isEnumConstant) {
err('Enum property "$enumProperty" does not exist.', property);
} else if (enumProperty.nonSynthetic is PropertyAccessorElement) {
err('Only fields are supported for enum properties', enumProperty);
}
final enumIsarType = enumProperty.type.isarType;
if (enumIsarType != IsarType.byte &&
enumIsarType != IsarType.int &&
enumIsarType != IsarType.long &&
enumIsarType != IsarType.string) {
err('Unsupported enum property type.', enumProperty);
}
isarType =
dartType.isDartCoreList ? enumIsarType!.listType : enumIsarType!;
enumMap = {};
for (final element in enumElements) {
final property =
element.computeConstantValue()!.getField(enumPropertyName)!;
final propertyValue = property.toBoolValue() ??
property.toIntValue() ??
property.toDoubleValue() ??
property.toStringValue();
if (propertyValue == null) {
err(
'Null values are not supported for enum properties.',
enumProperty,
);
}
if (enumMap.values.contains(propertyValue)) {
err(
'Enum property has duplicate values.',
enumProperty,
);
}
enumMap[element.name] = propertyValue;
}
}
} else {
if (dartType.isarType != null) {
isarType = dartType.isarType!;
} else {
err(
'Unsupported type. Please annotate the property with @ignore.',
property,
);
}
}
final nullable = dartType.nullabilitySuffix != NullabilitySuffix.none;
final elementNullable = isarType.isList &&
dartType.scalarType.nullabilitySuffix != NullabilitySuffix.none;
if ((isarType == IsarType.byte && nullable) ||
(isarType == IsarType.byteList && elementNullable)) {
err('Bytes must not be nullable.', property);
}
final constructorParameter =
constructor.parameters.firstOrNullWhere((p) => p.name == property.name);
int? constructorPosition;
late PropertyDeser deserialize;
if (constructorParameter != null) {
if (constructorParameter.type != property.type) {
err(
'Constructor parameter type does not match property type',
constructorParameter,
);
}
deserialize = constructorParameter.isNamed
? PropertyDeser.namedParam
: PropertyDeser.positionalParam;
constructorPosition =
constructor.parameters.indexOf(constructorParameter);
} else {
deserialize =
property.setter == null ? PropertyDeser.none : PropertyDeser.assign;
}
return ObjectProperty(
dartName: property.displayName,
isarName: property.isarName,
typeClassName: dartType.scalarType.element!.name!,
targetIsarName: isarType.containsObject
? dartType.scalarType.element!.isarName
: null,
isarType: isarType,
isId: dartType.isIsarId,
enumMap: enumMap,
enumProperty: enumPropertyName,
defaultEnumElement: defaultEnumElement,
nullable: nullable,
elementNullable: elementNullable,
userDefaultValue: constructorParameter?.defaultValueCode,
deserialize: deserialize,
assignable: property.setter != null,
constructorPosition: constructorPosition,
);
}
ObjectLink analyzeObjectLink(PropertyInducingElement property) {
if (property.type.nullabilitySuffix != NullabilitySuffix.none) {
err('Link properties must not be nullable.', property);
} else if (property.isLate) {
err('Link properties must not be late.', property);
}
final type = property.type as ParameterizedType;
final linkType = type.typeArguments[0];
if (linkType.nullabilitySuffix != NullabilitySuffix.none) {
err('Links type must not be nullable.', property);
}
final targetCol = linkType.element! as ClassElement;
if (targetCol.collectionAnnotation == null) {
err('Link target is not annotated with @collection');
}
final backlinkAnn = property.backlinkAnnotation;
String? targetLinkIsarName;
if (backlinkAnn != null) {
final targetProperty = targetCol.allAccessors
.firstOrNullWhere((e) => e.displayName == backlinkAnn.to);
if (targetProperty == null) {
err('Target of Backlink does not exist', property);
} else if (targetProperty.backlinkAnnotation != null) {
err('Target of Backlink is also a backlink', property);
}
if (!targetProperty.isLink && !targetProperty.isLinks) {
err('Target of backlink is not a link', property);
}
final targetLink = analyzeObjectLink(targetProperty);
targetLinkIsarName = targetLink.isarName;
}
return ObjectLink(
dartName: property.displayName,
isarName: property.isarName,
targetLinkIsarName: targetLinkIsarName,
targetCollectionDartName: linkType.element!.name!,
targetCollectionIsarName: targetCol.isarName,
isSingle: property.isLink,
);
}
Iterable<ObjectIndex> analyzeObjectIndex(
List<ObjectProperty> properties,
PropertyInducingElement element,
) sync* {
final property =
properties.firstOrNullWhere((it) => it.dartName == element.name);
if (property == null || property.isId) {
return;
}
for (final index in element.indexAnnotations) {
final indexProperties = <ObjectIndexProperty>[];
final isString = property.isarType == IsarType.string ||
property.isarType == IsarType.stringList;
final defaultType = property.isarType.isList || isString
? IndexType.hash
: IndexType.value;
indexProperties.add(
ObjectIndexProperty(
property: property,
type: index.type ?? defaultType,
caseSensitive: index.caseSensitive ?? isString,
),
);
for (final c in index.composite) {
final compositeProperty =
properties.firstOrNullWhere((it) => it.dartName == c.property);
if (compositeProperty == null) {
err('Property does not exist: "${c.property}".', element);
} else if (compositeProperty.isId) {
err('Ids cannot be indexed', element);
} else {
final isString = compositeProperty.isarType == IsarType.string ||
compositeProperty.isarType == IsarType.stringList;
final defaultType = compositeProperty.isarType.isList || isString
? IndexType.hash
: IndexType.value;
indexProperties.add(
ObjectIndexProperty(
property: compositeProperty,
type: c.type ?? defaultType,
caseSensitive: c.caseSensitive ?? isString,
),
);
}
}
final name = index.name ??
indexProperties.map((e) => e.property.isarName).join('_');
checkIsarName(name, element);
final objectIndex = ObjectIndex(
name: name,
properties: indexProperties,
unique: index.unique,
replace: index.replace,
);
_verifyObjectIndex(objectIndex, element);
yield objectIndex;
}
}
void _verifyObjectIndex(ObjectIndex index, Element element) {
final properties = index.properties;
if (properties.map((it) => it.property.isarName).distinct().length !=
properties.length) {
err('Composite index contains duplicate properties.', element);
}
for (var i = 0; i < properties.length; i++) {
final property = properties[i];
if (property.isarType.isList &&
property.type != IndexType.hash &&
properties.length > 1) {
err('Composite indexes do not support non-hashed lists.', element);
}
if (property.isarType.containsFloat && i != properties.lastIndex) {
err(
'Only the last property of a composite index may be a '
'double value.',
element,
);
}
if (property.isarType == IsarType.string) {
if (property.type != IndexType.hash && i != properties.lastIndex) {
err(
'Only the last property of a composite index may be a '
'non-hashed String.',
element,
);
}
}
if (property.isarType.containsObject) {
err(
'Embedded objects may not be indexed.',
element,
);
}
if (property.type != IndexType.value) {
if (!property.isarType.isList && property.isarType != IsarType.string) {
err('Only Strings and Lists may be hashed.', element);
} else if (property.isarType.containsFloat) {
err('List<double> may must not be hashed.', element);
}
}
if (property.isarType != IsarType.stringList &&
property.type == IndexType.hashElements) {
err('Only String lists may have hashed elements.', element);
}
}
if (!index.unique && index.replace) {
err('Only unique indexes can replace.', element);
}
}
}

107
lib/src/isar_type.dart Normal file

@ -0,0 +1,107 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:isar/isar.dart';
import 'package:isar_generator/src/helper.dart';
import 'package:source_gen/source_gen.dart';
const TypeChecker _dateTimeChecker = TypeChecker.fromRuntime(DateTime);
bool _isDateTime(Element element) => _dateTimeChecker.isExactly(element);
extension DartTypeX on DartType {
IsarType? get _primitiveIsarType {
if (isDartCoreBool) {
return IsarType.bool;
} else if (isDartCoreInt) {
if (alias?.element.name == 'byte') {
return IsarType.byte;
} else if (alias?.element.name == 'short') {
return IsarType.int;
} else {
return IsarType.long;
}
} else if (isDartCoreDouble) {
if (alias?.element.name == 'float') {
return IsarType.float;
} else {
return IsarType.double;
}
} else if (isDartCoreString) {
return IsarType.string;
} else if (_isDateTime(element2!)) {
return IsarType.dateTime;
} else if (element2!.embeddedAnnotation != null) {
return IsarType.object;
}
return null;
}
bool get isIsarId {
return alias?.element.name == 'Id';
}
DartType get scalarType {
if (isDartCoreList) {
final parameterizedType = this as ParameterizedType;
final typeArguments = parameterizedType.typeArguments;
if (typeArguments.isNotEmpty) {
return typeArguments[0];
}
}
return this;
}
IsarType? get isarType {
final primitiveType = _primitiveIsarType;
if (primitiveType != null) {
return primitiveType;
}
if (isDartCoreList) {
switch (scalarType._primitiveIsarType) {
case IsarType.bool:
return IsarType.boolList;
case IsarType.byte:
return IsarType.byteList;
case IsarType.int:
return IsarType.intList;
case IsarType.float:
return IsarType.floatList;
case IsarType.long:
return IsarType.longList;
case IsarType.double:
return IsarType.doubleList;
case IsarType.dateTime:
return IsarType.dateTimeList;
case IsarType.string:
return IsarType.stringList;
case IsarType.object:
return IsarType.objectList;
// ignore: no_default_cases
default:
return null;
}
}
return null;
}
}
extension IsarTypeX on IsarType {
bool get containsBool => this == IsarType.bool || this == IsarType.boolList;
bool get containsFloat =>
this == IsarType.float ||
this == IsarType.floatList ||
this == IsarType.double ||
this == IsarType.doubleList;
bool get containsDate =>
this == IsarType.dateTime || this == IsarType.dateTimeList;
bool get containsString =>
this == IsarType.string || this == IsarType.stringList;
bool get containsObject =>
this == IsarType.object || this == IsarType.objectList;
}

211
lib/src/object_info.dart Normal file

@ -0,0 +1,211 @@
import 'dart:convert';
import 'dart:typed_data';
import 'package:dartx/dartx.dart';
import 'package:isar/isar.dart';
import 'package:xxh3/xxh3.dart';
class ObjectInfo {
ObjectInfo({
required this.dartName,
required this.isarName,
this.accessor,
required List<ObjectProperty> properties,
this.embeddedDartNames = const {},
this.indexes = const [],
this.links = const [],
}) {
this.properties = properties.sortedBy((e) => e.isarName).toList();
}
final String dartName;
final String isarName;
final String? accessor;
late final List<ObjectProperty> properties;
final Map<String, String> embeddedDartNames;
final List<ObjectIndex> indexes;
final List<ObjectLink> links;
int get id => xxh3(utf8.encode(isarName) as Uint8List);
bool get isEmbedded => accessor == null;
ObjectProperty get idProperty => properties.firstWhere((it) => it.isId);
List<ObjectProperty> get objectProperties =>
properties.where((it) => !it.isId).toList();
String get getIdName => '_${dartName.decapitalize()}GetId';
String get getLinksName => '_${dartName.decapitalize()}GetLinks';
String get attachName => '_${dartName.decapitalize()}Attach';
String get estimateSizeName => '_${dartName.decapitalize()}EstimateSize';
String get serializeName => '_${dartName.decapitalize()}Serialize';
String get deserializeName => '_${dartName.decapitalize()}Deserialize';
String get deserializePropName =>
'_${dartName.decapitalize()}DeserializeProp';
}
enum PropertyDeser {
none,
assign,
positionalParam,
namedParam,
}
class ObjectProperty {
ObjectProperty({
required this.dartName,
required this.isarName,
required this.typeClassName,
this.targetIsarName,
required this.isarType,
required this.isId,
required this.enumMap,
required this.enumProperty,
required this.defaultEnumElement,
required this.nullable,
required this.elementNullable,
this.userDefaultValue,
required this.deserialize,
required this.assignable,
this.constructorPosition,
});
final String dartName;
final String isarName;
final String typeClassName;
final String? targetIsarName;
final bool isId;
final IsarType isarType;
final Map<String, dynamic>? enumMap;
final String? enumProperty;
final String? defaultEnumElement;
final bool nullable;
final bool elementNullable;
final String? userDefaultValue;
final PropertyDeser deserialize;
final bool assignable;
final int? constructorPosition;
bool get isEnum => enumMap != null;
String get scalarDartType {
if (isId) {
return 'Id';
} else if (isEnum) {
return typeClassName;
}
switch (isarType) {
case IsarType.bool:
case IsarType.boolList:
return 'bool';
case IsarType.byte:
case IsarType.byteList:
case IsarType.int:
case IsarType.intList:
case IsarType.long:
case IsarType.longList:
return 'int';
case IsarType.float:
case IsarType.floatList:
case IsarType.double:
case IsarType.doubleList:
return 'double';
case IsarType.dateTime:
case IsarType.dateTimeList:
return 'DateTime';
case IsarType.object:
case IsarType.objectList:
return typeClassName;
case IsarType.string:
case IsarType.stringList:
return 'String';
}
}
String get nScalarDartType => isarType.isList
? '$scalarDartType${elementNullable ? '?' : ''}'
: '$scalarDartType${nullable ? '?' : ''}';
String get dartType => isarType.isList
? 'List<$nScalarDartType>${nullable ? '?' : ''}'
: nScalarDartType;
String get targetSchema => '${scalarDartType.capitalize()}Schema';
String enumValueMapName(ObjectInfo object) {
return '_${object.dartName}${dartName}EnumValueMap';
}
String valueEnumMapName(ObjectInfo object) {
return '_${object.dartName}${dartName}ValueEnumMap';
}
}
class ObjectIndexProperty {
const ObjectIndexProperty({
required this.property,
required this.type,
required this.caseSensitive,
});
final ObjectProperty property;
final IndexType type;
final bool caseSensitive;
IsarType get isarType => property.isarType;
bool get isMultiEntry => isarType.isList && type != IndexType.hash;
}
class ObjectIndex {
ObjectIndex({
required this.name,
required this.properties,
required this.unique,
required this.replace,
});
final String name;
final List<ObjectIndexProperty> properties;
final bool unique;
final bool replace;
late final id = xxh3(utf8.encode(name) as Uint8List);
}
class ObjectLink {
const ObjectLink({
required this.dartName,
required this.isarName,
this.targetLinkIsarName,
required this.targetCollectionDartName,
required this.targetCollectionIsarName,
required this.isSingle,
});
final String dartName;
final String isarName;
// isar name of the original link (only for backlinks)
final String? targetLinkIsarName;
final String targetCollectionDartName;
final String targetCollectionIsarName;
final bool isSingle;
bool get isBacklink => targetLinkIsarName != null;
int id(String objectIsarName) {
final col = isBacklink ? targetCollectionIsarName : objectIsarName;
final colId = xxh3(utf8.encode(col) as Uint8List, seed: isBacklink ? 1 : 0);
final name = targetLinkIsarName ?? isarName;
return xxh3(utf8.encode(name) as Uint8List, seed: colId);
}
}

26
pubspec.yaml Normal file

@ -0,0 +1,26 @@
name: isar_generator
description: Code generator for the Isar Database. Finds classes annotated with @Collection.
version: 3.1.0+1
repository: https://github.com/isar/isar
homepage: https://isar.dev
environment:
sdk: ">=2.17.0 <3.0.0"
dependencies:
analyzer: ">=4.6.0 <6.0.0"
build: ^2.3.0
dart_style: ^2.2.3
dartx: ^1.1.0
glob: ^2.0.2
isar:
path: ../isar
path: ^1.8.1
source_gen: ^1.2.2
xxh3: ^1.0.1
dev_dependencies:
build_test: ^2.1.5
matcher: ^0.12.12
test: ^1.21.0
very_good_analysis: ^3.0.1

33
test/error_test.dart Normal file

@ -0,0 +1,33 @@
import 'dart:io';
import 'package:build/build.dart';
import 'package:build_test/build_test.dart';
import 'package:isar_generator/isar_generator.dart';
import 'package:test/test.dart';
void main() {
group('Error case', () {
for (final file in Directory('test/errors').listSync(recursive: true)) {
if (file is! File || !file.path.endsWith('.dart')) continue;
test(file.path, () async {
final content = await file.readAsLines();
final errorMessage = content.first.split('//').last.trim();
var error = '';
try {
await testBuilder(
getIsarGenerator(BuilderOptions.empty),
{'a|${file.path}': content.join('\n')},
reader: await PackageAssetReader.currentIsolate(),
);
} catch (e) {
error = e.toString();
}
expect(error.toLowerCase(), contains(errorMessage.toLowerCase()));
});
}
});
}

@ -0,0 +1,8 @@
// must not be abstract
import 'package:isar/isar.dart';
@collection
abstract class Model {
Id? id;
}

@ -0,0 +1,19 @@
// supertype annotated with @collection
import 'package:isar/isar.dart';
@collection
class Supertype {
Id? id;
}
class Subtype implements Supertype {
@override
Id? id;
}
@collection
class Model implements Subtype {
@override
Id? id;
}

@ -0,0 +1,10 @@
// unnamed constructor
import 'package:isar/isar.dart';
@collection
class Model {
Model.create();
Id? id;
}

@ -0,0 +1,13 @@
// constructor parameter does not match a property
import 'package:isar/isar.dart';
@collection
class Model {
// ignore: avoid_unused_constructor_parameters
Model(this.prop1, String somethingElse);
Id? id;
final String prop1;
}

@ -0,0 +1,13 @@
// constructor parameter type does not match property type
import 'package:isar/isar.dart';
@collection
class Model {
// ignore: avoid_unused_constructor_parameters
Model(int prop1);
Id? id;
String prop1 = '5';
}

@ -0,0 +1,7 @@
// only classes
import 'package:isar/isar.dart';
// ignore: invalid_annotation_target
@collection
enum Test { a, b, c }

@ -0,0 +1,8 @@
// must not be abstract
import 'package:isar/isar.dart';
@collection
abstract class Model {
Id? id;
}

@ -0,0 +1,7 @@
// only classes
import 'package:isar/isar.dart';
// ignore: invalid_annotation_target
@collection
mixin Test {}

@ -0,0 +1,9 @@
// must be public
import 'package:isar/isar.dart';
@collection
// ignore: unused_element
class _Model {
Id? id;
}

@ -0,0 +1,7 @@
// only classes
import 'package:isar/isar.dart';
// ignore: invalid_annotation_target
@collection
const t = 'hello';

@ -0,0 +1,10 @@
// two or more properties with type "Id" defined
import 'package:isar/isar.dart';
@collection
class Test {
Id? id1;
Id? id2;
}

@ -0,0 +1,10 @@
// no id property defined
import 'package:isar/isar.dart';
@collection
class Test {
late int id;
late String name;
}

@ -0,0 +1,13 @@
// only the last property of a composite index may be a double value
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Index(composite: [CompositeIndex('val2')])
double? val1;
String? val2;
}

@ -0,0 +1,13 @@
// composite indexes do not support non-hashed lists
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Index(composite: [CompositeIndex('str')], type: IndexType.value)
List<int>? list;
String? str;
}

@ -0,0 +1,13 @@
// last property of a composite index may be a non-hashed string
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Index(composite: [CompositeIndex('str2')], type: IndexType.value)
String? str1;
String? str2;
}

@ -0,0 +1,11 @@
// ids cannot be indexed
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Index(composite: [CompositeIndex('id')])
String? str;
}

@ -0,0 +1,11 @@
// list<double> may must not be hashed
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Index(type: IndexType.hash)
List<double>? list;
}

@ -0,0 +1,14 @@
// same name
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Index(name: 'myindex')
String? prop1;
@Index(name: 'myindex')
String? prop2;
}

@ -0,0 +1,13 @@
// composite index contains duplicate properties
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Index(composite: [CompositeIndex('str1')], type: IndexType.value)
String? str1;
String? str2;
}

@ -0,0 +1,11 @@
// names must not be blank or start with "_"
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Index(name: '_index')
String? str;
}

@ -0,0 +1,11 @@
// only strings and lists may be hashed
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Index(type: IndexType.hash)
int? val;
}

@ -0,0 +1,11 @@
// only string lists may have hashed elements
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Index(type: IndexType.hashElements)
List<int>? list;
}

@ -0,0 +1,11 @@
// only unique indexes can replace
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Index(replace: true)
String? str;
}

@ -0,0 +1,14 @@
// objects may not be indexed
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Index()
EmbeddedModel? obj;
}
@embedded
class EmbeddedModel {}

@ -0,0 +1,14 @@
// objects may not be indexed
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Index(type: IndexType.hash)
List<EmbeddedModel>? list;
}
@embedded
class EmbeddedModel {}

@ -0,0 +1,11 @@
// property does not exist
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Index(composite: [CompositeIndex('myProp')])
String? str;
}

@ -0,0 +1,16 @@
// target of backlink does not exist
import 'package:isar/isar.dart';
@collection
class Model1 {
Id? id;
@Backlink(to: 'abc')
final IsarLink<Model2> link = IsarLink();
}
@collection
class Model2 {
Id? id;
}

@ -0,0 +1,19 @@
// target of backlink is also a backlink
import 'package:isar/isar.dart';
@collection
class Model1 {
Id? id;
@Backlink(to: 'link')
final IsarLink<Model2> link = IsarLink();
}
@collection
class Model2 {
Id? id;
@Backlink(to: 'link')
final IsarLink<Model1> link = IsarLink();
}

@ -0,0 +1,18 @@
// target of backlink is not a link
import 'package:isar/isar.dart';
@collection
class Model1 {
Id? id;
@Backlink(to: 'str')
final IsarLink<Model2> link = IsarLink();
}
@collection
class Model2 {
Id? id;
String? str;
}

@ -0,0 +1,18 @@
// same name
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
final IsarLink<Model2> prop1 = IsarLink();
@Name('prop1')
final IsarLinks<Model2> prop2 = IsarLinks();
}
@collection
class Model2 {
Id? id;
}

@ -0,0 +1,16 @@
// names must not be blank or start with
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Name('_link')
final IsarLink<Model2> link = IsarLink();
}
@collection
class Model2 {
Id? id;
}

@ -0,0 +1,15 @@
// must not be late
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
late IsarLink<Model2> link;
}
@collection
class Model2 {
Id? id;
}

@ -0,0 +1,15 @@
// must not be nullable
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
IsarLink<Model2>? link;
}
@collection
class Model2 {
Id? id;
}

@ -0,0 +1,10 @@
// link target is not annotated with @collection
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
final IsarLink<int> link = IsarLink();
}

@ -0,0 +1,15 @@
// links type must not be nullable
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
final IsarLink<Model2?> link = IsarLink();
}
@collection
class Model2 {
Id? id;
}

@ -0,0 +1,13 @@
// same name
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
String? prop1;
@Name('prop1')
String? prop2;
}

@ -0,0 +1,17 @@
// unsupported enum property type
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Enumerated(EnumType.value, 'value')
late MyEnum field;
}
enum MyEnum {
optionA;
final bool value = true;
}

@ -0,0 +1,17 @@
// unsupported enum property type
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Enumerated(EnumType.value, 'value')
late MyEnum field;
}
enum MyEnum {
optionA;
final double value = 5.5;
}

@ -0,0 +1,21 @@
// has duplicate values
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Enumerated(EnumType.value, 'value')
late MyEnum field;
}
enum MyEnum {
option1(1),
option2(2),
option3(1);
const MyEnum(this.value);
final int value;
}

@ -0,0 +1,17 @@
// unsupported enum property type
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Enumerated(EnumType.value, 'value')
late MyEnum field;
}
enum MyEnum {
optionA;
final float value = 5.5;
}

@ -0,0 +1,17 @@
// unsupported enum property type
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Enumerated(EnumType.value, 'value')
late MyEnum prop;
}
enum MyEnum {
optionA;
final List<String> value = [];
}

@ -0,0 +1,14 @@
// enum property must be annotated with @enumerated
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
late MyEnum? prop;
}
enum MyEnum {
a;
}

@ -0,0 +1,17 @@
// null values are not supported
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Enumerated(EnumType.value, 'value')
late MyEnum prop;
}
enum MyEnum {
optionA;
final String? value = null;
}

@ -0,0 +1,20 @@
// unsupported enum property type
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Enumerated(EnumType.value, 'value')
late MyEnum prop;
}
enum MyEnum {
optionA;
final value = EmbeddedModel();
}
@embedded
class EmbeddedModel {}

@ -0,0 +1,11 @@
// names must not be blank or start with "_"
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
@Name('_prop')
String? prop;
}

@ -0,0 +1,10 @@
// bytes must not be nullable
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
late byte? prop;
}

@ -0,0 +1,10 @@
// bytes must not be nullable
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
late List<byte?> prop;
}

@ -0,0 +1,10 @@
// unsupported type
import 'package:isar/isar.dart';
@collection
class Model {
Id? id;
late Set<String>? prop;
}