Repository: isar-community/isar
Branch: v3
Commit: 36e70529ac90
Files: 688
Total size: 22.4 MB
Directory structure:
gitextract_p8q538ro/
├── .all-contributorsrc
├── .github/
│ ├── FUNDING.yml
│ ├── ISSUE_TEMPLATE/
│ │ ├── bug_report.md
│ │ ├── config.yml
│ │ └── feature_request.md
│ ├── actions/
│ │ └── prepare-build/
│ │ └── action.yaml
│ ├── dependabot.yaml
│ └── workflows/
│ ├── cron_test.yaml
│ ├── docs.yaml
│ ├── release.yaml
│ ├── skynet.yaml
│ ├── test.yaml
│ └── testlab.yaml
├── .gitignore
├── Cargo.toml
├── LICENSE
├── TODO.md
├── docs/
│ ├── .gitignore
│ ├── README.md
│ ├── docs/
│ │ ├── .vuepress/
│ │ │ ├── config.ts
│ │ │ ├── locales.ts
│ │ │ ├── redirect.ts
│ │ │ └── styles/
│ │ │ └── index.scss
│ │ ├── README.md
│ │ ├── crud.md
│ │ ├── de/
│ │ │ ├── README.md
│ │ │ ├── crud.md
│ │ │ ├── faq.md
│ │ │ ├── indexes.md
│ │ │ ├── limitations.md
│ │ │ ├── links.md
│ │ │ ├── queries.md
│ │ │ ├── recipes/
│ │ │ │ ├── data_migration.md
│ │ │ │ ├── full_text_search.md
│ │ │ │ ├── multi_isolate.md
│ │ │ │ └── string_ids.md
│ │ │ ├── schema.md
│ │ │ ├── transactions.md
│ │ │ ├── tutorials/
│ │ │ │ └── quickstart.md
│ │ │ └── watchers.md
│ │ ├── es/
│ │ │ ├── README.md
│ │ │ ├── crud.md
│ │ │ ├── faq.md
│ │ │ ├── indexes.md
│ │ │ ├── limitations.md
│ │ │ ├── links.md
│ │ │ ├── queries.md
│ │ │ ├── recipes/
│ │ │ │ ├── data_migration.md
│ │ │ │ ├── full_text_search.md
│ │ │ │ ├── multi_isolate.md
│ │ │ │ └── string_ids.md
│ │ │ ├── schema.md
│ │ │ ├── transactions.md
│ │ │ ├── tutorials/
│ │ │ │ └── quickstart.md
│ │ │ └── watchers.md
│ │ ├── faq.md
│ │ ├── fr/
│ │ │ ├── README.md
│ │ │ ├── crud.md
│ │ │ ├── faq.md
│ │ │ ├── indexes.md
│ │ │ ├── limitations.md
│ │ │ ├── links.md
│ │ │ ├── queries.md
│ │ │ ├── recipes/
│ │ │ │ ├── data_migration.md
│ │ │ │ ├── full_text_search.md
│ │ │ │ ├── multi_isolate.md
│ │ │ │ └── string_ids.md
│ │ │ ├── schema.md
│ │ │ ├── transactions.md
│ │ │ ├── tutorials/
│ │ │ │ └── quickstart.md
│ │ │ └── watchers.md
│ │ ├── indexes.md
│ │ ├── it/
│ │ │ ├── README.md
│ │ │ ├── crud.md
│ │ │ ├── faq.md
│ │ │ ├── indexes.md
│ │ │ ├── limitations.md
│ │ │ ├── links.md
│ │ │ ├── queries.md
│ │ │ ├── recipes/
│ │ │ │ ├── data_migration.md
│ │ │ │ ├── full_text_search.md
│ │ │ │ ├── multi_isolate.md
│ │ │ │ └── string_ids.md
│ │ │ ├── schema.md
│ │ │ ├── transactions.md
│ │ │ ├── tutorials/
│ │ │ │ └── quickstart.md
│ │ │ └── watchers.md
│ │ ├── ja/
│ │ │ ├── README.md
│ │ │ ├── crud.md
│ │ │ ├── faq.md
│ │ │ ├── indexes.md
│ │ │ ├── limitations.md
│ │ │ ├── links.md
│ │ │ ├── queries.md
│ │ │ ├── recipes/
│ │ │ │ ├── data_migration.md
│ │ │ │ ├── full_text_search.md
│ │ │ │ ├── multi_isolate.md
│ │ │ │ └── string_ids.md
│ │ │ ├── schema.md
│ │ │ ├── transactions.md
│ │ │ ├── tutorials/
│ │ │ │ └── quickstart.md
│ │ │ └── watchers.md
│ │ ├── ko/
│ │ │ ├── README.md
│ │ │ ├── crud.md
│ │ │ ├── faq.md
│ │ │ ├── indexes.md
│ │ │ ├── limitations.md
│ │ │ ├── links.md
│ │ │ ├── queries.md
│ │ │ ├── recipes/
│ │ │ │ ├── data_migration.md
│ │ │ │ ├── full_text_search.md
│ │ │ │ ├── multi_isolate.md
│ │ │ │ └── string_ids.md
│ │ │ ├── schema.md
│ │ │ ├── transactions.md
│ │ │ ├── tutorials/
│ │ │ │ └── quickstart.md
│ │ │ └── watchers.md
│ │ ├── limitations.md
│ │ ├── links.md
│ │ ├── pt/
│ │ │ ├── README.md
│ │ │ ├── crud.md
│ │ │ ├── faq.md
│ │ │ ├── indexes.md
│ │ │ ├── limitations.md
│ │ │ ├── links.md
│ │ │ ├── queries.md
│ │ │ ├── schema.md
│ │ │ ├── transactions.md
│ │ │ ├── tutorials/
│ │ │ │ └── quickstart.md
│ │ │ └── watchers.md
│ │ ├── queries.md
│ │ ├── recipes/
│ │ │ ├── data_migration.md
│ │ │ ├── full_text_search.md
│ │ │ ├── multi_isolate.md
│ │ │ └── string_ids.md
│ │ ├── schema.md
│ │ ├── transactions.md
│ │ ├── tutorials/
│ │ │ └── quickstart.md
│ │ ├── ur/
│ │ │ ├── README.md
│ │ │ ├── crud.md
│ │ │ ├── faq.md
│ │ │ ├── indexes.md
│ │ │ ├── limitations.md
│ │ │ ├── links.md
│ │ │ ├── queries.md
│ │ │ ├── recipes/
│ │ │ │ ├── data_migration.md
│ │ │ │ ├── full_text_search.md
│ │ │ │ ├── multi_isolate.md
│ │ │ │ └── string_ids.md
│ │ │ ├── schema.md
│ │ │ ├── transactions.md
│ │ │ ├── tutorials/
│ │ │ │ └── quickstart.md
│ │ │ └── watchers.md
│ │ ├── watchers.md
│ │ └── zh/
│ │ ├── README.md
│ │ ├── crud.md
│ │ ├── faq.md
│ │ ├── indexes.md
│ │ ├── limitations.md
│ │ ├── links.md
│ │ ├── queries.md
│ │ ├── recipes/
│ │ │ ├── data_migration.md
│ │ │ ├── full_text_search.md
│ │ │ ├── multi_isolate.md
│ │ │ └── string_ids.md
│ │ ├── schema.md
│ │ ├── transactions.md
│ │ ├── tutorials/
│ │ │ └── quickstart.md
│ │ └── watchers.md
│ └── package.json
├── examples/
│ └── pub/
│ ├── .gitignore
│ ├── .metadata
│ ├── README.md
│ ├── analysis_options.yaml
│ ├── lib/
│ │ ├── asset_loader.dart
│ │ ├── main.dart
│ │ ├── models/
│ │ │ ├── api/
│ │ │ │ ├── metrics.dart
│ │ │ │ └── package.dart
│ │ │ ├── asset.dart
│ │ │ └── package.dart
│ │ ├── package_manager.dart
│ │ ├── provider.dart
│ │ ├── repository.dart
│ │ └── ui/
│ │ ├── app_bar.dart
│ │ ├── detail_page.dart
│ │ ├── home_page.dart
│ │ ├── markdown_viewer.dart
│ │ ├── package_metadata.dart
│ │ ├── package_versions.dart
│ │ ├── publisher.dart
│ │ ├── search.dart
│ │ └── search_page.dart
│ └── pubspec.yaml
├── packages/
│ ├── isar/
│ │ ├── .gitignore
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── analysis_options.yaml
│ │ ├── example/
│ │ │ └── README.md
│ │ ├── lib/
│ │ │ ├── isar.dart
│ │ │ └── src/
│ │ │ ├── annotations/
│ │ │ │ ├── backlink.dart
│ │ │ │ ├── collection.dart
│ │ │ │ ├── embedded.dart
│ │ │ │ ├── enumerated.dart
│ │ │ │ ├── ignore.dart
│ │ │ │ ├── index.dart
│ │ │ │ ├── name.dart
│ │ │ │ └── type.dart
│ │ │ ├── common/
│ │ │ │ ├── isar_common.dart
│ │ │ │ ├── isar_link_base_impl.dart
│ │ │ │ ├── isar_link_common.dart
│ │ │ │ ├── isar_links_common.dart
│ │ │ │ └── schemas.dart
│ │ │ ├── isar.dart
│ │ │ ├── isar_collection.dart
│ │ │ ├── isar_connect.dart
│ │ │ ├── isar_connect_api.dart
│ │ │ ├── isar_error.dart
│ │ │ ├── isar_link.dart
│ │ │ ├── isar_reader.dart
│ │ │ ├── isar_writer.dart
│ │ │ ├── native/
│ │ │ │ ├── bindings.dart
│ │ │ │ ├── encode_string.dart
│ │ │ │ ├── index_key.dart
│ │ │ │ ├── isar_collection_impl.dart
│ │ │ │ ├── isar_core.dart
│ │ │ │ ├── isar_impl.dart
│ │ │ │ ├── isar_link_impl.dart
│ │ │ │ ├── isar_reader_impl.dart
│ │ │ │ ├── isar_writer_impl.dart
│ │ │ │ ├── open.dart
│ │ │ │ ├── query_build.dart
│ │ │ │ ├── query_impl.dart
│ │ │ │ ├── split_words.dart
│ │ │ │ └── txn.dart
│ │ │ ├── query.dart
│ │ │ ├── query_builder.dart
│ │ │ ├── query_builder_extensions.dart
│ │ │ ├── query_components.dart
│ │ │ ├── schema/
│ │ │ │ ├── collection_schema.dart
│ │ │ │ ├── index_schema.dart
│ │ │ │ ├── link_schema.dart
│ │ │ │ ├── property_schema.dart
│ │ │ │ └── schema.dart
│ │ │ └── web/
│ │ │ ├── bindings.dart
│ │ │ ├── isar_collection_impl.dart
│ │ │ ├── isar_impl.dart
│ │ │ ├── isar_link_impl.dart
│ │ │ ├── isar_reader_impl.dart
│ │ │ ├── isar_web.dart
│ │ │ ├── isar_writer_impl.dart
│ │ │ ├── open.dart
│ │ │ ├── query_build.dart
│ │ │ ├── query_impl.dart
│ │ │ └── split_words.dart
│ │ ├── pubspec.yaml
│ │ ├── test/
│ │ │ └── isar_reader_writer_test.dart
│ │ └── tool/
│ │ ├── get_version.dart
│ │ └── verify_release_version.dart
│ ├── isar_core/
│ │ ├── Cargo.toml
│ │ ├── README.md
│ │ ├── src/
│ │ │ ├── collection.rs
│ │ │ ├── cursor.rs
│ │ │ ├── error.rs
│ │ │ ├── index/
│ │ │ │ ├── index_key.rs
│ │ │ │ ├── index_key_builder.rs
│ │ │ │ └── mod.rs
│ │ │ ├── instance.rs
│ │ │ ├── legacy/
│ │ │ │ ├── isar_object_v1.rs
│ │ │ │ └── mod.rs
│ │ │ ├── lib.rs
│ │ │ ├── link.rs
│ │ │ ├── mdbx/
│ │ │ │ ├── cursor.rs
│ │ │ │ ├── db.rs
│ │ │ │ ├── env.rs
│ │ │ │ ├── mod.rs
│ │ │ │ └── txn.rs
│ │ │ ├── object/
│ │ │ │ ├── data_type.rs
│ │ │ │ ├── id.rs
│ │ │ │ ├── isar_object.rs
│ │ │ │ ├── json_encode_decode.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── object_builder.rs
│ │ │ │ └── property.rs
│ │ │ ├── query/
│ │ │ │ ├── fast_wild_match.rs
│ │ │ │ ├── filter.rs
│ │ │ │ ├── id_where_clause.rs
│ │ │ │ ├── index_where_clause.rs
│ │ │ │ ├── link_where_clause.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── query_builder.rs
│ │ │ │ └── where_clause.rs
│ │ │ ├── schema/
│ │ │ │ ├── collection_schema.rs
│ │ │ │ ├── index_schema.rs
│ │ │ │ ├── link_schema.rs
│ │ │ │ ├── migrate_v1.rs
│ │ │ │ ├── mod.rs
│ │ │ │ ├── property_schema.rs
│ │ │ │ └── schema_manager.rs
│ │ │ ├── txn.rs
│ │ │ └── watch/
│ │ │ ├── change_set.rs
│ │ │ ├── isar_watchers.rs
│ │ │ ├── mod.rs
│ │ │ └── watcher.rs
│ │ └── tests/
│ │ ├── binary_golden.json
│ │ ├── test_binary.rs
│ │ └── test_hash.rs
│ ├── isar_core_ffi/
│ │ ├── .cargo/
│ │ │ └── config.toml
│ │ ├── Cargo.toml
│ │ ├── build.rs
│ │ └── src/
│ │ ├── c_object_set.rs
│ │ ├── crud.rs
│ │ ├── dart.rs
│ │ ├── error.rs
│ │ ├── filter.rs
│ │ ├── index_key.rs
│ │ ├── instance.rs
│ │ ├── lib.rs
│ │ ├── link.rs
│ │ ├── query.rs
│ │ ├── query_aggregation.rs
│ │ ├── txn.rs
│ │ └── watchers.rs
│ ├── isar_flutter_libs/
│ │ ├── .pubignore
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── android/
│ │ │ ├── .gitignore
│ │ │ ├── build.gradle
│ │ │ ├── gradle/
│ │ │ │ └── wrapper/
│ │ │ │ └── gradle-wrapper.properties
│ │ │ ├── gradle.properties
│ │ │ ├── settings.gradle
│ │ │ └── src/
│ │ │ └── main/
│ │ │ ├── AndroidManifest.xml
│ │ │ └── java/
│ │ │ └── dev/
│ │ │ └── isar/
│ │ │ └── isar_flutter_libs/
│ │ │ └── IsarFlutterLibsPlugin.java
│ │ ├── ios/
│ │ │ ├── .gitignore
│ │ │ ├── Assets/
│ │ │ │ └── .gitkeep
│ │ │ ├── Classes/
│ │ │ │ ├── IsarFlutterLibsPlugin.h
│ │ │ │ ├── IsarFlutterLibsPlugin.m
│ │ │ │ ├── SwiftIsarFlutterLibsPlugin.swift
│ │ │ │ └── binding.h
│ │ │ ├── Resources/
│ │ │ │ └── PrivacyInfo.xcprivacy
│ │ │ └── isar_flutter_libs.podspec
│ │ ├── lib/
│ │ │ └── isar_flutter_libs.dart
│ │ ├── linux/
│ │ │ ├── CMakeLists.txt
│ │ │ ├── include/
│ │ │ │ └── isar_flutter_libs/
│ │ │ │ └── isar_flutter_libs_plugin.h
│ │ │ └── isar_flutter_libs_plugin.cc
│ │ ├── macos/
│ │ │ ├── Classes/
│ │ │ │ └── IsarFlutterLibsPlugin.swift
│ │ │ └── isar_flutter_libs.podspec
│ │ ├── pubspec.yaml
│ │ ├── pubspec_overrides.yaml
│ │ └── windows/
│ │ ├── .gitignore
│ │ ├── CMakeLists.txt
│ │ ├── include/
│ │ │ └── isar_flutter_libs/
│ │ │ └── isar_flutter_libs_plugin.h
│ │ └── isar_flutter_libs_plugin.cpp
│ ├── isar_generator/
│ │ ├── CHANGELOG.md
│ │ ├── LICENSE
│ │ ├── README.md
│ │ ├── analysis_options.yaml
│ │ ├── build.yaml
│ │ ├── lib/
│ │ │ ├── isar_generator.dart
│ │ │ └── src/
│ │ │ ├── code_gen/
│ │ │ │ ├── by_index_generator.dart
│ │ │ │ ├── collection_schema_generator.dart
│ │ │ │ ├── query_distinct_by_generator.dart
│ │ │ │ ├── query_filter_generator.dart
│ │ │ │ ├── query_filter_length.dart
│ │ │ │ ├── query_link_generator.dart
│ │ │ │ ├── query_object_generator.dart
│ │ │ │ ├── query_property_generator.dart
│ │ │ │ ├── query_sort_by_generator.dart
│ │ │ │ ├── query_where_generator.dart
│ │ │ │ └── type_adapter_generator.dart
│ │ │ ├── collection_generator.dart
│ │ │ ├── helper.dart
│ │ │ ├── isar_analyzer.dart
│ │ │ ├── isar_type.dart
│ │ │ └── object_info.dart
│ │ ├── pubspec.yaml
│ │ ├── pubspec_overrides.yaml
│ │ └── test/
│ │ ├── error_test.dart
│ │ └── errors/
│ │ ├── class/
│ │ │ ├── abstract.dart
│ │ │ ├── collection_supertype.dart
│ │ │ ├── constructor_named.dart
│ │ │ ├── constructor_unknown_parameter.dart
│ │ │ ├── constructor_wrong_parameter.dart
│ │ │ ├── enum.dart
│ │ │ ├── invalid_name.dart
│ │ │ ├── mixin.dart
│ │ │ ├── private.dart
│ │ │ └── variable.dart
│ │ ├── id/
│ │ │ ├── duplicate.dart
│ │ │ └── missing.dart
│ │ ├── index/
│ │ │ ├── composite_double_not_last.dart
│ │ │ ├── composite_non_hashed_list.dart
│ │ │ ├── composite_string_value_not_last.dart
│ │ │ ├── contains_id.dart
│ │ │ ├── double_list_hashed.dart
│ │ │ ├── duplicate_name.dart
│ │ │ ├── duplicate_property.dart
│ │ │ ├── invalid_name.dart
│ │ │ ├── non_string_hashed.dart
│ │ │ ├── non_string_list_hashed_elements.dart
│ │ │ ├── non_unique_replace.dart
│ │ │ ├── object_hashed.dart
│ │ │ ├── object_list_hashed.dart
│ │ │ └── property_does_not_exist.dart
│ │ ├── link/
│ │ │ ├── backlink_target_does_no_exist.dart
│ │ │ ├── backlink_target_is_backlink.dart
│ │ │ ├── backlink_target_not_a_link.dart
│ │ │ ├── duplicate_name.dart
│ │ │ ├── invalid_name.dart
│ │ │ ├── late.dart
│ │ │ ├── nullable.dart
│ │ │ ├── target_not_a_collection.dart
│ │ │ └── type_nullable.dart
│ │ └── property/
│ │ ├── duplicate_name.dart
│ │ ├── enum_bool_type.dart
│ │ ├── enum_double_type.dart
│ │ ├── enum_duplicate.dart
│ │ ├── enum_float_type.dart
│ │ ├── enum_list_type.dart
│ │ ├── enum_not_annotated.dart
│ │ ├── enum_null_value.dart
│ │ ├── enum_object_type.dart
│ │ ├── invalid_name.dart
│ │ ├── null_byte.dart
│ │ ├── null_byte_element.dart
│ │ └── unsupported_type.dart
│ ├── isar_inspector/
│ │ ├── .metadata
│ │ ├── README.md
│ │ ├── analysis_options.yaml
│ │ ├── isar_inspector.iml
│ │ ├── lib/
│ │ │ ├── collection/
│ │ │ │ ├── button_prev_next.dart
│ │ │ │ ├── button_sort.dart
│ │ │ │ ├── collection_area.dart
│ │ │ │ └── objects_list_sliver.dart
│ │ │ ├── collections_list.dart
│ │ │ ├── connect_client.dart
│ │ │ ├── connected_layout.dart
│ │ │ ├── connection_screen.dart
│ │ │ ├── error_screen.dart
│ │ │ ├── instance_selector.dart
│ │ │ ├── main.dart
│ │ │ ├── object/
│ │ │ │ ├── isar_object.dart
│ │ │ │ ├── object_view.dart
│ │ │ │ ├── property_builder.dart
│ │ │ │ ├── property_embedded_view.dart
│ │ │ │ ├── property_link_view.dart
│ │ │ │ ├── property_value.dart
│ │ │ │ └── property_view.dart
│ │ │ ├── query_builder/
│ │ │ │ ├── query_filter.dart
│ │ │ │ └── query_group.dart
│ │ │ ├── sidebar.dart
│ │ │ └── util.dart
│ │ ├── pubspec.yaml
│ │ └── web/
│ │ ├── index.html
│ │ └── manifest.json
│ ├── isar_test/
│ │ ├── .gitignore
│ │ ├── .metadata
│ │ ├── README.md
│ │ ├── analysis_options.yaml
│ │ ├── android/
│ │ │ ├── .gitignore
│ │ │ ├── app/
│ │ │ │ ├── build.gradle
│ │ │ │ └── src/
│ │ │ │ ├── androidTest/
│ │ │ │ │ └── java/
│ │ │ │ │ └── dev/
│ │ │ │ │ └── isar/
│ │ │ │ │ └── isar_test/
│ │ │ │ │ └── MainActivityTest.java
│ │ │ │ └── main/
│ │ │ │ ├── AndroidManifest.xml
│ │ │ │ ├── kotlin/
│ │ │ │ │ └── dev/
│ │ │ │ │ └── isar/
│ │ │ │ │ └── isar_test/
│ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── res/
│ │ │ │ ├── drawable/
│ │ │ │ │ └── launch_background.xml
│ │ │ │ └── values/
│ │ │ │ └── styles.xml
│ │ │ ├── build.gradle
│ │ │ ├── gradle/
│ │ │ │ └── wrapper/
│ │ │ │ └── gradle-wrapper.properties
│ │ │ ├── gradle.properties
│ │ │ ├── settings.gradle
│ │ │ └── settings_aar.gradle
│ │ ├── integration_test/
│ │ │ └── integration_test.dart
│ │ ├── ios/
│ │ │ ├── .gitignore
│ │ │ ├── Flutter/
│ │ │ │ ├── AppFrameworkInfo.plist
│ │ │ │ ├── Debug.xcconfig
│ │ │ │ └── Release.xcconfig
│ │ │ ├── Podfile
│ │ │ ├── Runner/
│ │ │ │ ├── AppDelegate.swift
│ │ │ │ ├── Assets.xcassets/
│ │ │ │ │ ├── AppIcon.appiconset/
│ │ │ │ │ │ └── Contents.json
│ │ │ │ │ └── LaunchImage.imageset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Base.lproj/
│ │ │ │ │ ├── LaunchScreen.storyboard
│ │ │ │ │ └── Main.storyboard
│ │ │ │ ├── Info.plist
│ │ │ │ └── Runner-Bridging-Header.h
│ │ │ ├── Runner.xcodeproj/
│ │ │ │ ├── project.pbxproj
│ │ │ │ ├── project.xcworkspace/
│ │ │ │ │ ├── contents.xcworkspacedata
│ │ │ │ │ └── xcshareddata/
│ │ │ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ │ │ └── WorkspaceSettings.xcsettings
│ │ │ │ └── xcshareddata/
│ │ │ │ └── xcschemes/
│ │ │ │ └── Runner.xcscheme
│ │ │ └── Runner.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── WorkspaceSettings.xcsettings
│ │ ├── lib/
│ │ │ ├── isar_test.dart
│ │ │ └── src/
│ │ │ ├── common.dart
│ │ │ ├── init_native.dart
│ │ │ ├── init_web.dart
│ │ │ ├── isar_web_src.dart
│ │ │ ├── listener.dart
│ │ │ ├── matchers.dart
│ │ │ ├── sync_async_helper.dart
│ │ │ ├── sync_future.dart
│ │ │ └── twitter/
│ │ │ ├── entities.dart
│ │ │ ├── geo.dart
│ │ │ ├── media.dart
│ │ │ ├── tweet.dart
│ │ │ ├── user.dart
│ │ │ └── util.dart
│ │ ├── linux/
│ │ │ ├── .gitignore
│ │ │ ├── CMakeLists.txt
│ │ │ ├── flutter/
│ │ │ │ ├── CMakeLists.txt
│ │ │ │ ├── generated_plugin_registrant.cc
│ │ │ │ ├── generated_plugin_registrant.h
│ │ │ │ └── generated_plugins.cmake
│ │ │ ├── main.cc
│ │ │ ├── my_application.cc
│ │ │ └── my_application.h
│ │ ├── macos/
│ │ │ ├── .gitignore
│ │ │ ├── Flutter/
│ │ │ │ ├── Flutter-Debug.xcconfig
│ │ │ │ └── Flutter-Release.xcconfig
│ │ │ ├── Podfile
│ │ │ ├── Runner/
│ │ │ │ ├── AppDelegate.swift
│ │ │ │ ├── Assets.xcassets/
│ │ │ │ │ └── AppIcon.appiconset/
│ │ │ │ │ └── Contents.json
│ │ │ │ ├── Base.lproj/
│ │ │ │ │ └── MainMenu.xib
│ │ │ │ ├── Configs/
│ │ │ │ │ ├── AppInfo.xcconfig
│ │ │ │ │ ├── Debug.xcconfig
│ │ │ │ │ ├── Release.xcconfig
│ │ │ │ │ └── Warnings.xcconfig
│ │ │ │ ├── DebugProfile.entitlements
│ │ │ │ ├── Info.plist
│ │ │ │ ├── MainFlutterWindow.swift
│ │ │ │ └── Release.entitlements
│ │ │ ├── Runner.xcodeproj/
│ │ │ │ ├── project.pbxproj
│ │ │ │ ├── project.xcworkspace/
│ │ │ │ │ └── xcshareddata/
│ │ │ │ │ └── IDEWorkspaceChecks.plist
│ │ │ │ └── xcshareddata/
│ │ │ │ └── xcschemes/
│ │ │ │ └── Runner.xcscheme
│ │ │ └── Runner.xcworkspace/
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata/
│ │ │ └── IDEWorkspaceChecks.plist
│ │ ├── pubspec.yaml
│ │ ├── test/
│ │ │ ├── clear_test.dart
│ │ │ ├── collection_size_test.dart
│ │ │ ├── compact_on_launch_test.dart
│ │ │ ├── constructor_test.dart
│ │ │ ├── copy_to_file_test.dart
│ │ │ ├── crud_test.dart
│ │ │ ├── default_value/
│ │ │ │ ├── common.dart
│ │ │ │ ├── default_test.dart
│ │ │ │ ├── no_default_test.dart
│ │ │ │ └── nullable_test.dart
│ │ │ ├── embedded_test.dart
│ │ │ ├── enum_test.dart
│ │ │ ├── filter/
│ │ │ │ ├── filter_bool_list_test.dart
│ │ │ │ ├── filter_bool_test.dart
│ │ │ │ ├── filter_byte_list_test.dart
│ │ │ │ ├── filter_byte_test.dart
│ │ │ │ ├── filter_date_time_list_test.dart
│ │ │ │ ├── filter_date_time_test.dart
│ │ │ │ ├── filter_embedded_list_test.dart
│ │ │ │ ├── filter_embedded_test.dart
│ │ │ │ ├── filter_float_list_test.dart
│ │ │ │ ├── filter_float_test.dart
│ │ │ │ ├── filter_id_test.dart
│ │ │ │ ├── filter_int_test.dart
│ │ │ │ ├── filter_list_length_test.dart
│ │ │ │ ├── filter_string_list_test.dart
│ │ │ │ ├── filter_string_test.dart
│ │ │ │ └── link/
│ │ │ │ ├── filter_backlink_test.dart
│ │ │ │ ├── filter_backlinks_test.dart
│ │ │ │ ├── filter_link_circular_direct_test.dart
│ │ │ │ ├── filter_link_circular_indirect_test.dart
│ │ │ │ ├── filter_link_nested_test.dart
│ │ │ │ ├── filter_link_self_test.dart
│ │ │ │ ├── filter_link_test.dart
│ │ │ │ ├── filter_links_self_test.dart
│ │ │ │ └── filter_links_test.dart
│ │ │ ├── id_test.dart
│ │ │ ├── index/
│ │ │ │ ├── composite2_test.dart
│ │ │ │ ├── composite3_test.dart
│ │ │ │ ├── composite_string_test.dart
│ │ │ │ ├── get_by_delete_by_test.dart
│ │ │ │ ├── multi_entry_test.dart
│ │ │ │ ├── put_by_test.dart
│ │ │ │ ├── where_bool_list_test.dart
│ │ │ │ ├── where_bool_test.dart
│ │ │ │ ├── where_byte_list_test.dart
│ │ │ │ ├── where_byte_test.dart
│ │ │ │ ├── where_date_time_list_test.dart
│ │ │ │ ├── where_date_time_test.dart
│ │ │ │ ├── where_float_list_test.dart
│ │ │ │ ├── where_float_test.dart
│ │ │ │ ├── where_id_test.dart
│ │ │ │ ├── where_int_test.dart
│ │ │ │ ├── where_string_list_test.dart
│ │ │ │ └── where_string_test.dart
│ │ │ ├── inheritance_test.dart
│ │ │ ├── instance_test.dart
│ │ │ ├── isolate_test.dart
│ │ │ ├── json_test.dart
│ │ │ ├── link_test.dart
│ │ │ ├── links/
│ │ │ │ ├── backlink_test.dart
│ │ │ │ ├── link_test.dart
│ │ │ │ └── links_test.dart
│ │ │ ├── max_size_test.dart
│ │ │ ├── migration/
│ │ │ │ ├── add_remove_collection_test.dart
│ │ │ │ ├── add_remove_embedded_field_test.dart
│ │ │ │ ├── add_remove_field_test.dart
│ │ │ │ ├── add_remove_index_test.dart
│ │ │ │ ├── add_remove_link_test.dart
│ │ │ │ ├── change_field_embedded_test.dart
│ │ │ │ ├── change_field_nullability_test.dart
│ │ │ │ ├── change_field_type_test.dart
│ │ │ │ └── change_link_links_test.dart
│ │ │ ├── mutli_type_model.dart
│ │ │ ├── name_test.dart
│ │ │ ├── open_close_isar_listener_test.dart
│ │ │ ├── other_test.dart
│ │ │ ├── query/
│ │ │ │ ├── aggregation_test.dart
│ │ │ │ ├── embedded_test.dart
│ │ │ │ ├── group_test.dart
│ │ │ │ ├── is_empty_is_not_empty_test.dart
│ │ │ │ ├── multi_filter_test.dart
│ │ │ │ ├── offset_limit_test.dart
│ │ │ │ ├── property_test.dart
│ │ │ │ ├── sort_by_distinct_by_test.dart
│ │ │ │ └── where_sort_distinct_test.dart
│ │ │ ├── regression/
│ │ │ │ └── issue_235_rename_field_test.dart
│ │ │ ├── schema_test.dart
│ │ │ ├── stress/
│ │ │ │ ├── long_string_test.dart
│ │ │ │ └── twitter_test.dart
│ │ │ ├── transaction_test.dart
│ │ │ ├── type_models.dart
│ │ │ ├── user_model.dart
│ │ │ └── watcher_test.dart
│ │ ├── tool/
│ │ │ ├── generate_all_tests.dart
│ │ │ └── generate_long_double_test.dart
│ │ ├── web/
│ │ │ ├── index.html
│ │ │ └── manifest.json
│ │ └── windows/
│ │ ├── .gitignore
│ │ ├── CMakeLists.txt
│ │ ├── flutter/
│ │ │ ├── CMakeLists.txt
│ │ │ ├── generated_plugin_registrant.cc
│ │ │ ├── generated_plugin_registrant.h
│ │ │ └── generated_plugins.cmake
│ │ └── runner/
│ │ ├── CMakeLists.txt
│ │ ├── Runner.rc
│ │ ├── flutter_window.cpp
│ │ ├── flutter_window.h
│ │ ├── main.cpp
│ │ ├── resource.h
│ │ ├── runner.exe.manifest
│ │ ├── utils.cpp
│ │ ├── utils.h
│ │ ├── win32_window.cpp
│ │ └── win32_window.h
│ ├── isar_web/
│ │ ├── .eslintrc.yml
│ │ ├── .gitignore
│ │ ├── .prettierrc
│ │ ├── README.md
│ │ ├── package.json
│ │ ├── src/
│ │ │ ├── bulk-delete.ts
│ │ │ ├── collection.ts
│ │ │ ├── cursor.ts
│ │ │ ├── index.ts
│ │ │ ├── instance.ts
│ │ │ ├── link.ts
│ │ │ ├── open.ts
│ │ │ ├── query.ts
│ │ │ ├── schema.ts
│ │ │ ├── txn.ts
│ │ │ └── watcher.ts
│ │ ├── tsconfig.json
│ │ └── webpack.config.js
│ └── mdbx_sys/
│ ├── .gitignore
│ ├── Cargo.toml
│ ├── build.rs
│ └── src/
│ └── lib.rs
└── tool/
├── build.sh
├── build_android.sh
├── build_ios.sh
├── build_linux.sh
├── build_macos.sh
├── build_windows.sh
├── cbindgen.toml
├── download_binaries.sh
├── ffigen.yaml
├── generate_bindings.sh
├── prepare_tests.sh
├── publish.sh
└── replace-versions.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .all-contributorsrc
================================================
{
"files": [
"packages/isar/README.md"
],
"imageSize": 100,
"commit": false,
"contributors": [
{
"login": "Jtplouffe",
"name": "JT",
"avatar_url": "https://avatars.githubusercontent.com/u/32107801?v=4",
"profile": "https://github.com/Jtplouffe",
"contributions": [
"test",
"bug"
]
},
{
"login": "leisim",
"name": "Simon Leier",
"avatar_url": "https://avatars.githubusercontent.com/u/13610195?v=4",
"profile": "https://www.linkedin.com/in/simon-leier/",
"contributions": [
"bug",
"code",
"doc",
"test",
"example"
]
},
{
"login": "h1376h",
"name": "Hamed H.",
"avatar_url": "https://avatars.githubusercontent.com/u/3498335?v=4",
"profile": "https://github.com/h1376h",
"contributions": [
"code",
"maintenance"
]
},
{
"login": "Viper-Bit",
"name": "Peyman",
"avatar_url": "https://avatars.githubusercontent.com/u/24822764?v=4",
"profile": "https://github.com/Viper-Bit",
"contributions": [
"bug",
"code"
]
},
{
"login": "blendthink",
"name": "blendthink",
"avatar_url": "https://avatars.githubusercontent.com/u/32213113?v=4",
"profile": "https://github.com/blendthink",
"contributions": [
"maintenance"
]
},
{
"login": "Moseco",
"name": "Moseco",
"avatar_url": "https://avatars.githubusercontent.com/u/10720298?v=4",
"profile": "https://github.com/Moseco",
"contributions": [
"bug"
]
},
{
"login": "Frostedfox",
"name": "Frostedfox",
"avatar_url": "https://avatars.githubusercontent.com/u/84601232?v=4",
"profile": "https://github.com/Frostedfox",
"contributions": [
"doc"
]
},
{
"login": "nohli",
"name": "Joachim Nohl",
"avatar_url": "https://avatars.githubusercontent.com/u/43643339?v=4",
"profile": "http://achim.io",
"contributions": [
"maintenance"
]
},
{
"login": "VoidxHoshi",
"name": "LaLucid",
"avatar_url": "https://avatars.githubusercontent.com/u/55886143?v=4",
"profile": "https://github.com/VoidxHoshi",
"contributions": [
"maintenance"
]
},
{
"login": "vothvovo",
"name": "Johnson",
"avatar_url": "https://avatars.githubusercontent.com/u/20894472?v=4",
"profile": "https://github.com/vothvovo",
"contributions": [
"bug"
]
},
{
"login": "ika020202",
"name": "Ura",
"avatar_url": "https://avatars.githubusercontent.com/u/42883378?v=4",
"profile": "https://zenn.dev/urasan",
"contributions": [
"translation"
]
},
{
"login": "mnkeis",
"name": "mnkeis",
"avatar_url": "https://avatars.githubusercontent.com/u/41247357?v=4",
"profile": "https://github.com/mnkeis",
"contributions": [
"translation"
]
},
{
"login": "CarloDotLog",
"name": "Carlo Loguercio",
"avatar_url": "https://avatars.githubusercontent.com/u/13763473?v=4",
"profile": "https://github.com/CarloDotLog",
"contributions": [
"translation"
]
},
{
"login": "hafeezrana",
"name": "Hafeez Rana",
"avatar_url": "https://avatars.githubusercontent.com/u/87476445?v=4",
"profile": "https://g.dev/hafeezrana",
"contributions": [
"doc"
]
},
{
"login": "inkomomutane",
"name": "Nelson Mutane",
"avatar_url": "https://avatars.githubusercontent.com/u/57417802?v=4",
"profile": "https://github.com/inkomomutane",
"contributions": [
"translation"
]
},
{
"login": "lodisy",
"name": "Michael",
"avatar_url": "https://avatars.githubusercontent.com/u/8101584?v=4",
"profile": "https://github.com/lodisy",
"contributions": [
"translation"
]
},
{
"login": "ritksm",
"name": "Jack Rivers",
"avatar_url": "https://avatars.githubusercontent.com/u/111809?v=4",
"profile": "http://blog.jackrivers.me/",
"contributions": [
"translation"
]
},
{
"login": "buraktabn",
"name": "Burak",
"avatar_url": "https://avatars.githubusercontent.com/u/49204989?v=4",
"profile": "http://buraktaban.ca",
"contributions": [
"bug"
]
},
{
"login": "AlexisL61",
"name": "Alexis",
"avatar_url": "https://avatars.githubusercontent.com/u/30233189?v=4",
"profile": "https://github.com/AlexisL61",
"contributions": [
"bug"
]
},
{
"login": "letyletylety",
"name": "Lety",
"avatar_url": "https://avatars.githubusercontent.com/u/16468579?v=4",
"profile": "https://letyarch.blogspot.com/",
"contributions": [
"doc"
]
},
{
"login": "nobkd",
"name": "nobkd",
"avatar_url": "https://avatars.githubusercontent.com/u/44443899?v=4",
"profile": "https://github.com/nobkd",
"contributions": [
"doc"
]
}
],
"contributorTemplate": "\">
\" width=\"<%= options.imageSize %>px;\" alt=\"\"/>
<%= contributor.name %>",
"contributorsPerLine": 7,
"contributorsSortAlphabetically": true,
"projectName": "isar",
"projectOwner": "isar",
"repoType": "github",
"repoHost": "https://github.com",
"skipCi": true,
"commitConvention": "angular"
}
================================================
FILE: .github/FUNDING.yml
================================================
github: simc
================================================
FILE: .github/ISSUE_TEMPLATE/bug_report.md
================================================
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: problem
assignees: leisim
---
### Steps to Reproduce
Please describe exactly how to reproduce the problem you are running into.
### Code sample
```dart
Provide a few simple lines of code to show your problem.
```
### Details
- Platform: iPhone 13 Pro, Galaxy S7, x86 Android Emulator on Windows etc.
- Flutter version: [e.g. 3.0.0]
- Isar version: [e.g. 2.5.0]
---
- [ ] I searched for similar issues already
- [ ] I filled the details section with the exact device model and version
- [ ] I am able to provide a reproducible example
================================================
FILE: .github/ISSUE_TEMPLATE/config.yml
================================================
blank_issues_enabled: false
contact_links:
- name: Ask a question
url: https://github.com/isar-community/isar/discussions
about: Ask questions and discuss with other community members
================================================
FILE: .github/ISSUE_TEMPLATE/feature_request.md
================================================
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: enhancement
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Version**
- Platform: iOS, Android, Mac, Windows, Linux, Web
- Flutter version: [e.g. 1.5.4]
- Isar version: [e.g. 0.5.0]
================================================
FILE: .github/actions/prepare-build/action.yaml
================================================
name: "Prepare Build"
description: "Prepares the build for Isar Core"
runs:
using: "composite"
steps:
- name: Install LLVM and Clang
if: runner.os == 'Windows'
uses: KyleMayes/install-llvm-action@v1
with:
version: "11.0"
directory: ${{ runner.temp }}/llvm
- name: Set LIBCLANG_PATH
if: runner.os == 'Windows'
shell: bash
run: echo "LIBCLANG_PATH=$((gcm clang).source -replace "clang.exe")" >> $env:GITHUB_ENV
# See https://github.com/godot-rust/godot-rust/pull/920
- name: "Workaround Android NDK due to Rust bug"
if: runner.os == 'Linux' || runner.os == 'macOS'
shell: bash
run: >
find -L $ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION -name libunwind.a
-execdir sh -c 'echo "INPUT(-lunwind)" > libgcc.a' \;
================================================
FILE: .github/dependabot.yaml
================================================
version: 2
enable-beta-ecosystems: true
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
reviewers:
- "leisim"
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
reviewers:
- "leisim"
- package-ecosystem: "pub"
directory: "/packages/isar"
schedule:
interval: "weekly"
reviewers:
- "leisim"
- package-ecosystem: "pub"
directory: "/packages/isar_flutter_libs"
schedule:
interval: "weekly"
reviewers:
- "leisim"
- package-ecosystem: "pub"
directory: "/packages/isar_generator"
schedule:
interval: "weekly"
reviewers:
- "leisim"
- package-ecosystem: "pub"
directory: "/packages/isar_inspector"
schedule:
interval: "weekly"
reviewers:
- "leisim"
- package-ecosystem: "pub"
directory: "/packages/isar_test"
schedule:
interval: "weekly"
reviewers:
- "leisim"
- package-ecosystem: "pub"
directory: "/examples/pub"
schedule:
interval: "weekly"
reviewers:
- "leisim"
- package-ecosystem: "gradle"
directory: "/packages/isar_flutter_libs/android"
schedule:
interval: "weekly"
reviewers:
- "leisim"
- package-ecosystem: "gradle"
directory: "/packages/isar_test/android"
schedule:
interval: "weekly"
reviewers:
- "leisim"
- package-ecosystem: "npm"
directory: "/docs"
schedule:
interval: "weekly"
reviewers:
- "leisim"
- package-ecosystem: "npm"
directory: "/packages/isar_web"
schedule:
interval: "weekly"
reviewers:
- "leisim"
================================================
FILE: .github/workflows/cron_test.yaml
================================================
name: Dart CI Cron
on:
schedule:
- cron: "0 0 * * 0"
jobs:
testlab:
uses: ./.github/workflows/testlab.yaml
secrets: inherit
================================================
FILE: .github/workflows/docs.yaml
================================================
name: Unified Deploy Docs
on:
push:
branches:
- main
- v3
jobs:
deploy:
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
permissions:
contents: write
id-token: write
pages: write
steps:
- name: Checkout v3 branch
uses: actions/checkout@v4
with:
ref: "v3"
path: "v3"
- name: Build v3 docs
run: |
cd v3
git fetch --unshallow
git fetch --tags
tool/replace-versions.sh
cd docs
sed -i'.bak' "s|base:.*|base: '/v3/',|" docs/.vuepress/config.ts
sed -i 's|text: "vx.x"|text: "v3.x"|' docs/.vuepress/config.ts
npm ci
npm run build
mv ./docs/.vuepress/dist ../../v3-docs
- name: Checkout main branch
uses: actions/checkout@v4
with:
ref: "main"
path: "main"
- name: Build main docs
run: |
cd main
git fetch --tags
tool/replace-versions.sh
cd docs
sed -i'.bak' "s|base:.*|base: '/',|" docs/.vuepress/config.ts
sed -i 's|text: "vx.x"|text: "v4.x"|' docs/.vuepress/config.ts
npm ci
npm run build
mv ./docs/.vuepress/dist ../../main-docs
- name: Prepare deployment directory
run: |
mkdir deploy
mv main-docs/* deploy/
mkdir deploy/v3
mv v3-docs/* deploy/v3/
- name: Setup Pages
uses: actions/configure-pages@v4
- name: Upload artifact
uses: actions/upload-pages-artifact@v3
with:
path: deploy
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v4
================================================
FILE: .github/workflows/release.yaml
================================================
name: Isar release
on:
push:
tags:
- "*"
jobs:
verify_version:
name: Verify version matches release
runs-on: ubuntu-latest
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
sdk: [3.0.0]
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Verify release version
run: |
flutter pub get
dart tool/verify_release_version.dart ${{ github.ref_name }}
working-directory: packages/isar
build_binaries:
name: Build Binaries
needs: verify_version
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
artifact_name: libisar_android_arm64.so
script: build_android.sh
- os: ubuntu-latest
artifact_name: libisar_android_armv7.so
script: build_android.sh armv7
- os: ubuntu-latest
artifact_name: libisar_android_x64.so
script: build_android.sh x64
- os: ubuntu-latest
artifact_name: libisar_android_x86.so
script: build_android.sh x86
- os: macos-latest
artifact_name: isar_ios.xcframework.zip
script: build_ios.sh
- os: ubuntu-20.04
artifact_name: libisar_linux_x64.so
script: build_linux.sh x64
- os: macos-latest
artifact_name: libisar_macos.dylib
script: build_macos.sh
- os: windows-latest
artifact_name: isar_windows_arm64.dll
script: build_windows.sh
- os: windows-latest
artifact_name: isar_windows_x64.dll
script: build_windows.sh x64
runs-on: ${{ matrix.os }}
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Set env
run: echo "ISAR_VERSION=${{ github.ref_name }}" >> $GITHUB_ENV
- name: Build binary
run: bash tool/${{ matrix.script }}
- name: Upload binary
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: ${{ matrix.artifact_name }}
asset_name: ${{ matrix.artifact_name }}
tag: ${{ github.ref }}
testlab:
needs: build_binaries
uses: ./.github/workflows/testlab.yaml
secrets: inherit
build_inspector:
name: Build Inspector
needs: build_binaries
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Build
run: flutter build web --base-href /${{ github.ref_name }}/
working-directory: packages/isar_inspector
- name: Deploy to GitHub Pages
uses: JamesIves/github-pages-deploy-action@v4
with:
folder: packages/isar_inspector/build/web
repository-name: isar-community/inspector
token: ${{ secrets.TOKEN }}
target-folder: ${{ github.ref_name }}
clean: false
upload_to_repo:
needs: build_binaries
runs-on: ubuntu-latest
steps:
- name: Download all artifacts
uses: actions/download-artifact@v2
with:
path: binaries/
- name: List contents of downloaded artifacts
run: |
echo "Listing contents of all downloaded artifacts..."
ls -Rlh binaries/
echo "Listing complete."
- name: Setup Git and clone target repository
run: |
git config --global user.email "vicente.russo@gmail.com"
git config --global user.name "GitHub Actions"
git clone https://github.com/isar-community/binaries repo
cd repo
git checkout main || git checkout -b main
env:
GITHUB_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
- name: Copy binaries to repository and push
run: |
cd repo
ISAR_VERSION=$(echo "${{ github.ref_name }}" | sed 's/refs\/tags\///')
echo "Deploying binaries to version: $ISAR_VERSION"
mkdir -p "$ISAR_VERSION"
cp ../binaries/**/* "$ISAR_VERSION"
git add .
git commit -m "Deploy binaries for version $ISAR_VERSION" || echo "No changes to commit"
git push https://x-access-token:${GITHUB_TOKEN}@github.com/isar-community/binaries.git main
env:
GITHUB_TOKEN: ${{ secrets.DEPLOY_TOKEN }}
# publish:
# name: Publish
# needs: build_inspector
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: subosito/flutter-action@v2
# - name: pub get
# run: dart pub get
# working-directory: packages/isar
# - name: Download Binaries
# run: sh tool/download_binaries.sh
# - name: pub.dev credentials
# run: |
# mkdir -p $HOME/.config/dart
# echo '${{ secrets.PUB_JSON }}' >> $HOME/.config/dart/pub-credentials.json
# - name: Publish isar
# run: dart pub publish --force
# working-directory: packages/isar
# - name: Publish isar_generator
# run: dart pub publish --force
# working-directory: packages/isar_generator
# - name: Publish isar_flutter_libs
# run: dart pub publish --force
# working-directory: packages/isar_flutter_libs
================================================
FILE: .github/workflows/skynet.yaml
================================================
name: Triggers remote jenkins
on:
push:
branches:
- main
- v3
permissions:
contents: read
pages: write
id-token: write
concurrency:
group: "pages"
cancel-in-progress: true
jobs:
trigger_remote:
name: Trigger remote jenkins instance
runs-on: ubuntu-latest
steps:
- name: Invoke trigger
run: |
curl -s 'https://isar-community.dev/git/notifyCommit?url=https://github.com/isar-community/isar.git&token=a641b59c61c22effd6dd258f8e713c7b'
================================================
FILE: .github/workflows/test.yaml
================================================
name: Dart CI
on:
push:
branches:
- v3
pull_request:
branches:
- v3
jobs:
version:
name: Version Display
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- run: flutter --version
format:
name: Check formatting
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Check formatting
run: dart format --set-exit-if-changed .
lint:
name: Check lints
runs-on: ubuntu-latest
if: ${{ false }}
steps:
- uses: actions/checkout@v4
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- run: flutter pub get
working-directory: packages/isar
- run: flutter pub get
working-directory: packages/isar_flutter_libs
- run: flutter pub get
working-directory: packages/isar_generator
- run: flutter pub get
working-directory: packages/isar_inspector
- run: flutter pub get
working-directory: examples/pub
- run: |
flutter pub get
flutter pub run build_runner build
dart tool/generate_all_tests.dart
working-directory: packages/isar_test
- name: Lint
run: flutter analyze
test:
name: Dart Test
strategy:
matrix:
os: [macos-latest, ubuntu-latest, windows-latest]
fail-fast: false
runs-on: ${{ matrix.os }}
steps:
- run: echo "$OSTYPE"
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core
run: sh tool/build.sh
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Unit tests
run: flutter test -j 1
working-directory: packages/isar_test
valgrind:
name: Valgrind
runs-on: ubuntu-latest
if: ${{ false }}
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Install valgrind and llvm
run: sudo apt update && sudo apt install -y valgrind libclang-dev
- name: Build Isar Core
run: sh tool/build.sh
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Valgrind
run: |
dart compile exe integration_test/all_tests.dart
valgrind \
--leak-check=full \
--error-exitcode=1 \
--show-mismatched-frees=no \
--show-possibly-lost=no \
--errors-for-leak-kinds=definite \
integration_test/all_tests.exe
working-directory: packages/isar_test
coverage:
name: Code Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core
run: sh tool/build.sh
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Add packages
run: |
flutter pub add json_annotation
flutter pub add intl
flutter pub add isar_test --path ../isar_test
working-directory: packages/isar
- name: Collect isar Coverage
run: |
flutter test --coverage --coverage-path lcov_isar.info
working-directory: packages/isar
- name: Collect isar_test Coverage
run: |
flutter test --coverage ../isar_test/test --coverage-path lcov_isar_test.info
working-directory: packages/isar
- name: Upload isar Coverage
uses: codecov/codecov-action@v3
with:
files: packages/isar/lcov_isar.info
- name: Upload isar_test Coverage
uses: codecov/codecov-action@v3
with:
files: packages/isar/lcov_isar_test.info
test_generator:
name: Generator Test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Run Generator Unit tests
run: |
dart pub get
dart test
working-directory: packages/isar_generator
integration_test_ios:
name: Integration Test iOS
runs-on: macos-12
steps:
- uses: actions/checkout@v4
- name: Start simulator
uses: futureware-tech/simulator-action@v3
with:
model: iPhone 13
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core
run: |
bash tool/build_ios.sh
unzip isar_ios.xcframework.zip -d packages/isar_flutter_libs/ios
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Integration tests
run: flutter test integration_test/integration_test.dart --dart-define STRESS=true
working-directory: packages/isar_test
integration_test_android:
name: Integration Test Android
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v3
with:
java-version: "11"
distribution: "zulu"
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core
run: |
bash tool/build_android.sh x64
mkdir -p packages/isar_flutter_libs/android/src/main/jniLibs/x86_64
mv libisar_android_x64.so packages/isar_flutter_libs/android/src/main/jniLibs/x86_64/libisar.so
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Integration tests
continue-on-error: true
timeout-minutes: ${{ inputs.timeout_minutes }}
uses: Wandalen/wretry.action@v1.0.36
with:
action: reactivecircus/android-emulator-runner@v2
with: |
api-level: 29
arch: x86_64
profile: pixel
working-directory: packages/isar_test
script: flutter test integration_test/integration_test.dart --dart-define STRESS=true
integration_test_macos:
name: Integration Test macOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
# flutter-version: "3.3.10" # https://github.com/flutter/flutter/issues/118469
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core
run: |
bash tool/build_macos.sh
install_name_tool -id @rpath/libisar.dylib libisar_macos.dylib
mv libisar_macos.dylib packages/isar_flutter_libs/macos/libisar.dylib
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Driver tests
run: |
flutter config --enable-macos-desktop
flutter test -d macos integration_test/integration_test.dart --dart-define STRESS=true
working-directory: packages/isar_test
integration_test_linux:
name: Integration Test Linux
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Install Linux requirements
run: sudo apt-get install clang cmake ninja-build pkg-config libgtk-3-dev
- name: Setup headless display
uses: pyvista/setup-headless-display-action@v1
- name: Build Isar Core
run: |
bash tool/build_linux.sh x64
mv libisar_linux_x64.so packages/isar_flutter_libs/linux/libisar.so
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Driver tests
run: |
flutter config --enable-linux-desktop
flutter test -d linux integration_test/integration_test.dart --dart-define STRESS=true
working-directory: packages/isar_test
integration_test_windows:
name: Integration Test Windows
runs-on: windows-2019
if: ${{ false }}
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core
run: |
bash tool/build_windows.sh x64
mv isar_windows_x64.dll packages/isar_flutter_libs/windows/libisar.dll
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Run Flutter Driver tests
run: |
flutter config --enable-windows-desktop
flutter test -d windows integration_test/integration_test.dart --dart-define STRESS=true
working-directory: packages/isar_test
drive_chrome:
runs-on: ubuntu-latest
if: ${{ false }}
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Install chromedricer
uses: nanasess/setup-chromedriver@v1
- name: Prepare chromedricer
run: chromedriver --port=4444 &
- name: Run Dart tests in browser
run: |
flutter pub get
dart tool/generate_long_double_test.dart
dart tool/generate_all_tests.dart
flutter pub run build_runner build
flutter drive --driver=isar_driver.dart --target=isar_driver_target.dart -d web-server --browser-name chrome
working-directory: packages/isar_test
drive_safari:
runs-on: macos-latest
if: ${{ false }}
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Prepare safaridricer
run: |
sudo safaridriver --enable
safaridriver --port=4444 &
- name: Run Dart tests in browser
run: |
flutter pub get
dart tool/generate_long_double_test.dart
flutter pub run build_runner build
dart tool/generate_all_tests.dart
flutter drive --driver=isar_driver.dart --target=isar_driver_target.dart -d web-server --browser-name safari
working-directory: packages/isar_test
drive_firefox:
runs-on: ubuntu-latest
if: ${{ false }}
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Install geckodriver
uses: browser-actions/setup-geckodriver@latest
- name: Prepare geckodriver
run: geckodriver --port=4444 &
- name: Run Dart tests in browser
run: |
flutter pub get
dart tool/generate_long_double_test.dart
flutter pub run build_runner build
dart tool/generate_all_tests.dart
flutter drive --driver=isar_driver.dart --target=isar_driver_target.dart -d web-server --browser-name firefox
working-directory: packages/isar_test
================================================
FILE: .github/workflows/testlab.yaml
================================================
on: workflow_call
jobs:
firebase_testlab_android:
name: Firebase Testlab Android
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: subosito/flutter-action@v2
with:
flutter-version: ${{ vars.FLUTTER_VERSION }}
- name: Prepare Build
uses: ./.github/actions/prepare-build
- name: Build Isar Core arm64
run: |
bash tool/build_android.sh arm64
mkdir -p packages/isar_flutter_libs/android/src/main/jniLibs/arm64-v8a
mv libisar_android_arm64.so packages/isar_flutter_libs/android/src/main/jniLibs/arm64-v8a/libisar.so
- name: Build Isar Core armv7
run: |
bash tool/build_android.sh armv7
mkdir -p packages/isar_flutter_libs/android/src/main/jniLibs/armeabi-v7a
mv libisar_android_armv7.so packages/isar_flutter_libs/android/src/main/jniLibs/armeabi-v7a/libisar.so
- name: Prepare Tests
run: sh tool/prepare_tests.sh
- name: Build dummy APK
run: flutter build apk integration_test/integration_test.dart
working-directory: packages/isar_test
- name: Build APKs
run: |
./gradlew app:assembleAndroidTest
./gradlew app:assembleDebug -Ptarget=integration_test/integration_test.dart
working-directory: packages/isar_test/android
- name: Login to Google Cloud
uses: "google-github-actions/auth@v1"
with:
credentials_json: "${{ secrets.FIREBASE_JSON }}"
- name: Run tests
run: |
gcloud firebase test android run \
--project isar-community \
--type instrumentation \
--timeout 5m \
--device model=starqlteue,version=26 \
--device model=cheetah,version=33 \
--device model=shiba,version=34 \
--app build/app/outputs/apk/debug/app-debug.apk \
--test build/app/outputs/apk/androidTest/debug/app-debug-androidTest.apk
working-directory: packages/isar_test
================================================
FILE: .gitignore
================================================
# Miscellaneous
*.class
*.lock
*.log
.DS_Store
.vscode/
.idea
# Dart related
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
**/generated_plugin_registrant.dart
.packages
.pub-cache/
.pub/
build/
# Rust related
target/
*.a
*.so
*.dylib
*.dll
*.zip
*.xcframework/
isar-dart.h
# Android related
**/android/**/gradle-wrapper.jar
.gradle/
**/android/captures/
**/android/gradlew
**/android/gradlew.bat
**/android/local.properties
**/android/**/GeneratedPluginRegistrant.java
# iOS/XCode related
**/ios/**/*.mode1v3
**/ios/**/*.mode2v3
**/ios/**/*.moved-aside
**/ios/**/*.pbxuser
**/ios/**/*.perspectivev3
**/ios/**/*sync/
**/ios/**/.sconsign.dblite
**/ios/**/.tags*
**/ios/**/.vagrant/
**/ios/**/DerivedData/
**/ios/**/Icon?
**/ios/**/Pods/
**/ios/**/.symlinks/
**/ios/**/profile
**/ios/**/xcuserdata
**/ios/.generated/
**/ios/Flutter/.last_build_id
**/ios/Flutter/App.framework
**/ios/Flutter/Flutter.framework
**/ios/Flutter/Flutter.podspec
**/ios/Flutter/Generated.xcconfig
**/ios/Flutter/ephemeral
**/ios/Flutter/app.flx
**/ios/Flutter/app.zip
**/ios/Flutter/flutter_assets/
**/ios/Flutter/flutter_export_environment.sh
**/ios/ServiceDefinitions.json
**/ios/Runner/GeneratedPluginRegistrant.*
# macOS
**/macos/Flutter/GeneratedPluginRegistrant.swift
**/ephemeral
**/.plugin_symlinks/
# Coverage
coverage/
================================================
FILE: Cargo.toml
================================================
[workspace]
resolver = "2"
members = [
"packages/isar_core",
"packages/isar_core_ffi",
"packages/mdbx_sys"
]
[profile.release]
lto = true
codegen-units = 1
panic = "abort"
strip = "symbols"
================================================
FILE: LICENSE
================================================
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.
================================================
FILE: TODO.md
================================================
Roadmap and TODOs
# Documentation
## API Docs
- [ ] Document all public APIs
## Schema
- [x] Update schema migration instructions
- [ ] Document all annotation options
## CRUD
- [ ] Document sync operations
- [x] `getAll()`, `putAll`, `deleteAll()`
- [ ] `getBy...()`, `deleteBy...()`
## Queries
- [x] Filter groups
- [x] Boolean operators `and()`, `or()`, `not()`
- [x] Offset, limit
- [x] Distinct where clauses
- [x] Different filter operations (`equalTo`, `beginsWith()` etc.)
- [ ] Better explanation for distinct and sorted where clauses
- [ ] Watching queries
## Indexes
- [ ] Intro
- [x] What are they
- [ ] Why use them
- [x] How to in isar?
## Examples
- [ ] Create minimal example
- [ ] Create complex example with indexes, filter groups etc.
- [ ] More Sample Apps
## Tutorials
- [ ] How to write fast queries
- [ ] Build a simple offline first app
- [ ] Advanced queries
----
# Isar Dart
## Features
- [x] Distinct by
- [x] Offset, Limit
- [x] Sorted by
## Fixes
- [x] Provide an option to change collection accessor names
## Unit tests
- [x] Download binaries automatically for tests
### Queries
- [x] Restructure query tests to make them less verbose
- [x] Define models that can be reused across tests
- [x] Where clauses with string indexes (value, hash, words, case-sensitive)
- [x] Distinct where clauses
- [x] String filter operations
----
# Isar Core
## Features (low priority)
- [ ] Draft Synchronization
- [x] Relationships
## Unit tests
- [ ] Make mdbx unit tests bulletproof
- [x] Migration tests
- [x] Binary format
- [x] CRUD
- [x] Links
- [ ] QueryBuilder
- [ ] WhereClause
- [ ] WhereExecutor
- [x] CollectionMigrator
- [ ] Watchers
----
# Isar Web
- [ ] MVP
================================================
FILE: docs/.gitignore
================================================
.DS_Store
node_modules
.temp
.cache
dist
================================================
FILE: docs/README.md
================================================
# Isar Docs
Run the docs locally:
```
npm install
npm run dev
```
## Create a new language
1. Create a new folder in `docs` with the language code (e.g. `de` for German).
2. Add the locale config to `.vueepress/locales.ts`.
3. Start translating the existing pages.
================================================
FILE: docs/docs/.vuepress/config.ts
================================================
import { shikiPlugin } from '@vuepress/plugin-shiki'
import { DefaultThemeLocaleData, defineUserConfig, LocaleConfig, SiteLocaleConfig, } from 'vuepress'
import { defaultTheme } from 'vuepress'
import { viteBundler } from 'vuepress'
import { getLocalePath, locales } from './locales'
import * as path from 'path'
import * as fs from 'fs'
const vueLocales: SiteLocaleConfig = {}
for (const locale of locales) {
vueLocales[getLocalePath(locale.code)] = {
lang: locale.language,
title: locale.dbName,
description: locale.dbDescription,
}
}
const themeLocales: LocaleConfig = {}
for (const locale of locales) {
themeLocales[getLocalePath(locale.code)] = {
selectLanguageName: locale.language,
selectLanguageText: locale.selectLanguage,
editLinkText: locale.editPage,
lastUpdatedText: locale.lastUpdated,
contributorsText: locale.contributors,
tip: locale.tip,
warning: locale.warning,
danger: locale.danger,
notFound: locale.notFound,
backToHome: locale.backToHome,
sidebar: getSidebar({
locale: locale.code,
tutorials: locale.tutorials,
concepts: locale.concepts,
recipes: locale.recipes,
sampleApps: locale.sampleApps,
chnagelog: locale.changelog,
contributors: locale.contributors,
}),
}
}
export default defineUserConfig({
locales: vueLocales,
bundler: viteBundler({}),
base: '/v3/',
theme: defaultTheme({
logo: "/isar.svg",
repo: "isar-community/isar",
docsRepo: "isar-community/isar",
docsDir: "docs/docs",
contributors: true,
locales: themeLocales,
navbar: [
{
text: "pub.dev",
link: "https://pub.dev/packages/isar",
},
{
text: "API",
link: "https://pub.dev/documentation/isar/latest/isar/isar-library.html",
},
{
text: "Telegram",
link: "https://t.me/isardb",
},
{
text: "v3.x",
children: [
{
text: "v4.x",
link: "https://isar-community.dev",
},
{
text: "v3.x",
link: "https://isar-community.dev/v3",
},
],
},
],
sidebarDepth: 1,
}),
markdown: {
code: {
lineNumbers: false,
},
},
plugins: [
[
shikiPlugin({
theme: "one-dark-pro",
}),
{
name: 'redirect-locale',
clientConfigFile: path.resolve(__dirname, 'redirect.ts'),
},
],
],
head: [
[
"link",
{
rel: "icon",
type: "image/png",
sizes: "256x256",
href: `/icon-256x256.png`,
},
],
[
"link",
{
rel: "icon",
type: "image/png",
sizes: "512x512",
href: `/icon-512x512.png`,
},
],
[
"link",
{
rel: "stylesheet",
href: "https://fonts.googleapis.com/css2?family=Montserrat:wght@800&display=swap",
},
],
["meta", { name: "application-name", content: "Isar Database" }],
["meta", { name: "apple-mobile-web-app-title", content: "Isar Database" }],
[
"meta",
{ name: "apple-mobile-web-app-status-bar-style", content: "black" },
],
[
"script",
{
async: "",
src: "https://www.googletagmanager.com/gtag/js?id=G-36LNDL9RHB",
},
],
[
"script",
{},
`window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-36LNDL9RHB');`,
],
[
"script",
{},
`(function(c,l,a,r,i,t,y){
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
})(window, document, "clarity", "script", "lkyzg3xacc");`,
],
],
})
function getSidebar({ locale, tutorials, concepts, recipes, sampleApps, chnagelog, contributors }) {
return [
{
text: tutorials,
children: getSidebarChildren(locale, ["tutorials/quickstart.md"])
},
{
text: concepts,
children: getSidebarChildren(
locale,
[
"schema.md",
"crud.md",
"queries.md",
"transactions.md",
"indexes.md",
"links.md",
"watchers.md",
"limitations.md",
"faq.md",
],
),
},
{
text: recipes,
children: getSidebarChildren(
locale,
[
"recipes/full_text_search.md",
"recipes/multi_isolate.md",
"recipes/string_ids.md",
"recipes/data_migration.md",
]
),
},
{
text: sampleApps,
link: "https://github.com/isar-community/isar/tree/main/examples",
},
{
text: chnagelog,
link: "https://github.com/isar-community/isar/blob/main/packages/isar/CHANGELOG.md",
},
{
text: contributors,
link: "https://github.com/isar-community/isar#contributors-",
},
]
}
function getSidebarChildren(locale: string, children: string[]) {
const localePath = getLocalePath(locale)
return children.map((child) => {
if (locale === "en") {
return '/' + child
}
const file = path.resolve(__dirname, '../', localePath.substring(1), child)
if (fs.existsSync(file)) {
return localePath + child
} else {
return '/' + child
}
});
}
================================================
FILE: docs/docs/.vuepress/locales.ts
================================================
export interface LocalConfig {
code: string;
language: string;
selectLanguage: string;
editPage: string;
lastUpdated: string;
tip: string;
warning: string;
danger: string;
notFound: string[];
backToHome: string;
translationOutdated: string;
dbName: string;
dbDescription: string;
tutorials: string;
concepts: string;
recipes: string;
sampleApps: string;
changelog: string;
contributors: string;
}
export function getLocalePath(code: string): string {
if (code === "en") {
return "/";
} else {
return "/" + code + "/";
}
}
export const locales: LocalConfig[] = [
{
code: "en",
language: "English",
selectLanguage: "Select Language",
editPage: "Edit Page",
lastUpdated: "Last Updated",
tip: "Tip",
warning: "Warning",
danger: "Danger",
notFound: [
"Nothing to see here.",
"How did we end up here?",
"This is a four-oh-four...",
"Looks like we have a broken link.",
],
backToHome: "Back to Home",
translationOutdated: "Translation is outdated. Please help us update it!",
dbName: "Isar Database",
dbDescription: "Super Fast Cross-Platform Database for Flutter",
tutorials: "TUTORIALS",
concepts: "CONCEPTS",
recipes: "RECIPES",
sampleApps: "Sample Apps",
changelog: "Changelog",
contributors: "Contributors",
},
{
code: "de",
language: "Deutsch",
selectLanguage: "Sprache wählen",
editPage: "Seite bearbeiten",
lastUpdated: "Zuletzt aktualisiert",
tip: "Tipp",
warning: "Warnung",
danger: "Achtung",
notFound: [
"Hier gibt es nichts zu sehen.",
"Wie sind wir hier gelandet?",
"Das ist ein vier-null-vier...",
"Sieht aus als hätten wir einen kaputten Link.",
],
backToHome: "Zurück zur Startseite",
translationOutdated:
"Übersetzung ist veraltet. Bitte hilf uns, sie zu aktualisieren!",
dbName: "Isar Datenbank",
dbDescription: "Super Schnelle Cross-Platform Flutter Datenbank",
tutorials: "TUTORIALS",
concepts: "KONZEPTE",
recipes: "REZEPTE",
sampleApps: "Beispiel Apps",
changelog: "Änderungsprotokoll",
contributors: "Mitwirkende",
},
{
code: "ja",
language: "日本語",
selectLanguage: "言語の選択",
editPage: "編集ページ",
lastUpdated: "最終更新日",
tip: "ヒント",
warning: "警告",
danger: "危険",
notFound: [
"何も見つかりませんでした.",
"どうしてこんなところに辿り着いたのだろう...",
"ここは404ページのようです...",
"リンク切れのようです。",
],
backToHome: "ホームに戻る",
translationOutdated:
"翻訳は古くなっています。翻訳の更新にご協力頂けませんか?",
dbName: "Isar Database",
dbDescription: "Flutterのための超高速クロスプラットフォームDatabase",
tutorials: "チュートリアル",
concepts: "コンセプト",
recipes: "レシピ集",
sampleApps: "サンプルアプリ",
changelog: "変更履歴",
contributors: "貢献者の方々",
},
{
code: "ko",
language: "한국어",
selectLanguage: "언어 선택",
editPage: "페이지 편집",
lastUpdated: "마지막 업데이트",
tip: "팁",
warning: "경고",
danger: "위험",
notFound: [
"여기는 볼 것이 없다.",
"우리가 어떻게 여기까지 오게 되었나요?",
"여기는 404...",
"연결이 끊어진 것 같습니다.",
],
backToHome: "홈으로 돌아가기",
translationOutdated: "번역이 낡았습니다. 업데이트 도와주세요!",
dbName: "Isar 데이터베이스",
dbDescription: "플러터를 위한 초고속 크로스 플랫폼 데이터베이스",
tutorials: "튜토리얼",
concepts: "개념",
recipes: "레시피",
sampleApps: "샘플 앱",
changelog: "체인지로그",
contributors: "기여자들",
},
{
code: "es",
language: "Español",
selectLanguage: "Seleccionar Idioma",
editPage: "Editar Página",
lastUpdated: "Última actualización",
tip: "Consejo",
warning: "Advertencia",
danger: "Peligro",
notFound: [
"No hay nada para ver aquí.",
"Cómo llegamos aquí?",
"Esto es vergonzoso, no tenemos nada...",
"Parece que hay un enlace roto.",
],
backToHome: "Volver al inicio",
translationOutdated:
"Esta traducción está desactualizada. Por favor ayúdanos a mantenerla al día!",
dbName: "Isar Database",
dbDescription: "Base de Datos Super rápida, Multiplataforma para Flutter",
tutorials: "TUTORIALES",
concepts: "CONCEPTOS",
recipes: "RECETAS",
sampleApps: "Aplicaciones de Ejemplo",
changelog: "Registro de cambios",
contributors: "Colaboradores",
},
{
code: "it",
language: "Italiano",
selectLanguage: "Seleziona Lingua",
editPage: "Modifica Pagina",
lastUpdated: "Ultimo aggiornamento",
tip: "Suggerimento",
warning: "Attenzione",
danger: "Pericolo",
notFound: [
"Nulla da vedere qui.",
"Come ci siamo finiti qui?",
"Questa è una quattro-zero-quattro...",
"Sembra che abbiamo un collegamento rotto.",
],
backToHome: "Indietro alla Home",
translationOutdated:
"La traduzione è obsoleta. Per favore aiutaci ad aggiornarla!",
dbName: "Isar Database",
dbDescription: "Database multipiattaforma super veloce per Flutter",
tutorials: "TUTORIALS",
concepts: "CONCETTI",
recipes: "RICETTE",
sampleApps: "App d'esempio",
changelog: "Registro delle modifiche",
contributors: "Contributori",
},
{
code: "pt",
language: "Português",
selectLanguage: "Selecione o idioma",
editPage: "Editar página",
lastUpdated: "Ultima atualização",
tip: "Dica",
warning: "Aviso",
danger: "Perigo",
notFound: [
"Nada para ver aqui.",
"Como chegamos aqui?",
"Isso é embaraçoso, não temos nada...",
"Parece que temos um link inválido.",
],
backToHome: "Voltar para Início",
translationOutdated:
"A tradução está desatualizada. Por favor, ajude-nos a atualizá-lo!",
dbName: "Isar Database",
dbDescription: "Banco de dados multiplataforma super rápido para Flutter",
tutorials: "TUTORIAIS",
concepts: "CONCEITOS",
recipes: "RECEITAS",
sampleApps: "Aplicativos de amostra",
changelog: "Registro de alterações",
contributors: "Contribuidores",
},
{
code: "ur",
language: "اردو",
selectLanguage: "زبان منتخب کریں",
editPage: "صفحہ میں ترمیم کریں",
lastUpdated: "آخری تازہ کاری",
tip: "ٹپ",
warning: "انتباہ",
danger: "خطرہ",
notFound: [
"یہاں دیکھنے کے لیے کچھ نہیں ہے۔",
"ہم یہاں کیسے پہنچے؟",
" یہ چار اوہ چار ہے۔۔۔",
"لگتا ہے ہمارے پاس کوئی ٹوٹا ہوا لنک ہے۔",
],
backToHome: "گھر پر واپس",
translationOutdated:
"ترجمہ پرانا ہے۔ براہ کرم اسے تروتازہ کرنے میں ہماری مدد کریں!",
dbName: "Isar Database",
dbDescription: " ڈیٹا بیس کے لیے سپر فاسٹ کراس پلیٹ فارم Flutter",
tutorials: "اسباق",
concepts: "تصورات",
recipes: "تراکیب",
sampleApps: "نمونہ ایپس",
changelog: "چینج لاگ",
contributors: "شراکت دار",
},
{
code: "fr",
language: "Français",
selectLanguage: "Sélectionner la langue",
editPage: "Modifier la page",
lastUpdated: "Dernière modification",
tip: "Conseil",
warning: "Avertissement",
danger: "Danger",
notFound: [
'Il n"y a rien a voir ici.',
"Comment en sommes-nous arrivés là ?",
"Ceci est un quatre-cent-quatre...",
"Il semble que nous avons un lien brisé.",
],
backToHome: "Retour à l'acceuil",
translationOutdated: "Translation is outdated. Please help us update it!",
dbName: "Base de données Isar",
dbDescription: "Base de données multiplateforme super rapide pour Flutter",
tutorials: "TUTORIELS",
concepts: "CONCEPTS",
recipes: "RECETTES",
sampleApps: "Exemples d'applications",
changelog: "Changements",
contributors: "Contributeurs",
},
{
code: "zh",
language: "简体中文",
selectLanguage: "选择语言",
editPage: "编辑页面",
lastUpdated: "更新日期",
tip: "提示",
warning: "警告",
danger: "危险",
notFound: [
"这里什么都没有。",
"怎么会来到这个页面?",
"404...",
"看起来链接失效了。",
],
backToHome: "回到主页",
translationOutdated: "翻译已过期,请帮助我们更新。",
dbName: "Isar 数据库",
dbDescription: "专门为 Flutter 打造的超高速跨平台数据库",
tutorials: "教程",
concepts: "概念",
recipes: "专题",
sampleApps: "示例 App",
changelog: "更新记录",
contributors: "贡献者",
},
];
================================================
FILE: docs/docs/.vuepress/redirect.ts
================================================
import { defineClientConfig } from '@vuepress/client'
import { locales } from './locales'
export default defineClientConfig({
enhance({ app, router, siteData }) {
router.beforeEach((to, from) => {
// open vuepress for the first time
let isFirstStart = to.fullPath == from.fullPath
// Whether the home page is about to be displayed
let isHome = to.fullPath == "/"
if (typeof navigator != 'undefined' && isFirstStart && isHome) {
const lang = navigator.language.split("-")[0].toLowerCase()
if (lang != "en" && locales.some((l) => l.code === lang)) {
const redirectUrl = "/" + lang + "/"
// Avoid infinite redirection
if (to.fullPath != redirectUrl) {
return redirectUrl
}
}
}
})
}
})
================================================
FILE: docs/docs/.vuepress/styles/index.scss
================================================
:root {
--c-brand: #4799fc;
--c-brand-light: #67abfd;
--c-text: rgb(30, 30, 30); // normal text
--c-text-light: rgb(30, 30, 30);
--c-text-lighter: rgb(30, 30, 30); // code block text
--c-text-lightest: rgba(30, 30, 30, 0.7);
.custom-container.tip {
color: rgb(57, 146, 255) !important;
border-color: rgb(57, 146, 255) !important;
background-color: rgba(57, 146, 255, 0.1) !important;
}
.custom-container.warning {
color: rgb(39, 31, 6) !important;
border-color: rgb(39, 31, 6) !important;
background-color: rgba(131, 122, 11, 0.15) !important;
}
.custom-container.danger {
color: rgb(170, 37, 58) !important;
border-color: rgb(170, 37, 58) !important;
background-color: rgba(170, 37, 58, 0.1) !important;
}
}
html.dark {
--c-brand: #67abfd;
--c-brand-light: #4799fc;
--c-bg: rgb(18, 18, 18);
--c-bg-light: rgb(30, 30, 30); // code block background
--code-bg-color: #1e1e1e; // code background
--c-text: rgb(183, 188, 190); // normal text
--c-text-light: rgba(183, 188, 190);
--c-text-lighter: rgb(183, 188, 190); // code block text
--c-text-lightest: rgba(183, 188, 190, 0.7);
--c-border: rgb(45, 45, 45);
--c-border-dark: rgb(60, 60, 60);
.custom-container.tip {
color: rgb(57, 146, 255) !important;
border-color: rgb(57, 146, 255) !important;
background-color: rgba(57, 146, 255, 0.1) !important;
}
.custom-container.warning {
color: rgb(248, 239, 159) !important;
border-color: rgb(248, 239, 159) !important;
background-color: rgba(131, 122, 11, 0.15) !important;
}
.custom-container.danger {
color: rgb(240, 158, 183) !important;
border-color: rgb(240, 158, 183) !important;
background-color: rgba(240, 158, 183, 0.1) !important;
}
}
h1 {
font-family: "Montserrat", sans-serif;
font-size: 38px;
@media (min-width: 750px) {
font-size: 55px;
}
}
h2 {
font-family: "Montserrat", sans-serif;
font-size: 27px;
@media (min-width: 750px) {
font-size: 38px;
}
}
h3 {
font-family: "Montserrat", sans-serif;
font-size: 20px;
@media (min-width: 750px) {
font-size: 27px;
}
}
h4 {
font-family: "Montserrat", sans-serif;
font-size: 16px;
}
mark {
padding: 2px;
}
.action-button.primary {
font-weight: 800;
}
summary {
cursor: pointer;
}
details[open] summary {
margin-bottom: 0.5rem;
}
.custom-container {
border-radius: 10px;
border-left-width: 0.25rem !important;
border-left-style: solid;
border-right-style: solid;
border-right-width: 0.25rem;
}
.custom-container-title {
display: none;
}
.video-block {
position: relative;
padding-bottom: 56.25%; /* 16:9 */
height: 0;
overflow: hidden;
width: 100%; height: auto;
}
.video-block iframe {
position: absolute;
top: 0; left: 0;
width: 100%; height: 100%;
}
================================================
FILE: docs/docs/README.md
================================================
---
home: true
title: Home
heroImage: /isar.svg
actions:
- text: Let's Get Started!
link: /tutorials/quickstart.html
type: primary
features:
- title: 💙 Made for Flutter
details: Minimal setup, Easy to use, no config, no boilerplate. Just add a few lines of code to get started.
- title: 🚀 Highly scalable
details: Store hundreds of thousands of records in a single NoSQL database and query them efficiently and asynchronously.
- title: 🍭 Feature-rich
details: Isar has a rich set of features to help you manage your data. Composite & multi-entry indexes, query modifiers, JSON support, and more.
- title: 🔎 Full-text search
details: Isar has built-in full-text search. Create a multi-entry index and search for records easily.
- title: 🧪 ACID semantics
details: Isar is ACID compliant and handles transactions automatically. It rolls back changes if an error occurs.
- title: 💃 Static typing
details: Isar's queries are statically typed and compile-time checked. No need to worry about runtime errors.
- title: 📱 Multiplatform
details: iOS, Android, Desktop, and FULL WEB SUPPORT!
- title: ⏱ Asynchronous
details: Parallel query operations & multi-isolate support out-of-the-box
- title: 🦄 Open Source
details: Everything is open source and free forever!
footer: Apache Licensed | Copyright © 2022 Simon Leier
---
================================================
FILE: docs/docs/crud.md
================================================
---
title: Create, Read, Update, Delete
---
# Create, Read, Update, Delete
When you have your collections defined, learn how to manipulate them!
## Opening Isar
Before you can do anything, we need an Isar instance. Each instance requires a directory with write permission where the database file can be stored. If you don't specify a directory, Isar will find a suitable default directory for the current platform.
Provide all the schemas you want to use with the Isar instance. If you open multiple instances, you still have to provide the same schemas to each instance.
```dart
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[RecipeSchema],
directory: dir.path,
);
```
You can use the default config or provide some of the following parameters:
| Config | Description |
| ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | Open multiple instances with distinct names. By default, `"default"` is used. |
| `directory` | The storage location for this instance. Not required for web. |
| `maxSizeMib` | The maximum size of the database file in MiB. Isar uses virtual memory which is not an endless resource so be mindful with the value here. If you open multiple instances they share the available virtual memory so each instance should have a smaller `maxSizeMib` . The default is 2048. |
| `relaxedDurability` | Relaxes the durability guarantee to increase write performance. In case of a system crash (not app crash), it is possible to lose the last committed transaction. Corruption is not possible |
| `compactOnLaunch` | Conditions to check whether the database should be compacted when the instance is opened. |
| `inspector` | Enabled the Inspector for debug builds. For profile and release builds this option is ignored. |
If an instance is already open, calling `Isar.open()` will yield the existing instance regardless of the specified parameters. That's useful for using Isar in an isolate.
:::tip
Consider using the [path_provider](https://pub.dev/packages/path_provider) package to get a valid path on all platforms.
:::
The storage location of the database file is `directory/name.isar`
## Reading from the database
Use `IsarCollection` instances to find, query, and create new objects of a given type in Isar.
For the examples below, we assume that we have a collection `Recipe` defined as follows:
```dart
@collection
class Recipe {
Id? id;
String? name;
DateTime? lastCooked;
bool? isFavorite;
}
```
### Get a collection
All your collections live in the Isar instance. You can get the recipes collection with:
```dart
final recipes = isar.recipes;
```
That was easy! If you don't want to use collection accessors, you can also use the `collection()` method:
```dart
final recipes = isar.collection();
```
### Get an object (by id)
We don't have data in the collection yet but let's pretend we do so we can get an imaginary object by the id `123`
```dart
final recipe = await isar.recipes.get(123);
```
`get()` returns a `Future` with either the object or `null` if it does not exist. All Isar operations are asynchronous by default, and most of them have a synchronous counterpart:
```dart
final recipe = isar.recipes.getSync(123);
```
:::warning
You should default to the asynchronous version of methods in your UI isolate. Since Isar is very fast, it is often acceptable to use the synchronous version.
:::
If you want to get multiple objects at once, use `getAll()` or `getAllSync()`:
```dart
final recipe = await isar.recipes.getAll([1, 2]);
```
### Query objects
Instead of getting objects by id you can also query a list of objects matching certain conditions using `.where()` and `.filter()`:
```dart
final allRecipes = await isar.recipes.where().findAll();
final favorites = await isar.recipes.filter()
.isFavoriteEqualTo(true)
.findAll();
```
➡️ Learn more: [Queries](queries)
## Modifying the database
It's finally time to modify our collection! To create, update, or delete objects, use the respective operations wrapped in a write transaction:
```dart
await isar.writeTxn(() async {
final recipe = await isar.recipes.get(123)
recipe.isFavorite = false;
await isar.recipes.put(recipe); // perform update operations
await isar.recipes.delete(123); // or delete operations
});
```
➡️ Learn more: [Transactions](transactions)
### Insert object
To persist an object in Isar, insert it into a collection. Isar's `put()` method will either insert or update the object depending on whether it already exists in the collection.
If the id field is `null` or `Isar.autoIncrement`, Isar will use an auto-increment id.
```dart
final pancakes = Recipe()
..name = 'Pancakes'
..lastCooked = DateTime.now()
..isFavorite = true;
await isar.writeTxn(() async {
await isar.recipes.put(pancakes);
})
```
Isar will automatically assign the id to the object if the `id` field is non-final.
Inserting multiple objects at once is just as easy:
```dart
await isar.writeTxn(() async {
await isar.recipes.putAll([pancakes, pizza]);
})
```
### Update object
Both creating and updating works with `collection.put(object)`. If the id is `null` (or does not exist), the object is inserted; otherwise, it is updated.
So if we want to unfavorite our pancakes, we can do the following:
```dart
await isar.writeTxn(() async {
pancakes.isFavorite = false;
await isar.recipes.put(pancakes);
});
```
### Delete object
Want to get rid of an object in Isar? Use `collection.delete(id)`. The delete method returns whether an object with the specified id was found and deleted. If you want to delete the object with id `123`, for example, you can do:
```dart
await isar.writeTxn(() async {
final success = await isar.recipes.delete(123);
print('Recipe deleted: $success');
});
```
Similarly to get and put, there is also a bulk delete operation that returns the number of deleted objects:
```dart
await isar.writeTxn(() async {
final count = await isar.recipes.deleteAll([1, 2, 3]);
print('We deleted $count recipes');
});
```
If you don't know the ids of the objects you want to delete, you can use a query:
```dart
await isar.writeTxn(() async {
final count = await isar.recipes.filter()
.isFavoriteEqualTo(false)
.deleteAll();
print('We deleted $count recipes');
});
```
================================================
FILE: docs/docs/de/README.md
================================================
---
home: true
title: Home
heroImage: /isar.svg
actions:
- text: Auf Los geht's los!
link: /de/tutorials/quickstart.html
type: primary
features:
- title: 💙 Für Flutter gemacht
details: Minimales Setup, einfach zu bedienen, keine Konfiguration, kein Boilerplate. Mit ein paar Zeilen Code geht's los.
- title: 🚀 Skalierbar
details: Speichere Hunderttausende von Datensätzen und rufe sie effizient und asynchron ab.
- title: 🍭 Viele Features
details: Isar hat unzählige Features. Komposit- und Mehrfach-Indizes, Query-Modifikatoren, JSON und mehr.
- title: 🔎 Volltextsuche
details: Volltextsuche ist integriert. Erstelle einen Mehrfach-Index und suche nach Datensätzen.
- title: 🧪 ACID Semantik
details: Isar ist ACID-konform und verwaltet Transaktionen automatisch. Änderungen werden rückgängig gemacht, falls ein Fehler auftritt.
- title: 💃 Statische Typisierung
details: Abfragen sind statisch typisiert und werden zur Kompilierzeit überprüft. Laufzeitfehler sind ein Problem von gestern.
- title: 📱 Multiplatform
details: iOS, Android, Desktop und VOLLE WEB UNTERSTÜTZUNG!
- title: ⏱ Asynchron
details: Parallelle Abfragen und Multi-Isolate-Unterstützung.
- title: 🦄 Open Source
details: Komplett Open Source und für immer kostenlos!
footer: Apache Licensed | Copyright © 2022 Simon Leier
---
================================================
FILE: docs/docs/de/crud.md
================================================
---
title: Erstellen, Lesen, Aktualisieren und Löschen
---
# Erstellen, Lesen, Aktualisieren und Löschen
Lerne wie du Collections in Isar nutzt nachdem du sie definiert hast.
## Öffnen von Isar
Als Erstes benötigen wir eine Isar Instanz. Jede Instanz erfordert einen Ordner mit Schreibrechten, in dem die Datenbankdatei gespeichert werden kann. Wenn du keinen Ordner angibst, wird Isar einen geeigneten Standardordner für die aktuelle Plattform finden.
Gib alle Schemas an, die du mit der Isar-Instanz verwenden möchtest. Wenn du mehrere Instanzen öffnest, musst du trotzdem jeder Instanz die gleichen Schemas mitgeben.
```dart
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[RecipeSchema],
directory: dir.path,
);
```
Du kannst die Standardkonfiguration verwenden oder einige der folgenden Parameter setzen:
| Konfiguration | Beschreibung |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | Öffne mehrere Instanzen mit unterschiedlichen Namen. Standardmäßig wird `"default"` verwendet. |
| `directory` | Der Speicherort für diese Instanz. Standardmäßig wird `NSDocumentDirectory` für iOS und `getDataDirectory` für Android verwendet. Nicht erforderlich für Web. |
| `relaxedDurability` | Entspannt die durability-Garantie, um die Schreibleistung zu erhöhen. Im Falle eines Systemabsturzes (nicht App-Absturz) ist es möglich, die letzte Transaktion zu verlieren. Datenbankkorruption ist nicht möglich |
Wenn eine Instanz bereits geöffnet ist, wird `Isar.open()` die vorhandene Instanz unabhängig von den angegebenen Parametern zurückgeben. Das ist nützlich, um Isar in einem Isolate zu verwenden.
:::tip
Verwende das [path_provider](https://pub.dev/packages/path_provider)-Paket, um einen gültigen Pfad auf allen Plattformen zu erhalten.
:::
Der Speicherort der Datenbankdatei ist `directory/name.isar`
## Aus der Datenbank lesen
Verwende `IsarCollection`-Instanzen um Objekte eines bestimmten Typs in Isar zu finden, abzufragen und neu zu erstellen.
Den folgenden Beispielen liegt die Collection `Recipe` zugrunde, die wie folgt definiert ist:
```dart
@collection
class Recipe {
Id? id;
String? name;
DateTime? lastCooked;
bool? isFavorite;
}
```
### Eine Collection erhalten
Alle deine Collections befinden sich in der Isar Instanz. Erhalte die Recipes-Collection über den Accessor:
```dart
final recipes = isar.recipes;
```
Das war einfach! Wenn du keine Collection-Accessors verwenden möchtest, ist alternativ die `collection()`-Methode verfügbar:
```dart
final recipes = isar.collection();
```
### Objekt abrufen (per ID)
Wir haben noch keine Daten in der Collection, aber wir nehmen an, dass bereits ein Objekt mit der ID `123` existiert.
```dart
final recipe = await recipes.get(123);
```
Die `get()`-Methode gibt ein `Future` zurück, das entweder das Objekt enthält, oder `null`, wenn die ID nicht existiert. Alle Isar-Operationen sind standardmäßig asynchron, auch wenn die meisten ein synchrones Gegenstück haben:
```dart
final recipe = recipes.getSync(123);
```
:::warning
Normalerweise solltest du die asynchrone Version der Methoden in deinem UI-Isolate bevorzugen. Da Isar sehr schnell ist, sind die synchronen Methoden oft auch in Ordnung.
:::
Wenn du mehrere Objekte auf einmal abrufen möchtest, kannst du `getAll()` oder `getAllSync()` verwenden:
```dart
final recipe = await recipes.getAll([1, 2]);
```
### Abfragen von Objekten
Anstatt Objekte über die ID zu erhalten, kannst du mittels `.where()` und `.filter()` auch eine Liste von Objekten abfragen, die bestimmten Bedingungen entsprechen:
```dart
final allRecipes = await recipes.where().findAll();
final favourites = await recipes.filter()
.isFavoriteEqualTo(true)
.findAll();
```
➡️ Lerne mehr: [Abfragen](queries)
## Ändern der Datenbank
Jetzt ist es endlich an der Zeit, unsere Collection zu verändern! Um Objekte zu erstellen, zu aktualisieren oder zu löschen, rufe die entsprechenden Operationen innerhalb einer Schreibtransaktion auf:
```dart
await isar.writeTxn(() async {
final recipe = await recipes.get(123)
recipe.isFavorite = false;
await recipes.put(recipe); // Aktualisierungsoperationen
await recipes.delete(123); // oder Löschoperationen durchführen
});
```
➡️ Lerne mehr: [Transaktionen](transactions)
### Objekt erstellen
Erstelle ein Objekt in einer Collection um es in Isar zu speichern. Die `put()`-Methode von Isar erstellt das Objekt entweder oder aktualisiert es, je nachdem, ob es bereits in der Collection existiert.
Wenn das ID-Feld `null` oder `Isar.autoIncrement` ist, verwendet Isar eine automatisch generierte ID.
```dart
final pancakes = Recipe()
..name = 'Pancakes'
..lastCooked = DateTime.now()
..isFavorite = true;
await isar.writeTxn(() async {
await recipes.put(pancakes);
})
```
Ist das ID-Feld nicht-final, weist Isar die generierte ID automatisch dem Objekt zu.
Das Erstellen von mehreren Objekten auf einmal ist genauso einfach:
```dart
await isar.writeTxn(() async {
await recipes.putAll([pancakes, pizza]);
})
```
### Objekt aktualisieren
Sowohl das Erstellen als auch das Aktualisieren funktioniert mit `collection.put(object)`. Wenn die ID `null` ist (oder nicht existiert), wird das Objekt erstellt, andernfalls wird es aktualisiert.
Wenn wir also Pfannkuchen nicht mehr mögen, können wir Folgendes tun:
```dart
await isar.writeTxn(() async {
pancakes.isFavorite = false;
await recipes.put(recipe);
});
```
### Objekt löschen
Willst du ein Objekt in Isar loswerden? Verwende `collection.delete(id)`. Die delete-Methode gibt zurück, ob ein Objekt mit der angegebenen ID gefunden und gelöscht wurde. Lass uns z.B. das Objekt mit der ID `123` löschen:
```dart
await isar.writeTxn(() async {
final success = await recipes.delete(123);
print('Recipe deleted: $success');
});
```
Ähnlich wie bei `get()` und `put()` gibt es auch einen Massenlöschvorgang, der die Anzahl der gelöschten Objekte zurückgibt:
```dart
await isar.writeTxn(() async {
final count = await recipes.deleteAll([1, 2, 3]);
print('We deleted $count recipes');
});
```
Wenn du die IDs der zu löschenden Objekte nicht kennst ist es auch möglich eine Abfrage zu verwenden:
```dart
await isar.writeTxn(() async {
final count = await recipes.filter()
.isFavoriteEqualTo(false)
.deleteAll();
print('We deleted $count recipes');
});
```
================================================
FILE: docs/docs/de/faq.md
================================================
---
title: FAQ
---
# Häufig gestellte Fragen
Eine zufällige Zusammenstellung an häufig gestellten Fragen zu Isar und Flutter-Datenbanken.
### Warum brauche ich eine Datenbank?
> Ich speichere meine Daten in einer Backend-Datenbank, warum benötige ich Isar?
Sogar heute kommt es vor, dass du keine Internetverbindung hast, wenn du in einer U-Bahn, einem Flugzeug oder zu Besuch bei deiner Oma bist, die kein WLAN und einen sehr schlechten Mobilfunkempfang hat. Du solltest deine App nicht durch schlechte Verbindung lahmlegen lassen.
### Isar vs Hive
Die Antwort ist leicht: Isar wurde [als Ersatz für Hive begonnen](https://github.com/hivedb/hive/issues/246) und ist nun an einem Punkt, wo ich immer empfehlen würde, Isar statt Hive zu benutzen.
### Where-Klauseln?!
> Warum muss **_ich_** wählen, welcher Index genutzt wird?
Es gibt mehrere Gründe. Viele Datenbanken benutzen Heuristik um den besten Index für eine bestimmte Abfrage zu nutzen. Die Datenbank muss zusätzliche Nutzungsdaten sammeln (-> Overhead) und verwendet möglicherweise immer noch den falschen Index. Es dauert dadurch auch länger eine Abfrage zu starten.
Niemand kennt deine Daten besser, als du, der Entwickler. Also kannst du den besten Index wählen und z.B. entscheiden, ob du einen Index zum Abfragen oder Sortieren verwenden willst.
### Muss ich Indizes / Where-Klauseln benutzen?
Nö! Isar ist vermutlich schnell genug, auch wenn du nur Filter verwendest.
### Ist Isar schnell genug?
Isar ist unter den schnellsten Datenbanken für Mobilgeräte, also sollte es in den meisten Fällen schnellgenug sein. Wenn du auf Leistungsprobleme stößt, besteht die Möglichkeit, dass du was falschmachst.
### Steigert Isar die Größe meiner App?
Ja, ein bisschen. Isar wird die Download-Größe deiner App um 1 - 1,5 MB erhöhen. Isar Web fügt nur wenige KB hinzu.
### Die Docs sind falsch / Da ist ein Tippfehler
Oh nein, sorry. Bitte [öffne ein Issue](https://github.com/isar-community/isar/issues/new/choose), oder noch besser, mach einen PR um den Fehler zu beheben 💪.
================================================
FILE: docs/docs/de/indexes.md
================================================
---
title: Indizes
---
# Indizes
Indizes sind Isars mächtigstes Feature. Viele eingebettete Datenbanken bieten "normale" Indizes (wenn überhaupt), aber Isar hat auch Komposit- und Mehrfach-Indizes. Zu verstehen, wie Indizes funktionieren ist grundlegend um die Abfrageleistung zu optimieren. Isar lässt dich wählen welchen Index du verwenden möchtest und wie du ihn benutzen willst. Wir beginnen mit einer schnellen Einführung was Indizes sind.
## Was sind Indizes?
Wenn eine Collection nicht indiziert ist, wird die Reihenfolge der Zeilen von der Abfrage aus sicherlich nicht als in irgendeiner Weise optimiert erkennbar sein. Daher muss die Abfrage linear alle Objekte durchsuchen. In anderen Worten, die Abfrage muss alle Objekte durchsuchen, um diejenigen zu finden, die zu den Bedingungen passen. Wie du dir bestimmt vorstellen kannst, kann das seine Zeit dauern. Durch jedes einzelne Objekt zu gucken ist nicht sehr effizient.
Zum Beispiel ist diese `Product`-Collection komplett unsortiert.
```dart
@collection
class Product {
Id? id;
late String name;
late int price;
}
```
**Daten:**
| id | name | price |
| --- | --------- | ----- |
| 1 | Book | 15 |
| 2 | Table | 55 |
| 3 | Chair | 25 |
| 4 | Pencil | 3 |
| 5 | Lightbulb | 12 |
| 6 | Carpet | 60 |
| 7 | Pillow | 30 |
| 8 | Computer | 650 |
| 9 | Soap | 2 |
Eine Abfrage, die versucht alle Produkte zu finden, die mehr als 30€ kosten, muss alle neun Zeilen durchsuchen. Das ist kein Problem für nur neun Zeilen, aber könnte ein Problem für 100k Zeilen werden.
```dart
final expensiveProducts = await isar.products.filter()
.priceGreaterThan(30)
.findAll();
```
Um die Leistung dieser Abfrage zu verbessern, indizieren wir die Eigenschaft `price`. Ein Index ist wie eine sortierte Nachschlagetabelle.
```dart
@collection
class Product {
Id? id;
late String name;
@Index()
late int price;
}
```
**Generierter Index:**
| price | id |
| -------------------- | ------------------ |
| 2 | 9 |
| 3 | 4 |
| 12 | 5 |
| 15 | 1 |
| 25 | 3 |
| 30 | 7 |
| **55** | **2** |
| **60** | **6** |
| **650** | **8** |
Jetzt kann die Abfrage deutlich schneller durchgeführt werden. Es kann direkt zu den letzten drei Indexzeilen gesprungen werden und die entsprechenden Objekte anhand ihrer ID gefunden werden.
### Sortierung
Eine andere coole Sache: Indizes können superschnell sortieren. Sortierte Abfragen sind kostenintensiv, weil die Datenbank alle Ergebnisse in den Speicher laden muss, bevor sie sortiert werden. Sogar wenn du einen Offset oder eine Limitierung angibst, werden diese erst nach dem Sortieren angewandt.
Stell dir vor, wir wollten die vier günstigsten Produkte finden. Wir könnten die folgende Abfrage verwenden:
```dart
final cheapest = await isar.products.filter()
.sortByPrice()
.limit(4)
.findAll();
```
In diesem Beispiel müsste die Datenbank alle (!) Objekte laden, sie nach dem Preis sortieren und die vier Produkte mit dem niedrigsten Preis zurückgeben.
Wie du dir vermutlich vorstellen kannst, kann das mit dem vorherigen Index sehr viel effizienter gemacht werden. Die Datenbank nimmt die ersten vier Zeilen des Indexes und gibt die zugehörigen Objekte zurück, da sie schon in der korrekten Reihenfolge sind.
Um einen Index zum Sortieren zu verwenden würden wir die Abfrage so schreiben:
```dart
final cheapestFast = await isar.products.where()
.anyPrice()
.limit(4)
.findAll();
```
Die `.anyX()` Where-Klausel teilt Isar mit, einen Index nur zum Sortieren zu verwenden. Du kannst also eine Where-Klausel wie `.priceGreaterThan()` benutzen und sortierte Ergenisse erhalten.
## Eindeutige Indizes
Ein eindeutiger Index stellt sicher, dass der Index keine doppelten Werte enthält. Er kann aus einem oder mehreren Eigenschaften bestehen. Wenn ein eindeutiger Index eine Eigenschaft hat, sind die Werte dieser Eigenschaft eindeutig. Wenn ein eindeutiger Index mehr als eine Eigenschaft hat, dann ist die Kombination der Werte dieser Eigenschaften eindeutig.
```dart
@collection
class User {
Id? id;
@Index(unique: true)
late String username;
late int age;
}
```
Jeder Versuch Daten in einen eindeutigen Index einzufügen oder zu aktualisieren, die ein Dukplikat verursachen würden, resultieren in einem Fehler:
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1); // -> Ok
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
// Versucht einen Benutzer mit dem gleichen Benutzernamen einzufügen
await isar.users.put(user2); // -> Fehler: Eindeutigkeitsbeschränkung verletzt
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
```
## Indizes ersetzen
Manchmal ist es nicht von Vorteil einen Fehler zu verursachen, wenn eine Eindeutigkeitsbeschränkung verletzt wird. Stattdessen möchtest du vielleicht das vorhandene Objekt mit dem Neuen ersetzen. Das kann erreicht werden, indem die Eigenschaft `replace` des Indexes auf `true` gesetzt wird.
```dart
@collection
class User {
Id? id;
@Index(unique: true, replace: true)
late String username;
}
```
Jetzt, wenn wir versuchen einen Benutzer mit einem vorhandenen Benutzernamen einzufügen, wird Isar den Vorhandenen mit dem neuen Benutzer ersetzen.
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1);
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
print(await isar.user.where().findAll());
// > [{id: 2, username: 'user1' age: 30}]
```
Ersetzbare Indizes generieren auch `putBy()`-Methoden, die es dir ermöglichen Objekte zu aktualisieren statt sie zu ersetzen. Die vorhandene ID wird wiederverwendet und Links bleiben erhalten.
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
// Nutzer existiert nicht, also ist es das gleiche wie put()
await isar.users.putByUsername(user1);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1' age: 30}]
```
Wie du sehen kannst, wird die ID des zuerst eingefügten Benutzers wiederverwendet.
## Indizes ohne Berücksichtigung auf Groß-/Kleinschreibung
Alle Indizes auf `String`- und `List`-Eigenschaften beachten standardmäßig die Groß-/Kleinschreibung. Wenn du einen Index erstellen willst, der die Groß-/Kleinschreibung nicht berücksichtigt, kannst du die `caseSensitive`-Option verwenden:
```dart
@collection
class Person {
Id? id;
@Index(caseSensitive: false)
late String name;
@Index(caseSensitive: false)
late List tags;
}
```
## Index-Typen
Es gibt verschiedene Typen von Indizes. Meistens wirst du einen `IndexType.value`-Index verwenden wollen, aber Hash-Indizes sind effizienter.
### Wert-Index
Wert-Indizes sind der Standardtyp und der Einzige, der für alle Eigenschaften erlaubt ist, die nicht Strings oder Listen enthalten. Eigenschaftswerte werden verwendet, um den Index zu erstellen. Im Fall von Listen, werden die Elemente der Liste verwendet. Es ist der flexibelste, aber auch platzraubendste der drei Index-Typen.
:::tip
Benutze `IndexType.value` für Primitives, Strings, wenn du `startsWith()`-Where-Klauseln brauchst, und Listen, wenn du nach einzelnen Elementen suchst.
:::
### Hash-Index
Strings und Listen können gehasht werden um den für den Index benötigten Speicher drastisch zu verringern. Der Nachteil eines Hash-Indexes ist, dass sie nicht für Präfixsuchen (`startsWith()`-Where-Klauseln) verwendet werden können.
:::tip
Verwende `IndexType.hash` für Strings und Listen, wenn du die `startsWith`- und `elementEqualTo`-Where-Klauseln nicht benötigst.
:::
### HashElements-Index
Stringlisten können als Ganzes gehasht werden (indem man `IndexType.hash` verwendet) oder die Elemente der Liste können seperat gehasht werden (indem man `IndexType.hashElements` nutzt) wodurch ein Mehreintragsindex mit gehashten Elementen erzeugt wird.
:::tip
Nutze `IndexType.hashElements` für `List` bei denen du `elementEqualTo`-Where-Klauseln benötigst.
:::
## Komposit-Indizes
Ein Komposit-Index ist ein Index auf mehrere Eigenschaften. Isar erlaubt es dir zusammengesetzte Indizes mit bis zu drei Eigenschaften zu erstellen.
Komposit-Indizes sind auch als Mehr-Spalten-Indizes bekannt.
Es ist vermutlich am besten mit einem Beispiel zu starten. Wir erstellen eine Personen-Collection und definieren einen zusammengesetzten Index auf die Alters- und Namenseigenschaften:
```dart
@collection
class Person {
Id? id;
late String name;
@Index(composite: [CompositeIndex('name')])
late int age;
late String hometown;
}
```
**Daten:**
| id | name | age | hometown |
| --- | ------ | --- | --------- |
| 1 | Daniel | 20 | Berlin |
| 2 | Anne | 20 | Paris |
| 3 | Carl | 24 | San Diego |
| 4 | Simon | 24 | Munich |
| 5 | David | 20 | New York |
| 6 | Carl | 24 | London |
| 7 | Audrey | 30 | Prague |
| 8 | Anne | 24 | Paris |
**Generierter Index:**
| age | name | id |
| --- | ------ | --- |
| 20 | Anne | 2 |
| 20 | Daniel | 1 |
| 20 | David | 5 |
| 24 | Anne | 8 |
| 24 | Carl | 3 |
| 24 | Carl | 6 |
| 24 | Simon | 4 |
| 30 | Audrey | 7 |
Der generierte zusammengesetzte Index enthält alle Personen sortiert nach ihrem Alter und ihrem Namen.
Komposit-Indizes sind super, wenn du effiziente Abfragen, sortiert nach mehreren Eigenschaften, stellen willst. Sie erlauben auch anspruchsvolle Where-Klauseln mit mehreren Eigenschaften:
```dart
final result = await isar.where()
.ageNameEqualTo(24, 'Carl')
.hometownProperty()
.findAll() // -> ['San Diego', 'London']
```
Die letzte Eigenschaft eines zusammengesetzten Index unterstützt auch Bedingungen wie `startsWith()` oder `lessThan()`:
```dart
final result = await isar.where()
.ageEqualToNameStartsWith(20, 'Da')
.findAll() // -> [Daniel, David]
```
## Mehrfach-Indizes
Wenn du eine Liste mit `IndexType.value` indizierst, wird Isar automatische einen Mehrfach-Index erzeugen und jeder Eintrag in der Liste wird mit dem Objekt indiziert. Das funktioniert für alle Listentypen.
Zu sinnvollen Anwendungen für Mehrfach-Indizes zählen das Indizieren einer Liste an Tags oder einen Volltext-Index zu erstellen.
```dart
@collection
class Product {
Id? id;
late String description;
@Index(type: IndexType.value, caseSensitive: false)
List get descriptionWords => Isar.splitWords(description);
}
```
`Isar.splitWords()` trennt einen String nach der [Unicode Annex #29](https://unicode.org/reports/tr29/)-Spezifikation in Worte, sodass es für fast alle Sprachen richtig funktioniert.
**Daten:**
| id | description | descriptionWords |
| --- | ---------------------------- | ---------------------------- |
| 1 | comfortable blue t-shirt | [comfortable, blue, t-shirt] |
| 2 | comfortable, red pullover!!! | [comfortable, red, pullover] |
| 3 | plain red t-shirt | [plain, red, t-shirt] |
| 4 | red necktie (super red) | [red, necktie, super, red] |
Einträge mit doppelten Worten tauchen nur einmal im Index auf.
**Generierter Index:**
| descriptionWords | id |
| ---------------- | --------- |
| comfortable | [1, 2] |
| blue | 1 |
| necktie | 4 |
| plain | 3 |
| pullover | 2 |
| red | [2, 3, 4] |
| super | 4 |
| t-shirt | [1, 3] |
Dieser Index kann nun für (Gleichheits- oder) Präfix-Where-Klauseln der individuellen Worte der Beschreibung verwendet werden.
:::tip
Statt Worte direkt zu speichern kannst du auch in Betracht ziehen das Ergebnis einer [Phonetischen Suche](https://de.wikipedia.org/wiki/Phonetische_Suche) wie von dem Algorithmus [Soundex](https://de.wikipedia.org/wiki/Soundex) zu verwenden.
:::
================================================
FILE: docs/docs/de/limitations.md
================================================
---
title: Limitationen
---
# Limitationen
Wie du weißt, funktioniert Isar auf Mobilgeräten und Desktops und läuft sowohl auf der VM, als auch im Web. Die beiden Plattformen sind sehr verschieden und haben unterschiedliche Limitationen.
## VM Limitationen
- Nur die ersten 1024 Bytes eines Strings können für eine Präfix-Where-Klausel verwendet werden
- Objekte können höchstens 16MB groß sein
## Web Limitationen
Weil Isar Web auf IndexedDB beruht, gibt es dort mehr Limitationen, aber sie sind kaum zu merken, während du Isar benutzt.
- Synchrone Methoden werden nicht unterstützt
- Zurzeit sind die `Isar.splitWords()`- und `.matches()`-Filter noch nicht implementiert
- Schemaänderungen werden nicht so genau wie in der VM überprüft, also achte darauf die Regeln einzuhalten
- Alle Zahlen-Typen werden als Double (dem einzigen JS Zahlen-Typ) gespeichert, also hat `@Size32` keine Wirkung
- Indizes werden anders dargestellt, wodurch Hash-Indizes nicht weniger Platz benötigen (auch wenn sie gleich funktionieren)
- `col.delete()` und `col.deleteAll()` funktionieren korrekt, aber der Rückgabewert ist nicht richtig
- `col.clear()` setzt den auto-increment-Wert nicht zurück
- `NaN` wird als Wert nicht unterstützt
================================================
FILE: docs/docs/de/links.md
================================================
---
title: Links
---
# Links
Links ermöglichen es dir Verhältnisse zwischen Objekten, wie z.B. dem Autor (Benutzer) eines Kommentars, auszudrücken. Du kannst `1:1`, `1:n`, `n:m` Verhältnisse mit Isar-Links modellieren. Links zu nutzen ist unpraktischer als eingebettete Objekte zu benutzen, und du solltest eingebettete Objekte, wann immer möglich, verwenden.
Stell dir den Link wie eine separate Tabelle vor, die die Beziehung enthält. Links ähneln SQL-Beziehungen, haben aber einen anderen Funktionsumfang und eine andere API.
## IsarLink
`IsarLink` kann keines oder ein zugehöriges Objekt enthalten und kann genutzt werden um eine zu-einem-Relation darzustellen. `IsarLink` hat eine einzige Eigenschaft genannt `value`, die das verlinkte Objekt enthält.
Links sind lazy, also musst du dem `IsarLink` explizit sagen den `value` zu Laden oder zu Speichern. Das kannst du erreichen, indem du `linkProperty.load()` und `linkProperty.save()` aufrufst.
:::tip
Die ID-Eigenschaft der Quell- und Ziel-Collections sollte nicht-final sein.
:::
Für nicht-Web-Ziele werden Links automatisch geladen, wenn du sie zum ersten Mal verwendest. Fangen wir damit an einen IsarLink zu einer Collection hinzuzufügen:
```dart
@collection
class Teacher {
Id? id;
late String subject;
}
@collection
class Student {
Id? id;
late String name;
final teacher = IsarLink();
}
```
Wir haben einen Link zwischen Lehrern und Schülern definiert. Jeder Schüler kann in diesem Beispiel genau einen Lehrer haben.
Zuerst legen wir einen Lehrer an und fügen ihn dann einem Schüler hinzu. Wir müssen den Lehrer mit der `.put()`-Methode einfügen und den Link manuell speichern.
```dart
final mathTeacher = Teacher()..subject = 'Math';
final linda = Student()
..name = 'Linda'
..teacher.value = mathTeacher;
await isar.writeTxn(() async {
await isar.students.put(linda);
await isar.teachers.put(mathTeacher);
await linda.teachers.save();
});
```
Wir können den Link jetzt nutzen:
```dart
final linda = await isar.students.where().nameEqualTo('Linda').findFirst();
final teacher = linda.teacher.value; // > Teacher(subject: 'Math')
```
Versuchen wir das gleiche mit synchronem Code. Wir brauchen den Link nicht manuell zu speichern, weil `.putSync()` automatisch alle Links speichert. Es erzeugt sogar den Lehrer für uns.
```dart
final englishTeacher = Teacher()..subject = 'English';
final david = Student()
..name = 'David'
..teacher.value = englishTeacher;
isar.writeTxnSync(() {
isar.students.putSync(david);
});
```
## IsarLinks
Es würde mehr Sinn ergeben, wenn der Schüler aus dem vorherigen Beispiel mehrere Lehrer haben kann. Glücklicherweise hat Isar `IsarLinks`, was mehrere zugehörige Objekte beinhalten kann und eine zu-vielen-Relation ausdrückt.
`IsarLinks` wird von `Set` erweitert und stellt alle Methoden die auf Sets angewandt werden können zur Verfügung.
`IsarLinks` verhält sich ähnlich wie `IsarLink` und ist auch lazy. Um alle verlinkten Objekte zu laden, musst du die Methode `linkProperty.load()` aufrufen. Um die Änderungen persistent zu machen, musst du `linkProperty.save()` aufrufen.
Intern werden `IsarLink` und `IsarLinks` auf die gleiche Weise dargestellt. Wir können den `IsarLink` von vorher zu einem `IsarLinks` ausbauen, um mehrere Lehrer einem einzelnen Schüler zuzuweisen (ohne Daten zu verlieren).
```dart
@collection
class Student {
Id? id;
late String name;
final teacher = IsarLinks();
}
```
Das funktioniert, weil wir den Namen des Links (`teacher`) nicht verändert haben, weshalb sich Isar von vorher daran erinnert.
```dart
final biologyTeacher = Teacher()..subject = 'Biology';
final linda = isar.students.where()
.filter()
.nameEqualTo('Linda')
.findFirst();
print(linda.teachers); // {Teacher('Math')}
linda.teachers.add(biologyTeacher);
await isar.writeTxn(() async {
await linda.teachers.save();
});
print(linda.teachers); // {Teacher('Math'), Teacher('Biology')}
```
## Rückverlinkungen
Ich höre dich schon, "Was, wenn wir umgekehrte Relationen ausdrücken möchten?", fragen. Mach dir keine Sorgen; wir führen jetzt Rückverlinkungen ein.
Rückverlinkungen sind Links in umgekerhrter Richtung. Jeder Link hat implizit immer eine Rückverlinkung. Du kannst sie in deiner App verfügbar machen, indem du `IsarLink` oder `IsarLinks` mit `@Backlink()` annotierst.
Rückverlinkungen benötigen keinen zusätzlichen Speicher oder Ressourcen; du kannst sie frei hinzufügen, löschen und umbenennen, ohne Daten zu verlieren.
Wir wollen wissen, welche Schüler ein spezifischer Lehrer hat, also definieren wir eine Rückverlinkung:
```dart
@collection
class Teacher {
Id id;
late String subject;
@Backlink(to: 'teacher')
final student = IsarLinks();
}
```
Wir müssen angeben, auf welchen Link die Rückverlinkung zeigt. Es ist möglich, mehrere verschiedene Links zwischen zwei Objekten zu haben.
## Links initialisieren
`IsarLink` und `IsarLinks` haben Konstruktoren ohne Argumente und sollten verwendet werden um die Link-Eigenschaft anzugeben, wenn das Objekt erstellt wird. Es hat sich bewährt Link-Eigenschaften `final` zu setzen.
Wenn du dein Objekt zum ersten Mal mit der `put()`-Methode speicherst, wird der Link mit Quell- und Ziel-Collection initialisiert und du kannst Methoden wie `load()` und `save()` benutzen. Ein Link fängt sofort an Änderungen zu verfolgen, nachdem er erzeugt wurde, sodass du Relationen sogar anlegen oder entfernen kannst, bevor der Link initialisiert wurde.
:::danger
Es ist verboten einen Link zu einem anderen Objekt zu übertragen.
:::
================================================
FILE: docs/docs/de/queries.md
================================================
---
title: Abfragen
---
# Abfragen
Mit Abfragen kannst du Einträge finden, die bestimmten Bedingungen entsprechen, zum Beispiel:
- Finde alle markierten Kontakte
- Finde eindeutige Vornamen in den Kontakten
- Lösche alle Kontakte, die keinen Nachnamen definiert haben
Weil Abfragen nicht in Dart, sondern auf der Datenbank ausgeführt werden, sind sie sehr schnell. Wenn du Indizes sinnvoll benutzt, kannst du deine Abfrageleistung sogar weiter steigern. Als nächstes lernst du, wie man Abfragen schreibt und wie du sie so schnell wie möglich machen kannst.
Es gibt zwei verschiedene Methoden um Einträge zu filtern: Filter und Where-Klauseln. Wir beginnen indem wir uns die Funktionsweise von Filtern ansehen.
## Filter
Filter sind leicht zu benutzen und zu verstehen. Abhängig von den Typen deiner Eigenschaften gibt es verschiedene verfügbare Filteroperationen mit größtenteils selbsterklärenden Namen.
Filter funktionieren, indem sie einen Ausdruck für jedes Objekt der zu filternden Collection auswerten. Wenn der Ausdruck `true` ergibt, fügt Isar das Objekt zu den Ergebnissen hinzu.
Filter haben keinen Einfluss auf die Reihenfolge der Ergebnisse.
Wir benutzen das folgende Modell für die Beispiele weiter unten:
```dart
@collection
class Shoe {
Id? id;
int? size;
late String model;
late bool isUnisex;
}
```
### Abfragebedingungen
Abhängig vom Feld-Typen gibt es verschiedene mögliche Bedingungen.
| Bedingung | Beschreibung |
| ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.equalTo(value)` | Trifft auf Werte zu, die mit dem angegebenen `value` übereinstimmen. |
| `.between(lower, upper)` | Trifft auf Werte zu, die zwischen `lower` und `upper` liegen. |
| `.greaterThan(bound)` | Trifft auf Werte zu, de größer als `bound` sind. |
| `.lessThan(bound)` | Trifft auf Werte zu, die kleiner als `bound` sind. `null`-Werte werden eingeschlossen, da `null` als kleiner als jeder andere Wert betrachtet wird. |
| `.isNull()` | Trifft auf Werte zu, die `null` sind. |
| `.isNotNull()` | Trifft auf Werte zu, die nicht `null` sind. |
| `.length()` | Abfragen nach Längen von Listen, Strings und Links filtern Objekte basierend auf der Anzahl der Elemente in einer Liste oder in einem Link. |
Nehmen wir an, dass die Datenbank vier Schuhe mit den Gößen 39, 40, 46 und einen mit einer nicht festgelegten Größe (`null`) hat. Wenn du keine Sortierung durchführst, werden die Werte nach ID geordnet zurückgegeben.
```dart
isar.shoes.filter()
.sizeLessThan(40)
.findAll() // -> [39, null]
isar.shoes.filter()
.sizeLessThan(40, include: true)
.findAll() // -> [39, null, 40]
isar.shoes.filter()
.sizeBetween(39, 46, includeLower: false)
.findAll() // -> [40, 46]
```
### Logische Operatoren
Du kannst Bedingungen verbinden, indem du die folgenden logischen Operatoren verwendest:
| Operator | Beschreibung |
| ---------- | ------------------------------------------------------------------------------------ |
| `.and()` | Ergibt `true`, wenn von linkem und rechtem Ausdruck beide `true` ergeben. |
| `.or()` | Ergibt `true`, wenn mindestens einer von beiden Ausdrücken `true` ergibt. |
| `.xor()` | Ergibt `true`, wenn genau einer von beiden Ausdrücken `true` ergibt. |
| `.not()` | Negiert das Ergebnis des nachfolgenden Ausdrucks. |
| `.group()` | Gruppiert Bedingungen und ermöglicht es eine Reihenfolge der Auswertung festzulegen. |
Wenn du alle Schuhe mit der Größe 46 finden möchstest, kannst du die folgende Abfrage verwenden:
```dart
final result = await isar.shoes.filter()
.sizeEqualTo(46)
.findAll();
```
Wenn du mehr als eine Bedingung angeben möchtest, kannst du mehrere Filter verbinden, indem du sie mit logischem **und** `.and()`, logischem **oder** `.or()` oder logischem **exklusiven oder** `.xor()` verbindest.
```dart
final result = await isar.shoes.filter()
.sizeEqualTo(46)
.and() // Optional. Filter werden implizit mit einem logischen UND verbunden.
.isUnisexEqualTo(true)
.findAll();
```
Diese Abfrage ist äquivalent zu: `size == 46 && isUnisex == true`.
Du kannst auch Bedingungen gruppieren, indem du `.group()` benutzt:
```dart
final result = await isar.shoes.filter()
.sizeBetween(43, 46)
.and()
.group((q) => q
.modelNameContains('Nike')
.or()
.isUnisexEqualTo(false)
)
.findAll()
```
Diese Abfrage ist äquivalent zu: `size >= 43 && size <= 46 && (modelName.contains('Nike') || isUnisex == false)`.
Um eine Bedingung oder Gruppe zu negieren kannst du das logische **oder** `.not()` verwenden:
```dart
final result = await isar.shoes.filter()
.not().sizeEqualTo(46)
.and()
.not().isUnisexEqualTo(true)
.findAll();
```
Diese Abfrage ist äquivalent zu: `size != 46 && isUnisex != true`.
### String-Bedingungen
Zusätzlich zu den vorher genannten Abfragebedingungen, bieten String-Werte ein paar mehr Bedingungen. Platzhalter, ähnlich zu beispielsweise Regex, erlauben mehr Flexibilität beim Suchen.
| Bedingung | Beschreibung |
| -------------------- | ------------------------------------------------------------------------------ |
| `.startsWith(value)` | Trifft auf String-Werte zu, die mit dem angegebenen `value` beginnen. |
| `.contains(value)` | Trifft auf String-Werte zu, die das angegebene `value` enthalten. |
| `.endsWith(value)` | Trifft auf String-Werte zu, die mit dem angegebenen `value` enden. |
| `.matches(wildcard)` | Trifft auf String-Werte zu, die dem angegebenen `wildcard`-Muster entsprechen. |
**Groß-/Kleinschreibung**
Alle String-Operationen haben eine optionale `caseSensitive`-Eigenschaft, die standardmäßig `true` ist.
**Platzhalter**
Der [Ausdruck eines Platzhalter-Strings](https://de.wikipedia.org/wiki/Wildcard_(Informatik)) ist ein String, der normale Zeichen mit zwei speziellen Platzhalter-Zeichen verwendet:
- Der `*` Platzhalter trifft auf keines oder mehr von jedem Zeichen zu.
- Der `?` Platzhalter trifft auf jedes Einzelzeichen zu.
Zum Beispiel trifft der Platzhalter-String `"d?g"` auf `"dog"`, `"dig"` und `"dug"` zu, nicht aber auf `"ding"`, `"dg"` oder `"a dog"`.
### Abfragemodifikatoren
Manchmal ist es notwendig eine Abfrage auf Bedingungen aufzubauen oder für verschiedene Werte zu bauen. Isar hat ein sehr mächtiges Werkzeug um bedingte Abfragen zu bauen:
| Modifikator | Beschreibung |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.optional(cond, qb)` | Erweitert die Abfrage nur, wenn die Bedingung `cond`, `true` ist. Das kann fast überall in einer Abfrage verwendet werden, beispielsweise um sie über eine Bedingung zu sortieren oder begrenzen. |
| `.anyOf(list, qb)` | Erweitert die Abfrage für jeden Wert in `values` und verbindet die Bedingungen mit einem logischen **oder**. |
| `.allOf(list, qb)` | Erweitert die Abfrage für jeden Wert in `values` und verbindet die Bedingungen mit einem logischen **und**. |
In diesem Beispiel bauen wir eine Methode, die Schuhe mit einem optionale Filter finden kann:
```dart
Future> findShoes(Id? sizeFilter) {
return isar.shoes.filter()
.optional(
sizeFilter != null, // Wendet den Filter nur an, wenn sizeFilter != null ist
(q) => q.sizeEqualTo(sizeFilter!),
).findAll();
}
```
Wenn du alle Schuhe finden möchtest, die eine von mehreren Schuhgrößen haben, kannst du entweder eine konventionelle Abfrage schreiben oder den `anyOf()` Modifikator verwenden:
```dart
final shoes1 = await isar.shoes.filter()
.sizeEqualTo(38)
.or()
.sizeEqualTo(40)
.or()
.sizeEqualTo(42)
.findAll();
final shoes2 = await isar.shoes.filter()
.anyOf(
[38, 40, 42],
(q, int size) => q.sizeEqualTo(size)
).findAll();
// shoes1 == shoes2
```
Abfragemodifikatoren sind besonders dann sinnvoll, wenn du dynamische Abfragen bauen möchtest.
### Listen
Abfragen können sogar auf Listen gestellt werden:
```dart
class Tweet {
Id? id;
String? text;
List hashtags = [];
}
```
Du kannst eine Abfrage auf Basis der Listenlänge bauen:
```dart
final tweetsWithoutHashtags = await isar.tweets.filter()
.hashtagsIsEmpty()
.findAll();
final tweetsWithManyHashtags = await isar.tweets.filter()
.hashtagsLengthGreaterThan(5)
.findAll();
```
Diese sind äquivalent zu dem Dart-Code `tweets.where((t) => t.hashtags.isEmpty);` und `tweets.where((t) => t.hashtags.length > 5);`. Du kannst auch Abfragen basierend auf Listenelementen stellen:
```dart
final flutterTweets = await isar.tweets.filter()
.hashtagsElementEqualTo('flutter')
.findAll();
```
Das ist äquivalent zum Dart-Code `tweets.where((t) => t.hashtags.contains('flutter'));`.
### Eingebettete Objekte
Eingebettete Objekte sind eines von Isars nützlichsten Features. Sie können sehr einfach abgefragt werden mit gleichen Bedingungen für Objekte der obersten Ebene. Nehmen wir an, dass wir das folgende Modell haben:
```dart
@collection
class Car {
Id? id;
Brand? brand;
}
@embedded
class Brand {
String? name;
String? country;
}
```
Wir wollen alle Autos abfragen, die eine Marke mit dem Namen `"BMW"` und dem Land `"Germany"` haben. Wir können das mit der folgenden Abfrage erreichen:
```dart
final germanCars = await isar.cars.filter()
.brand((q) => q
.nameEqualTo('BMW')
.and()
.countryEqualTo('Germany')
).findAll();
```
Versuche immer verschachtelte Abfragen zu gruppieren. Die vorherige Abfrage ist effizienter als die folgende, auch wenn das Ergebnis gleich ist:
```dart
final germanCars = await isar.cars.filter()
.brand((q) => q.nameEqualTo('BMW'))
.and()
.brand((q) => q.countryEqualTo('Germany'))
.findAll();
```
### Links
Wenn dein Modell [Links oder Rückverlinkungen](links) enthält, kannst du deine Abfrage auf Basis der verlinkten Objekte oder der Anzahl an verlinkten Objekten filtern.
:::warning
Beachte, dass Link-Abfragen teuer sein können, weil Isar die verlinkten Objekte abrufen muss. Versuche stattdessen eingebettete Objekte zu verwenden.
:::
```dart
@collection
class Teacher {
Id? id;
late String subject;
}
@collection
class Student {
Id? id;
late String name;
final teachers = IsarLinks();
}
```
Wir wollen alle Schüler finden, die einen Mathe- oder Englischlehrer haben:
```dart
final result = await isar.students.filter()
.teachers((q) {
return q.subjectEqualTo('Math')
.or()
.subjectEqualTo('English');
}).findAll();
```
Link-Filter resultieren in `true`, wenn mindestens eines der verlinkten Objekte den Bedingungen entspricht.
Suchen wir nach allen Schülern, die keine Lehrer haben:
```dart
final result = await isar.students.filter().teachersLengthEqualTo(0).findAll();
```
oder alternativ:
```dart
final result = await isar.students.filter().teachersIsEmpty().findAll();
```
## Where-Klauseln
Where-Klauseln sind eine sehr mächtiges Werkzeug, aber es kann ein bisschen herausfordernd sein sie zu meistern.
Im Gegensatz zu Filtern nutzen Where-Klauseln die Indizes, die du im Schema definiert hast, um die Abfragebedingungen zu überprüfen. Einen Index abzufragen ist deutlich schneller als jeden Eintrag einzeln zu filtern.
➡️ Lerne mehr: [Indizes](indexes)
:::tip
Als eine einfache Regel solltest du immer versuchen die Einträge so weit wie möglich mit Where-Klauseln einzugrenzen und das restliche Filtern mit Filtern machen.
:::
Du kannst Where-Klauseln nur mit logischem **oder** verbinden. In anderen Worten, kannst du mehrere Where-Klauseln zusammenfügen, aber nicht die Überschneidung mehrerer Where-Klauseln abfragen.
Lass uns Indizes zu der Schuh-Collection hinzufügen:
```dart
@collection
class Shoe with IsarObject {
Id? id;
@Index()
Id? size;
late String model;
@Index(composite: [CompositeIndex('size')])
late bool isUnisex;
}
```
Hier gibt es zwei Indizes. Der Index auf `size` erlaubt es uns Where-Klauseln wie `.sizeEqualTo()` zu verwenden. Der zusammengesetzte Index auf `isUnisex` erlaubt es uns Where-Klauseln wie `.isUnisexSizeEqualTo()` zu nutzen. Aber auch `.isUnisexEqualTo()` ist möglich, weil du immer jedes Präfix eines Indexes benutzen kannst.
Wir können unsere Abfrage von vorher, die Unisex-Schuhe der Größe 46 findet, also mithilfe des zusammengesetzten Indexes umschreiben. Diese Abfrage sollte deutlich schneller sein, als die vorherige:
```dart
final result = isar.shoes.where()
.isUnisexSizeEqualTo(true, 46)
.findAll();
```
Where-Klauseln haben zwei weitere Superkräfte: Sie geben dir "kostenloses" Sortieren und eine superschnelle Eindeutigkeitsoperation.
### Where-Klauseln und Filter verbinden
Erinnerst du dich an die `shoes.filter()`-Abfragen? Das ist in Wirklichkeit nur eine Kurzform für `shoes.where().filter()`. Du kannst (und solltest) Where-Klauseln und Filter in der gleichen Abfrage verbinden, um die Vorteile beider zu nutzen:
```dart
final result = isar.shoes.where()
.isUnisexEqualTo(true)
.filter()
.modelContains('Nike')
.findAll();
```
Die Where-Klausel wird zuerst angewendet, um die Anzahl an Objekten, die gefiltert werden müssen, zu reduzieren. Dann wird der Filter auf die übrig gebliebenen Objekte angewendet.
## Sortierung
Du kannst definieren, wie Ergebnisse deiner Abfrage sortiert werden sollen, indem du die Methoden `.sortBy()`, `.sortByDesc()`, `.thenBy()` und `.thenByDesc()` nutzt.
Um alle Schuhe nach Modellnamen in aufsteigender und nach der Größe in absteigender Reihenfolge sortiert zu bekommen, ohne einen Index zu benutzen, aknnst du die folgende Abfrage stellen:
```dart
final sortedShoes = isar.shoes.filter()
.sortByModel()
.thenBySizeDesc()
.findAll();
```
Viele Ergebnisse zu sortieren kann teuer sein, besonders, weil das Sortieren vor dem Offset und vor der Limitierung stattfindet. Die Sortiermethoden benutzen niemals Indizes. Glücklicherweise können wir wieder Sortierung mit Where-Klauseln verwenden und so unsere Abfrage blitzschnell machen, auch wenn wir eine Million Objekte sortieren müssen.
### Sortierung mit Where-Klauseln
Wenn du eine **einzige** Where-Klausel in deiner Abfrage nutzt, sind die Ergebnisse schon nach dem Index sortiert. Das ist eine große Sache!
Nehmen wir an, wir haben Schuhe in den Größen `[43, 39, 48, 40, 42, 45]` und wir wollen alle Schuhe mit einer Größe größer als `42` haben und sie auch nach Größe sortiert haben:
```dart
final bigShoes = isar.shoes.where()
.sizeGreaterThan(42) // Sortiert die Ergebnisse auch nach Größe
.findAll(); // -> [43, 45, 48]
```
Wie du sehen kannst, sind die Ergebnisse nach dem `size`-Index sortiert. Wenn du die Reihenfolge der Where-Klausel umkehren möchtest, kannst du `sort` auf `Sort.desc` setzen:
```dart
final bigShoesDesc = await isar.shoes.where(sort: Sort.desc)
.sizeGreaterThan(42)
.findAll(); // -> [48, 45, 43]
```
Manchmal willst du keine Where-Klausel verwenden, aber trotzdem von der impliziten Sortierung profitieren. Dann kannst du die Where-Klausel `any` verwenden:
```dart
final shoes = await isar.shoes.where()
.anySize()
.findAll(); // -> [39, 40, 42, 43, 45, 48]
```
Wenn du einen Komposit-Index verwendest, werden die Ergebnisse nach allen Feldern des Indexes sortiert.
:::tip
Für den Fall, dass deine Ergebnisse sortiert sein müssen, versuche einen Index zu benutzen. Besonders wenn du mit `offset()` oder `limit()` arbeitest:
:::
Manchmal ist es nicht möglich oder sinnvoll einen Index zum Sortieren zu nutzen. Für solche Fälle solltest du Indizes benutzen, um zumindest die Anzahl an zu sortierenden Einträgen so weit wie möglich einzugrenzen.
## Eindeutige Werte
Um nur Einträge mit eindeutigen Werten zurückzubekommen, kannst du das Unterscheidbarkeitsprädikat verwenden. Zum Beispiel, um herauszufinden, wie viele unterscheidbare Schuhmodelle es in deiner Isar-Datenbank gibt:
```dart
final shoes = await isar.shoes.filter()
.distinctByModel()
.findAll();
```
Du kannst auch mehrere Unterscheidbarkeitsbedingungen verketten, um alle Schuhe mit unterscheidbaren Modell-Größe-Kombinationen zu finden:
```dart
final shoes = await isar.shoes.filter()
.distinctByModel()
.distinctBySize()
.findAll();
```
Nur das erste Ergebnis jeder Unterscheidbarkeitskombination wird zurückgegeben. Um das zu überprüfen kannst du Where-Klauseln und Sortieroperationen verwenden.
### Unterscheidbare Where-Klauseln
Wenn du einen nicht eindeutigen Index hast, kann es sein, dass du alle seine unterscheidbaren Werte haben möchtest. Du könntest die `distinctBy`-Operation des vorherigen Abschnitts verwenden, aber sie wird erst nach dem Sortieren und Filtern angewandt, sodass ein bisschen Overhead entsteht.
Wenn du nur eine einzelne Where-Klausel verwendest, kannst du stattdessen dem Index vertrauen die Unterscheidbarkeitsoperation durchzuführen.
```dart
final shoes = await isar.shoes.where(distinct: true)
.anySize()
.findAll();
```
:::tip
Theoretisch könntest du sogar mehrere Where-Klauseln für Sortierung und Unterscheidbarkeit nutzen. Die einzige Einschränkung besteht darin, dass sich diese Where-Klauseln nicht überschneiden, also nicht denselben Index verwenden dürfen. Für die richtige Sortierung müssen sie auch in Sortierreihenfolge angewandt werden. Sei sehr vorsichtig, wenn du dich darauf verlässt.
:::
## Offset & Limitierung
Es ist oft eine gute Idee die Anzahl an Ergebnissen einer Abfrage zu beschränken, für beispielsweise lazy Listenansichten. Du kannst das erreichen, indem du ein `limit()` setzt:
```dart
final firstTenShoes = await isar.shoes.where()
.limit(10)
.findAll();
```
Indem du ein `offset()` setzt, kannst du die Ergebnisse deiner Abfrage in mehrere Auflistungen aufteilen.
```dart
final firstTenShoes = await isar.shoes.where()
.offset(20)
.limit(10)
.findAll();
```
Weil das instanziieren eines Dart-Objekts meistens der teuerste Teil beim Ausführen einer Abfrage ist, ist es eine gute Idee nur die Objekte zu laden, die du benötigst.
## Reihenfolge der Ausführung
Isar führt Abfragen immer in der gleichen Reihenfolge aus:
1. Primär- oder Sekundärindex durchlaufen, um Objekte zu finden (Where-Klauseln anwenden)
2. Objekte filtern
3. Ergebnisse sortieren
4. Unterscheidbarkeitsoperation durchführen
5. Offset & Limit auf Ergebnisse anwenden
6. Ergebnisse zurückgeben
## Abfrageoperationen
In den vorangegangenen Beispielen haben wir `.findAll()` verwendet, um alle passenden Objekte zu erhalten. Es sind jedoch mehr Operationen verfügbar:
| Operation | Beschreibung |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `.findFirst()` | Erhalte nur das erste passende Objekt oder `null` wenn kein passendes gefunden wird. |
| `.findAll()` | Erhalte alle passenden Objekte. |
| `.count()` | Zählt, wieviele Objekte der Abfrage entsprechen. |
| `.deleteFirst()` | Löscht das erste passende Objekt aus der Collection. |
| `.deleteAll()` | Löscht alle passenden Objekte aus der Collection. |
| `.build()` | Konstruiert eine Abfrage um sie später wiederzuverwenden. Das erspart die Kosten, eine Abfrage erneut zu bauen, wenn du sie mehrfach ausführen willst. |
## Abfragen auf Eigenschaften
Wenn du nur an den Werten einer bestimmten Eigenschaft interessiert bist, kannst du Abfragen auf Eigenschaften machen. Baue einfach eine normale Abfrage und wähle eine Eigenschaft:
```dart
List models = await isar.shoes.where()
.modelProperty()
.findAll();
List sizes = await isar.shoes.where()
.sizeProperty()
.findAll();
```
Nur eine einzige Eigenschaft zu nutzen erspart Zeit bei der Deserialisierung. Abfragen auf Eigenschaften funktionieren auch bei eingebetteten Objekten und Listen.
## Aggregation
Isar unterstützt die Aggregation der Werte einer Abfrage auf Eigenschaften. Die folgenden Aggregatoroperationen sind verfügbar:
| Operation | Beschreibung |
| ------------ | -------------------------------------------------------------------- |
| `.min()` | Findet den minimalen Wert oder `null`, wenn keiner passt. |
| `.max()` | Findet den maximalen Wert oder `null`, wenn keiner passt. |
| `.sum()` | Addiert alle Werte. |
| `.average()` | Berechnet den Durchschnitt aller Werte oder `NaN` wenn keiner passt. |
Aggregatoren zu nutzen ist deutlich schneller, als alle passenden Objekte zu finden und die Aggregation manuell durchzuführen.
## Dynamische Abfragen
:::danger
Dieser Abschnitt ist höchstwahrscheinlich nicht wichtig für dich. Es ist davon abzuraten dynamische Abfragen zu nutzen, es sei denn du benötigst sie wirklich (was selten vorkommt).
:::
Alle der vorherigen Beispiele haben den QueryBuilder und seine statischen Erweiterungsmethoden genutzt. Vielleicht möchtest du dynamische Abfragen oder eine benutzerdefinierte Abfragesprache (wie den Isar Inspektor) bauen. In dem Fall kannst du die Methode `buildQuery()` verwenden:
| Parameter | Beschreibung |
| --------------- | ---------------------------------------------------------------------------------------------------------- |
| `whereClauses` | Die Where-Klauseln der Abfrage. |
| `whereDistinct` | Ob Where-Klauseln nur unterscheidbare Werte zurückgeben sollen (nur sinnvoll für einzelne Where-Klauseln). |
| `whereSort` | Die Durchlaufreihenfolge der Where-Klauseln (nur sinnvoll für einzelne Where-Klauseln). |
| `filter` | Die Filter, die auf die Ergebnisse angewendet werden sollen. |
| `sortBy` | Eine Liste an Eigenschaften nach denen sortiert werden soll. |
| `distinctBy` | Eine Liste an Eigenschaften, an denen die Unterscheidbarkeit festgemacht wird. |
| `offset` | Der Offset der Ergebnisse. |
| `limit` | Die maximale Anzahl an Ergebnissen, die zurückgegeben werden. |
| `property` | Wenn nicht-null, werden nur die Werte dieser Eigenschaft zurückgegeben. |
Bauen wir eine dynamische Abfrage:
```dart
final shoes = await isar.shoes.buildQuery(
whereClauses: [
WhereClause(
indexName: 'size',
lower: [42],
includeLower: true,
upper: [46],
includeUpper: true,
)
],
filter: FilterGroup.and([
FilterCondition(
type: ConditionType.contains,
property: 'model',
value: 'nike',
caseSensitive: false,
),
FilterGroup.not(
FilterCondition(
type: ConditionType.contains,
property: 'model',
value: 'adidas',
caseSensitive: false,
),
),
]),
sortBy: [
SortProperty(
property: 'model',
sort: Sort.desc,
)
],
offset: 10,
limit: 10,
).findAll();
```
Die folgende Abfrage ist äquivalent:
```dart
final shoes = await isar.shoes.where()
.sizeBetween(42, 46)
.filter()
.modelContains('nike', caseSensitive: false)
.not()
.modelContains('adidas', caseSensitive: false)
.sortByModelDesc()
.offset(10).limit(10)
.findAll();
```
================================================
FILE: docs/docs/de/recipes/data_migration.md
================================================
---
title: Migration von Daten
---
# Migration vron Daten
Isar migriert deine Datenbankschemas automatisch, wenn du Collections, Felder oder Indizes hinzufügst oder entfernst. Manchmal möchtest du möglicherweise auch deine Daten migrieren. Isar liefert keine eingebaute Lösung, weil das willkürliche Migrationsbeschränkungen festlegen würde. Es ist leicht eine passende Migrationslogik zu implementieren.
Wir wollen in diesem Beispiel eine einzige Version für die gesamte Datenbank verwenden. Wir benutzen Shared Preferences um die derzeitige Version zu speichern und vergleichen diese mit der Version zu der wir unsere Daten migrieren wollen. Wenn die Versionen nicht übereinstimmen, migrieren wir die Daten und aktualisieren die Version.
:::tip
Du kannst auch jeder Collection seine eigene Version zuweisen und sie individuell migrieren.
:::
Stell dir vor, wie haben eine Benutzer-Collection mit einem Feld Geburtstag. In Version 2 unserer App benötigen wir ein zusätzliches Feld Geburtsjahr um Benutzer anhand des Alters abzufragen.
Version 1:
```dart
@collection
class User {
Id? id;
late String name;
late DateTime birthday;
}
```
Version 2:
```dart
@collection
class User {
Id? id;
late String name;
late DateTime birthday;
short get birthYear => birthday.year;
}
```
Das Problem ist, dass vorhandene Benutzermodelle ein leeres `birthYear`-Feld haben werden, weil es in Version 1 nicht existiert hat. Wir müssen die Daten migrieren, um das `birthYear`-Feld zu setzen.
```dart
import 'package:isar/isar.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() async {
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[UserSchema],
directory: dir.path,
);
await performMigrationIfNeeded(isar);
runApp(MyApp(isar: isar));
}
Future performMigrationIfNeeded(Isar isar) async {
final prefs = await SharedPreferences.getInstance();
final currentVersion = prefs.getInt('version') ?? 2;
switch(currentVersion) {
case 1:
await migrateV1ToV2(isar);
break;
case 2:
// Wenn die Version nicht gesetzt (neue Installation), oder schon 2 ist, müssen wir nicht migrieren
return;
default:
throw Exception('Unknown version: $currentVersion');
}
// Version aktualisieren
await prefs.setInt('version', 2);
}
Future migrateV1ToV2(Isar isar) async {
final userCount = await isar.users.count();
// Wir paginieren durch die Benutzer, um zu vermeiden, dass wir alle Benutzer gleichzeitig in den Speicher laden
for (var i = 0; i < userCount; i += 50) {
final users = await isar.users.where().offset(i).limit(50).findAll();
await isar.writeTxn((isar) async {
// Wir müssen nichts aktualisieren, weil der birthYear-Getter verwendet wird
await isar.users.putAll(users);
});
}
}
```
:::warning
Wenn du viele Daten migrieren musst, solltest du überlegen einen Hintergrund-Isolate zu verwenden, um eine Belastung des UI-Threads zu verhindern.
:::
================================================
FILE: docs/docs/de/recipes/full_text_search.md
================================================
---
title: Volltextsuche
---
# Volltextsuche
Volltextsuche ist ein mächtiges Werkzeug um Text in der Datenbank zu suchen. Du solltest schon damit vertraut sein, wie [Indizes](/indexes) funktionieren, aber wir schauen uns die Grundlagen an.
Ein Index funktioniert wie eine Nachschlagetabelle, die es der Abfrage-Engine ermöglicht Einträge mit einem bestimmten Wert schnell zu finden. Zum Beispiel, wenn du ein `title`-Feld in deinem Objekt hast, kannst du einen Index auf das Feld anlegen, um die Geschwindigkeit zu erhöhen, ein Objekt mit bestimmtem Titel zu finden.
## Warum ist Volltextsuche sinnvoll?
Du kannst Text leicht durchsuchen, indem du Filter verwendest. Es gibt mehrere unterschiedliche String-Operationen, zum Beispiel `.startsWith()`, `.contains()` und `.matches()`. Das Problem mit Filtern ist, dass ihre Laufzeit `O(n)` ist, wobei `n` die Anzahl der Einträge in der Collection ist. String-Operationen wie `.matches()` sind besonders teuer.
:::tip
Volltextsuche ist deutlich schneller als Filter, aber Indizes haben ein paar Einschränkungen. In diesem Rezept wollen wir uns angucken, wie man diese Limitationen umgeht.
:::
## Grundlegendes Beispiel
Die Idee ist immer die Gleiche: Anstatt den ganzen Text zu indizieren, indizieren wir die Worte im Text, sodass wir individuell nach ihnen suchen können.
Bauen wir den grundlegendsten Volltext-Index:
```dart
class Message {
Id? id;
late String content;
@Index()
List get contentWords => content.split(' ');
}
```
Wir können jetzt nach Nachrichten suchen, die spezifische Worte enthalten:
```dart
final posts = await isar.messages
.where()
.contentWordsAnyEqualTo('hello')
.findAll();
```
Diese Abfrage ist superschnell, aber es gibt ein paar Probleme:
1. Wir können nur nach ganzen Worten suchen
2. Wir missachten Zeichensetzung
3. Wir unterstützen keine anderen Leerzeichen
## Text richtig trennen
Versuchen wir das vorherige Beispiel zu verbessern. Wir könnten versuchen einen komplizierten Regex zu entwickeln, um Worte zu trennen, aber das ist vermutlich langsam und in Grenzfällen falsch.
Der [Unicode Annex #29](https://unicode.org/reports/tr29/) definiert wie man, für fast alle Sprachen, Text richtig in Worte trennt. Das ist ziemlich kompliziert, aber glücklicherweise macht Isar den schwierigsten Teil der Arbeit für uns:
```dart
Isar.splitWords('hello world'); // -> ['hello', 'world']
Isar.splitWords('The quick (“brown”) fox can’t jump 32.3 feet, right?');
// -> ['The', 'quick', 'brown', 'fox', 'can’t', 'jump', '32.3', 'feet', 'right']
```
## Ich will mehr Kontrolle
Das ist kinderleicht! Wir können unseren Index so ändern, dass er auch Präfixe findet und Groß-/Kleinschreibung ignoriert:
```dart
class Post {
Id? id;
late String title;
@Index(type: IndexType.value, caseSensitive: false)
List get titleWords => title.split(' ');
}
```
Isar speichert die Worte standardmäßig als gehashte Werte, was schnell und platzsparend ist. Aber Hashes können nicht für die Präfixüberprüfung verwendet werden. Wenn wir `IndexType.value` verwenden, können wir den Index ändern, um direkt Worte zu benutzen. Das ermöglicht uns die `.titleWordsAnyStartsWith()`-Where-Klausel benutzen zu können:
```dart
final posts = await isar.posts
.where()
.titleWordsAnyStartsWith('hel')
.or()
.titleWordsAnyStartsWith('welco')
.or()
.titleWordsAnyStartsWith('howd')
.findAll();
```
## Ich brauche auch `.endsWith()`
Klar! Wir werden einen Trick verwenden, um `.endsWith()` verwenden zu können:
```dart
class Post {
Id? id;
late String title;
@Index(type: IndexType.value, caseSensitive: false)
List get revTitleWords {
return Isar.splitWords(title).map(
(word) => word.reversed).toList()
);
}
}
```
Vergiss nicht das Wortende umzukehren nach dem du suchen willst:
```dart
final posts = await isar.posts
.where()
.revTitleWordsAnyStartsWith('lcome'.reversed)
.findAll();
```
## Abstammungsalgorithmen
Leider unterstützen Indizes nicht die `.contains()`-Methode (das stimmt auch für andere Datenbanken). Aber es gibt ein paar Alternativen, die es wert sind, erkundet zu werden. Eine Wahl hängt stark von deinem Verwendungszweck ab. Ein Beispiel ist, den Ursprung von Worten, statt ganzer Worte, zu indizieren.
Ein Abstammungsalgorithmus ist der Prozess einer linguistischen Normalisierung, bei dem die Varianten eines Wortes in eine gleichmäßige Form reduziert werden:
```
connection
connections
connective ---> connect
connected
connecting
```
Beliebte Algorithmen sind der [Porter stemming algorithm](https://tartarus.org/martin/PorterStemmer/) und der [Snowball stemming algorithms](https://snowballstem.org/algorithms/).
Es gibt auch fortgeschrittenere Formen wie der [Lemmatisierung](https://de.wikipedia.org/wiki/Lemma_(Lexikographie)#Lemmatisierung).
## Phonetische Suche
Eine [Phonetische Suche](https://de.wikipedia.org/wiki/Phonetische_Suche) ist ein Algorithmus, um Worte nach ihrer Aussprache zu indizieren. Anders augedrückt, erlaubt es dir Worte zu finden, die ähnlich zu den Gesuchten klingen.
:::warning
Die meisten phonetischen Algorithmen unterstützen nur eine einzige Sprache.
:::
### Soundex
[Soundex](https://de.wikipedia.org/wiki/Soundex) ist ein phonetischer Algorithmus um Namen danach zu indizieren, wie sie im Englischen ausgesprochen werden. Das Ziel ist es Homophone in die gleiche Repräsentation zu übertragen, sodass sie gefunden werden, trotz der kleinen Unterschiede in der Rechtschreibung. Es ist ein unkomplizierter Algorithmus, von dem es mehrere verbesserte Versionen gibt.
Wenn du diesen Algorithmus verwendest, wegeben `"Robert"` und `"Rupert"` beide den String `"R163"`, während `"Rubin"` `"R150"` ergibt. `"Ashcraft"` und `"Ashcroft"` erzeugen beide `"A261"`.
### Double Metaphone
Der phonetische Umwandlungsalgorithmus [Double Metaphone](https://en.wikipedia.org/wiki/Metaphone) ist die zweite Generation dieses Algorithmus. Er macht mehrere fundamentale Designverbesserungen gegenüber dem originalen Metaphone-Algorithmus.
Double Metaphone klärt verschiedene Unregelmäßigkeiten im Englischen aufgrund von slawischer, germanischer, keltischer, griechischer, französischer, italienischer, spanischer, chinesischer und anderer Herkunft.
================================================
FILE: docs/docs/de/recipes/multi_isolate.md
================================================
---
title: Nutzung von mehreren Isolates
---
# Nutzung von mehreren Isolates
Statt in Threads, läuft der gesamte Dart-Code innerhalb von Isolates. Jeder Isolate hat einen eigenen Memory-Heap, was dafür sorgt, dass der Status eines Isolates von keinem anderen Isolate erreichbar ist.
Auf Isar kann von mehreren Isolates gleichzeitig zugegriffen werden und sogar Watcher funktionieren über Isolates hinweg. In diesem Rezept werden wir prüfen, wie man Isar in einem Umfeld mit mehreren Isolates nutzt.
## Wann man mehrere Isolates benutzt
Isar-Transaktionen werden parallel ausgeführt, auch wenn sie im gleichen Isolate laufen. In manchen Fällen ist es dennoch von Vorteil von mehreren Isolates auf Isar zuzugreifen.
Der Grund ist, dass Isar einige Zeit benötigt, um Daten von und zu Dart-Objekten zu codieren und decodieren. Du kannst es dir vorstellen, als würdest du JSON codieren und decodieren (nur effizienter). Diese Operationen laufen innerhalb des Isolates, von dem auf die Daten zugegriffen wird und blockieren daher natürlich anderen Code in dem Isolate. In anderen Worten: Isar führt einen Teil der Arbeit in deinem Dart-Isolate aus.
Wenn du nur ein paar hundert Objekte gleichzeitig lesen oder schreiben musst, ist es kein Problem, das im UI-Isolate zu tun. Aber für riesige Transaktionen oder wenn dein UI-Thread schon zu tun hat, solltest du überlegen ein seperates Isolate zu verwenden.
## Beispiel
Die erste Sache, die wir machen müssen, ist Isar in einem neuen Isolate zu öffnen. Weil eine Instanz von Isar schon im zentralen Isolate offen ist, wird `Isar.open()` diese Instanz zurückgeben.
:::warning
Stelle sicher, dass du die gleichen Schemas wie im zentralen Isolate zur Verfügung stellst. Sonst wirst du einen Fehler erhalten.
:::
`compute()` startet ein neues Isolate in Flutter und führt die angegebene Funktion in ihm aus.
```dart
void main() {
// Isar im UI-Isolate öffnen
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[MessageSchema],
directory: dir.path,
name: 'myInstance',
);
// Auf Änderungen in der Datenbank warten
isar.messages.watchLazy(() {
print('omg the messages changed!');
});
// Startet ein neues Isolate und erzeugt 10000 Nachrichten
compute(createDummyMessages, 10000).then(() {
print('isolate finished');
});
// Nach einiger Zeit:
// > omg the messages changed!
// > isolate finished
}
// Funktion, die im neuen Isolate ausgeführt werden soll
Future createDummyMessages(int count) async {
// Wir benötigen hier keinen Pfad, weil die Instanz schon offen ist
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[PostSchema],
directory: dir.path,
name: 'myInstance',
);
final messages = List.generate(count, (i) => Message()..content = 'Message $i');
// Wir benutzen synchrone Transaktionen in Isolates
isar.writeTxnSync(() {
isar.messages.insertAllSync(messages);
});
}
```
Es gibt ein paar interessante Dinge, die in dem Beispiel von eben auffallen:
- `isar.messages.watchLazy()` wird im UI-Isolate aufgerufen und wird über Änderungen durch ein anderes Isolate benachrichtigt.
- Instanzen werden über den Namen referenziert. Der Standardname ist `default`, aber in diesem Beispiel haben wir ihn auf `myInstance` gesetzt.
- Wir haben eine synchrone Transaktion benutzt, um die Nachrichten zu erzeugen. Unser neues Isolate zu blockieren ist kein Problem und synchrone Transaktionen sind ein bisschen schneller.
================================================
FILE: docs/docs/de/recipes/string_ids.md
================================================
---
title: String-IDs
---
# String-IDs
Das hier ist eine der häufigsten Anfragen, die ich erhalte, daher ist hier ein Tutorial zur Verwendung von String-IDs.
Isar unterstützt String-IDs nicht nativ, was einen guten Grund hat: Integer-IDs sind viel effizienter und schneller. Besonders bei Links ist der Overhead einer String-ID zu signifikant.
Ich verstehe, dass du manchmal externe Daten speichern musst, die UUIDs oder andere nicht-Integer-IDs verwenden. Ich empfehle, die String-ID als Eigenschaft in deinem Objekt zu speichern und eine schnelle Hash-Implementation um 64-bit Integer zu generieren und als ID zu verwenden.
```dart
@collection
class User {
String? id;
Id get isarId => fastHash(id!);
String? name;
int? age;
}
```
Mit diesem Ansatz bekommst du das Beste aus beiden Welten: Effiziente Integer-IDs für Links und die Fähigkeit String-IDs zu nutzen.
## Schnelle Hash-Funktion
Idealerweise sollte deine Hash-Funktion eine hohe Qualität haben (du willst keine Kollisionen) und schnell sein. Ich empfehle die folgende Implementation:
```dart
/// FNV-1a 64bit Hash-Algorithmus optimiert für Dart-Strings
int fastHash(String string) {
var hash = 0xcbf29ce484222325;
var i = 0;
while (i < string.length) {
final codeUnit = string.codeUnitAt(i++);
hash ^= codeUnit >> 8;
hash *= 0x100000001b3;
hash ^= codeUnit & 0xFF;
hash *= 0x100000001b3;
}
return hash;
}
```
Wenn du eine andere Hash-Funktion wählst, stelle sicher, dass sie einen 64-bit Integer zurückgibt und vermeide kryptographische Hash-Funktionen, weil die sehr viel langsamer sind.
:::warning
Vermeide es `string.hashCode` zu verwenden, weil nicht garantiert werden kann, dass die Methode über verschiedenen Plattformen und Versionen von Dart hinweg stabil ist.
:::
================================================
FILE: docs/docs/de/schema.md
================================================
---
title: Schema
---
# Schema
Wenn du Isar benutzt, um deine App-Daten zu speichern, dann hast du mit Collections zu tun. Eine Collection ist wie die Tabelle einer Datenbank in der angeschlossenen Isar-Datenbank und kann nur einen einzigen Typen von Dart Objekt enthalten. Jedes Collection-Objekt repräsentiert eine Zeile mit Daten in der zugehörigen Collection.
Die Definition einer Collection wird "Schema" genannt. Der Isar-Generator macht die meiste Arbeit für dich und generiert den Großteil des Codes den du benötigst, um die Collection zu benutzen.
## Aufbau einer Collection
Jede Collection in Isar wird über die Annotation `@collection` oder `@Collection()` an einer Klasse definiert. Eine Isar-Collection enthält Felder für jede Spalte in der zugehörigen Tabelle der Datenbank, auch eine, die dem Primärschlüssel entspricht.
Der folgende Code ist ein Beispiel einer simplen Collection, welche eine `User`-Tabelle mit den Spalten ID, Vorname und Nachname definiert:
```dart
@collection
class User {
Id? id;
String? firstName;
String? lastName;
}
```
:::tip
Um ein Feld persistent zu machen, muss Isar Zugriff auf das Feld haben. Du kannst sicherstellen, dass Isar Zugriff auf ein Feld hat, indem du es öffentlich machst, oder indem du Getter- und Setter-Methoden definierst.
:::
Es gibt ein paar optionale Parameter, um die Collection anzupassen:
| Konfiguration | Beschreibung |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `inheritance` | Steuert, ob Felder von Elternklassen und Mixins in Isar gespeichert werden. Standardmäßig aktiviert. |
| `accessor` | Erlaubt dir den Standardnamen des Collection-Accessors umzubenennen (zum Beispiel zu `isar.contacts` für die `Contact`-Collection). |
| `ignore` | Erlaubt es bestimmte Eigenschaften zu ignorieren. Diese werden auch bei Superklassen angewendet. |
### Isar ID
Jede Collection-Klasse muss eine ID-Eigenschaft vom Typen `Id` definieren, die ein Objekt eindeutig identifiziert. `Id` ist eigentlich nur ein Alias für `int`, der es dem Isar Generator ermöglicht die ID-Eigenschaft zu erkennen.
Isar indiziert ID Felder automatisch, was dir ermöglicht Objekte effizient anhand ihrer ID zu erhalten und modifizieren.
Du kannst eintweder IDs selbst zuweisen oder Isar fragen eine sich automatisch erhöhende ID festzulegen. Wenn das `id`-Feld `null` und nicht `final` ist, wird Isar eine auto-increment ID setzen. Wenn du eine nicht null-bare auto-increment ID haben möchtest, kannst du `Isar.autoIncrement` anstatt von `null` verwenden.
:::tip
Auto-increment IDs werden nicht wiederverwendet, wenn ein Objekt gelöscht wird. Der einzige Weg auto-increment IDs zurückzusetzen ist die Datenbank zu leeren.
:::
### Collections und Felder umbenennen
Isar benutzt standardmäßig den Klassennamen als Collectionnamen. Genauso verwendet Isar in der Datenbank Feldnamen als Spaltennamen. Wenn du willst, dass eine Collection oder ein Feld einen anderen Namen hat, dann füge die Annotation `@Name` hinzu. Das folgende Beispiel demonstriert angepasste Namen für Collections und Felder:
```dart
@collection
@Name("User")
class MyUserClass1 {
@Name("id")
Id myObjectId;
@Name("firstName")
String theFirstName;
@Name("lastName")
String familyNameOrWhatever;
}
```
Du solltest besonders dann, wenn du Dart-Felder oder -Klassen umbenennen willst, überlegen, die `@Name`-Annotation zu verwenden. Sonst wird die Datenbank das Feld oder die Collection löschen und neu erzeugen.
### Felder ignorieren
Isar stell sicher, dass alle öffentlichen Felder einer Collecion-Klasse erhalten bleiben. Wenn du eine Eigenschaft oder einen Getter mit `@ignore` annotierst, kannst du diese von der Sicherstellung ausschließen, wie im folgenden Code-Schnipsel gezeigt:
```dart
@collection
class User {
Id? id;
String? firstName;
String? lastName;
@ignore
String? password;
}
```
In Fällen, in denen deine Collection Felder von der Eltern-Collection erhält, ist es meist leichter die `ignore`-Eigenschaft der `@Collection`-Annotation zu verwenden:
```dart
@collection
class User {
Image? profilePicture;
}
@Collection(ignore: {'profilePicture'})
class Member extends User {
Id? id;
String? firstName;
String? lastName;
}
```
Wenn eine Collection ein Feld mit einem Typen enthält, der nicht von Isar unterstützt wird, musst du das Feld ignorieren.
:::warning
Beachte, dass es keine gute Vorgehensweise ist, Informationen in Isar-Objekten zu speichern, die nicht gesichert werden.
:::
## Unterstützte Typen
Isar unterstützt die folgenden Datentypen:
- `bool`
- `byte`
- `short`
- `int`
- `float`
- `double`
- `DateTime`
- `String`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
Zusätzlich sind eingebettete Objekte und Enums unterstützt. Wir behandeln diese weiter unten.
## byte, short, float
In vielen Fällen benötigst du nicht den gesamten Bereich eines 64-bit Integers oder Doubles. Isar unterstützt zusätzliche Typen, die es dir erlauben Speicherpaltz beim Speichern kleinerer Zahlen zu sparen.
| Typ | Größe in bytes | Bereich |
| ---------- | -------------- | -------------------------------------------------------- |
| **byte** | 1 | 0 bis 255 |
| **short** | 4 | -2.147.483.647 bis 2.147.483.647 |
| **int** | 8 | -9.223.372.036.854.775.807 bis 9.223.372.036.854.775.807 |
| **float** | 4 | -3,4e38 bis 3,4e38 |
| **double** | 8 | -1,7e308 bis 1,7e308 |
Die zusätzlichen Zahl-Typen sind nur Aliase für die nativen Dart-Typen, also beispielsweise `short` zu benutzen funktioniert genau, wie wenn du `int` nutzen würdest.
Hier ist eine Beispiel-Collection, welche alle der eben genannten Typen enthält:
```dart
@collection
class TestCollection {
Id? id;
late byte byteValue;
short? shortValue;
int? intValue;
float? floatValue;
double? doubleValue;
}
```
Alle Zahlen-Typen können auch in Listen verwendet werden. Um Bytes zu speichern solltest du `List` benutzen.
## Null-bare Typen
Zu verstehen wie Null-barkeit in Isar funktioniert ist essentiell: Zahlen-Typen haben **KEINE** gemeinsame festgelegte `null`-Darstellung. Stattdessen wird ein bestimmter Wert genutzt:
| Typ | VM |
| ---------- | ------------- |
| **short** | `-2147483648` |
| **int** | `int.MIN` |
| **float** | `double.NaN` |
| **double** | `double.NaN` |
`bool`, `String`, und `List` haben eine seperate `null`-Darstellung.
Dieses Verhalten erlaubt Leistungsverbesserungen, und ermöglicht es die Null-barkeit deiner Felder frei zu ändern, ohne eine Migration oder speziellen Code zum handhaben von `null`-Werten zu benötigen.
:::warning
Der `byte`-Typ unterstützt keine Null-Werte.
:::
## DateTime
Isar speichert keine Zeitzoneninformationen von deinen Daten. Stattdessen wandelt es `DateTime`s zu UTC um, bervor es diese speichert. Isar gibt jedes Datum in lokaler Zeit zurück.
`DateTime`s werden mit Mikrosekunden-Präzision gespeichert. In Browsern ist, aufgrund von JavaScript-Limitationen, nur Millisekunden-Präzision möglich.
## Enum
Isar ermöglicht es Enums wie andere Isar-Typen zu nutzen und zu speichern. Du musst aber wählen, wie Isar den Enum auf dem Datenträger abbilden soll. Isar unterstützt vier verschiedene Strategien:
| Enum-Typ | Beschreibung |
| ------------ | -------------------------------------------------------------------------------------------------------------- |
| `ordinal` | Der Index des Enums wird als `byte` gespeichert. Das ist sehr effizienzt, aber erlaubt keine Null-baren Enums. |
| `ordinal32` | Der Index des Enums wird als `short` (4-Byte-Integer) gespeichert. Erlaubt keine Null-baren Enums. |
| `name` | Der Name des Enums wird als `String` gespeichert. |
| `value` | Eine angepasste Eigenschaft wird genutzt, um den Enum-Wert abzurufen. |
:::warning
`ordinal` und `ordinal32` basieren auf der Reihenfolge der Enum-Werte. Wenn du die Reihenfolge änderst, werden existierende Datenbanken falsche Werte zurückgeben.
:::
Schauen wir uns ein Beispiel für jede Strategie an.
```dart
@collection
class EnumCollection {
Id? id;
@enumerated // entspricht EnumType.ordinal
late TestEnum byteIndex; // ist nicht Null-bar
@Enumerated(EnumType.ordinal)
late TestEnum byteIndex2; // ist nicht Null-bar
@Enumerated(EnumType.ordinal32)
TestEnum? shortIndex;
@Enumerated(EnumType.name)
TestEnum? name;
@Enumerated(EnumType.value, 'myValue')
TestEnum? myValue;
}
enum TestEnum {
first(10),
second(100),
third(1000);
const TestEnum(this.myValue);
final short myValue;
}
```
Natürlich können Enums auch in Listen benutzt werden.
## Eingebettete Objekte
Es ist oft hilfreich verschachtelte Objekte in deinem Collection-Modell zu haben. Daher gibt es keine Begrenzung, wie tief die Verschachtelung von Objekten sein kann. Beachte jedoch, dass der gesamte Objekt-Baum in die Datenbank geschrieben werden muss, um ein sehr tief verschachteltes Objekt zu aktualisieren.
```dart
@collection
class Email {
Id? id;
String? title;
Recepient? recipient;
}
@embedded
class Recepient {
String? name;
String? address;
}
```
Eingebettete Objekte können Null-bar sein und andere Objekte erweitern. Die einzige Voraussetzung ist, dass sie mit `@embedded` annotiert werden und einen Standardkonstruktor ohne erforderliche Parameter haben.
================================================
FILE: docs/docs/de/transactions.md
================================================
---
title: Transaktionen
---
# Transaktionen
In Isar verbinden Transaktionen mehrere Datenbankoperationen in einen einzigen Arbeitsvorgang. Die meisten Interaktionen mit Isar nutzen implizit Transaktionen. Lese- & Schreibzugriff ist in Isar [ACID](https://de.wikipedia.org/wiki/ACID)-konform. Transaktionen werden automatisch zurückgesetzt, wenn ein Fehler auftritt.
## Explizite Transaktionen
In einer expliziten Transaktion kannst du einen konsistenten Schnappschuss der Datenbank erhalten. Versuche die Dauer einer Transaktion zu minimieren. Es ist verboten Netzwerkabfragen oder andere lang andauernde Operationen in einer Transaktion zu machen.
Transaktionen (besonders Schreib-Transaktionen) sind sehr teuer. Du solltest immer versuchen aufeinander folgende Operationen in eine einzelne Transaktion zu vereinen.
Transaktionen können entweder synchron oder asynchron sein. In synchronen Transaktionen kannst du nur synchrone Operationen verwenden. In asynchronen Transaktionen sind nur asynchrone Operationen möglich.
| | Lesen | Lesen & Schreiben |
| --------- | ------------ | ----------------- |
| Synchron | `.txnSync()` | `.writeTxnSync()` |
| Asynchron | `.txn()` | `.writeTxn()` |
### Lese-Transaktionen
Explizite Lese-Transaktionen sind optional, aber sie erlauben es atomare Lesevorgänge durchzuführen und auf einem konsistenten Status der Datenbank innerhalb der Transaktion zu arbeiten. Intern nutzt Isar für alle Lese-Operationen immer Lese-Transaktionen.
:::tip
Asynchrone Lese-Transaktionen laufen parallel zu anderen Lese- und Schreib-Transaktionen. Ziemlich cool, oder?
:::
### Schreib-Transaktionen
Anders als Lese-Operationen müssen Schreib-Operationen in Isar in einer expliziten Transaktion ausgeführt werden.
Wenn eine Schreib-Transaktion erfolgreich beendet wird, wird sie automatisch festgesetzt und alle Änderungen werden auf den Datenträger geschrieben. Wenn ein Fehler auftritt, wird die Transaktion abgebrochen und alle Änderungen werden zurückgesetzt. Transaktionen sind „Alles oder Nichts”: Entweder sind alle Schreibvorgänge in der Transaktion erfolgreich oder keine von ihnen findet statt. Somit ist sichergestellt, dass die Daten konsistent sind.
:::warning
Wenn eine Datenbankoperation fehlschlägt, wird die Transaktion abgeborchen und darf nicht mehr verwendet werden, auch wenn der Fehler in Dart aufgefangen wird.
:::
```dart
@collection
class Contact {
Id? id;
String? name;
}
// GUT
await isar.writeTxn(() async {
for (var contact in getContacts()) {
await isar.contacts.put(contact);
}
});
// SCHLECHT: Bewege die Schleife in die Transaktion
for (var contact in getContacts()) {
await isar.writeTxn(() async {
await isar.contacts.put(contact);
});
}
```
================================================
FILE: docs/docs/de/tutorials/quickstart.md
================================================
---
title: Schnellstart
---
# Schnellstart
Holla, die Waldfee! Du bist bestimmt hier um mit der coolsten Flutter-Datenbank zu starten...
Dieser Schnellstart wird wenig um den heißen Brei herumreden und direkt mit dem Coden beginnen.
## 1. Abhängigkeiten hinzufügen
Bevor es losgeht, müssen wir ein paar Pakete zur `pubspec.yaml` hinzufügen. Damit es schneller geht lassen wir pub das für uns erledigen.
```bash
dart pub add isar:^0.0.0-placeholder isar_flutter_libs:^0.0.0-placeholder --hosted-url=https://pub.isar-community.dev
dart pub add dev:isar_generator:^0.0.0-placeholder --hosted-url=https://pub.isar-community.dev
```
## 2. Klassen annotieren
Annotiere deine Collection-Klassen mit `@collection` und wähle ein `Id`-Feld.
```dart
part 'user.g.dart';
@collection
class User {
Id id = Isar.autoIncrement; // Für auto-increment kannst du auch id = null zuweisen
String? name;
int? age;
}
```
IDs identifizieren Objekte in einer Collection eindeutig und erlauben es dir, sie später wiederzufinden.
## 3. Code-Generator ausführen
Führe den folgenden Befehl aus, um den `build_runner` zu starten:
```
dart run build_runner build
```
Wenn du Flutter verwendest:
```
flutter pub run build_runner build
```
## 4. Isar-Instanz öffnen
Öffne eine neue Isar-Instanz und übergebe alle Collection-Schemata. Optional kannst du einen Instanznamen und ein Verzeichnis angeben.
```dart
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[UserSchema],
directory: dir.path,
);
```
## 5. Schreiben und lesen
Wenn deine Instanz geöffnet ist, hast du Zugriff auf die Collections.
Alle grundlegenden CRUD-Operationen sind über die `IsarCollection` verfügbar .
```dart
final newUser = User()..name = 'Jane Doe'..age = 36;
await isar.writeTxn(() async {
await isar.users.put(newUser); // Einfügen & akualisieren
});
final existingUser = await isar.users.get(newUser.id); // Erhalten
await isar.writeTxn(() async {
await isar.users.delete(existingUser.id!); // Löschen
});
```
## Weitere Ressourcen
Du lernst am besten visuell? Schau dir diese Videos an, um mit Isar zu starten:
================================================
FILE: docs/docs/de/watchers.md
================================================
---
title: Watcher
---
# Watcher
Isar ermöglicht es dir zu Änderungen in der Datenbank zu abbonieren. Du kannst Änderungen in einem Objekt, einer ganzen Collection oder einer Abfrage "beobachten".
Watcher erlauben es dir auf Änderungen in der Datenbank effizient zu reagieren. Du kannst z.B. dein UI neuladen, wenn ein Kontakt hinzugefügt wurde, eine Netzwerkabfrage machen, wenn ein Dokument aktualisiert wurde, etc.
Ein Watcher wird benachrichtigt, wenn eine Transaktion efolgreich stattfindet, und das Ziel sich wirklich ändert.
## Objekte beobachten
Wenn du benachrichtigt werden möchtest, wenn ein spezifisches Objekt erstellt, aktualisiert oder gelöscht wird, solltest du ein Objekt beobachten:
```dart
Stream userChanged = isar.users.watchObject(5);
userChanged.listen((newUser) {
print('User changed: ${newUser?.name}');
});
final user = User(id: 5)..name = 'David';
await isar.users.put(user);
// Ausgabe: User changed: David
final user2 = User(id: 5)..name = 'Mark';
await isar.users.put(user);
// Ausgabe: User changed: Mark
await isar.users.delete(5);
// Ausgabe: User changed: null
```
Wie du im eben gezeigten Beispiel sehen kannst, muss das Objekt noch nicht existieren. Der Watcher wird benachrichtigt, wenn es erzeugt wird.
Es gibt den zusätzlichen Parameter `fireImmediately`. Wenn du ihn auf `true` gesetzt hast, wird Isar sofort die Werte des aktuellen Objekts in den Stream geben.
### Lazy Beobachtung
Vielleicht möchtest du gar nicht den neuen Wert erhalten, sondern nur über die Änderungen informiert werden. Das erspart es Isar die Objekte abrufen zu müssen:
```dart
Stream userChanged = isar.users.watchObjectLazy(5);
userChanged.listen(() {
print('User 5 changed');
});
final user = User(id: 5)..name = 'David';
await isar.users.put(user);
// Ausgabe: User 5 changed
```
## Collections beobachten
Statt ein einzelnes Objekt zu beobachten kannst du auch eine ganze Collection beobachten und benachrichtigt werden, wenn irgendein Objekt hinzugefügt, geändert oder gelöscht wird:
```dart
Stream userChanged = isar.users.watchLazy();
userChanged.listen(() {
print('A User changed');
});
final user = User()..name = 'David';
await isar.users.put(user);
// Ausgabe: A User changed
```
## Abfragen beobachten
Es ist sogar möglich ganze Abfragen zu beobachten. Isar versucht sein Bestes dich nur zu benachrichtigen, wenn das Abfrageergebnis sich wirklich ändert. Du wirst nicht informiert, wenn Links darin resultieren, dass deine Abfrageergebnisse sich ändern. Benutze einen Collection-Watcher, wenn du über Linkänderungen benachrichtigt werden willst.
```dart
Query usersWithA = isar.users.filter()
.nameStartsWith('A')
.build();
Stream> queryChanged = usersWithA.watch(fireImmediately: true);
queryChanged.listen((users) {
print('Users with A are: $users');
});
// Ausgabe: Users with A are: []
await isar.users.put(User()..name = 'Albert');
// Ausgabe: Users with A are: [User(name: Albert)]
await isar.users.put(User()..name = 'Monika');
// keine Ausgabe
await isar.users.put(User()..name = 'Antonia');
// Ausgabe: Users with A are: [User(name: Albert), User(name: Antonia)]
```
:::warning
Wenn du einen Offset mit Limitierung oder Eindeutigkeitsabfragen benutzt, wird Isar dich auch informieren, wenn Ergebnisse innerhalb der Abfrage, aber außerhalb der Abfragegrenzen stattfinden.
:::
Genau wie bei `watchObject()` kannst du `watchLazy()` verwenden, um über Änderungen in den Abfrageergebnissen benachrichtigt zu werden, ohne die Ergebnisse zu erhalten.
:::danger
Abfragen für jede Änderung erneut ablaufen zu lassen ist sehr ineffizient. Es wäre besser, wenn du stattdessen einen lazy Collection-Watcher verwenden würdest.
:::
================================================
FILE: docs/docs/es/README.md
================================================
---
home: true
title: Home
heroImage: /isar.svg
actions:
- text: Empecemos!
link: /es/tutorials/quickstart.html
type: primary
features:
- title: 💙 Hecho para Flutter
details: Mínima inicialización, fácil de usar, sin configuración, sin repetición. Solo agrega algunas líneas de código para comenzar.
- title: 🚀 Altamente escalable
details: Almacena cientos de miles de registros en una sola base de datos NoSQL y consúltalos de forma eficiente y asíncrona.
- title: 🍭 Múltiples características
details: Isar posee una gran cantidad de características para ayudarte a administrar tus datos. Índices compuestos y multi-entrada, modificadores de consultas, soporte para JSON, y mucho más.
- title: 🔎 Búsqueda de texto completo
details: Isar tiene incorporado un buscador de texto completo. Crea un índice multi-entrada y busca texto de forma fácil.
- title: 🧪 Semántica ACID
details: Isar es compatible con ACID y maneja las transacciones automáticamente. Retrocede los cambios en caso de error.
- title: 💃 Tipeado estático
details: Las consultas de Isar son tipeadas estáticamente y verificadas en tiempo de compilación. No hay necesidad de preocuparse por errores en tiempo de ejecución.
- title: 📱 Multiplataforma
details: Soporte completo para iOS, Android, Desktop, WEB!
- title: ⏱ Asíncrona
details: Isar incluye operaciones de consulta en paralelo y soporte multi-isolate.
- title: 🦄 Código abierto
details: Completamente de código abierto y libre para siempre!
footer: Apache Licensed | Copyright © 2022 Simon Leier
---
================================================
FILE: docs/docs/es/crud.md
================================================
---
title: Crear, Leer, Actualizar, Eliminar
---
# Crear, Leer, Actualizar, Eliminar (CRUD)
Cuando ya has definido tus colecciones, aprende cómo manipularlas!
## Abriendo Isar
Antes de hacer nada, necesitamos una instancia Isar. Cada instancia requiere un directorio con permisos de escritura donde el archivo de la base de datos pueda ser almacenado. Si no defines un directorio, Isar encontrará un directorio por defecto apropiado para la plataforma en uso.
Provee todos los esquemas que quieras usar con la instancia Isar. Si abres múltiples instancias, aún tienes que proveer todos los esquemas a cada instancia.
```dart
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[ContactSchema],
directory: dir.path,
);
```
Puedes usar la configuración por defecto o proveer algunos de los siguientes parámetros:
| Configuración | Descripción |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | Abre múltiples instancias con distinto nombre. `"default"` es el nomre usado por defecto. |
| `directory` | La ubicación de almacenamiento para esta instancia. Puedes usar una ruta relativa o absoluta. `NSDocumentDirectory` para iOS y `getDataDirectory` para Android son los usados por defecto. No se requiere para web. |
| `relaxedDurability` | Relaja la garantía de durabilidad para incrementar el rendimiento de escritura. En caso de falla del sistema (no de la aplicación), es posible perder la última transacción ejecutada. La corrupción de los datos no es posible |
| `compactOnLaunch` | Condiciones a verificar cuando la base de datos deba ser compactada cuando se abra la instancia. |
| `inspector` | Habilita el inspector para las compilaciones de depuración. Esta opción se ignora para las compilaciones de perfil y entrega. |
Si existiera una instancia ya abierta al momento de llamar a `Isar.open()`, ésta retornará la instancia existente independientemente de los parámetros especificados. Ésto es útil para usar Isar en un isolate.
:::tip
Considera usar el paquete [path_provider](https://pub.dev/packages/path_provider) para obtener una ruta válida en todas las plataformas.
:::
La ubicación de almacenamiento del archivo de la base de datos Isar es `directory/name.isar`
## Leyendo la base de datos
Usa instancias `IsarCollection` para buscar, consultar y crear nuevos objetos de un tipo dado en Isar.
Para los ejemplos siguientes, asumimos que tenemos una colección `Recipe` definida como sigue:
```dart
@collection
class Recipe {
Id? id;
String? name;
DateTime? lastCooked;
bool? isFavorite;
}
```
### Obtener una colección
Todas tus colecciones viven en la instancia Isar. Puedes obtener tu colección Recipes con:
```dart
final recipes = isar.recipes;
```
Eso fue fácil! Si no quieres usar los accesores de la colección, puedes usar el método `collection()`:
```dart
final recipes = isar.collection();
```
### Obtener un objeto (por su id)
Todavía no tenemos datos en la colección, pero pretendamos que tenemos así podemos obtener un objeto imaginario dado su id `123`
```dart
final recipe = await recipes.get(123);
```
`get()` retorna un `Future` con el objeto o `null` si éste no existe. Por defecto todas las operaciones Isar son asíncronas, y la mayoría de ellas tienen su versión síncrona:
```dart
final recipe = recipes.getSync(123);
```
:::warning
En tus isolate de UI, por defecto deberías usar los métodos en su versión asíncrona. Debido a que Isar es súper rápido, a menudo es aceptable usar la versión síncrona.
:::
Si quieres obtener múltiples objetos de una vez, utiliza `getAll()` o `getAllSync()`:
```dart
final recipe = await recipes.getAll([1, 2]);
```
### Consulta de objectos
En lugar de obtener objetos por su id, puedes también consultar una lista objetos que coincidan con ciertas condiciones usando `.where()` y `.filter()`:
```dart
final allRecipes = await recipes.where().findAll();
final favouires = await recipes.filter()
.isFavoriteEqualTo(true)
.findAll();
```
➡️ Ver más en: [Consultas](queries)
## Modificando los datos
Finalmente es momento de modificar los datos en nuestra colección! Para crear, actualizar o eliminar objectos, usa las respectivas operaciones juntas en una transacción de escritura:
```dart
await isar.writeTxn(() async {
final recipe = await recipes.get(123)
recipe.isFavorite = false;
await recipes.put(recipe); // perform update operations
await recipes.delete(123); // or delete operations
});
```
➡️ Ver más en: [Transacciones](transactions)
### Insertar objectos
Para almacenar un objeto en Isar, insértalo en una colección. El método `put()` de Isar insertará o actualizará el objecto dependiendo si el mismo ya existe o no en la colección.
Si el campo id es `null` o `Isar.autoIncrement`, Isar usará un id auto incrementable.
```dart
final pancakes = Recipe()
..name = 'Pancakes'
..lastCooked = DateTime.now()
..isFavorite = true;
await isar.writeTxn(() async {
await recipes.put(pancakes);
})
```
Isar asignará automáticamente el id al objeto si el campo id es no-final.
Insertar múltiples objetos de una sola vez es muy fácil:
```dart
await isar.writeTxn(() async {
await recipes.putAll([pancakes, pizza]);
})
```
### Actualizar objectos
Crear y actualizar objetos funcionan ambos con `collection.put(object)`. Si el id es `null` (o no existe), el object se crea; de otra manera será actualizado.
Entonces si queremos quitar los pancakes de los favoritos, podemos hacer los siguiente:
```dart
await isar.writeTxn(() async {
pancakes.isFavorite = false;
await recipes.put(recipe);
});
```
### Eliminar objetos
Quieres eliminar un objeto en Isar? Usa `collection.delete(id)`. El método delete retorna verdadero si el objeto con el id especificado fue encontrado y eliminado. Por ejemplo, si quieres eliminar el objeto con el id `123`, puedes hacer:
```dart
await isar.writeTxn(() async {
final success = await recipes.delete(123);
print('Recipe deleted: $success');
});
```
De manera similar a get y put, también existe una operación para eliminar múltiples objetos de una vez que retorna la cantidad de objetos eliminados:
```dart
await isar.writeTxn(() async {
final count = await recipes.deleteAll([1, 2, 3]);
print('We deleted $count recipes');
});
```
Si no conoces los ids de los objetos que quieres eliminar, puedes utilizar una consulta:
```dart
await isar.writeTxn(() async {
final count = await recipes.filter()
.isFavoriteEqualTo(false)
.deleteAll();
print('We deleted $count recipes');
});
```
================================================
FILE: docs/docs/es/faq.md
================================================
---
title: FAQ
---
# Preguntas frecuentes
Una colección aleatoria de preguntas frecuentes sobre Isar y bases de datos en Flutter.
### Porqué necesito una base de datos?
> Estoy almacenando mis datos es una base datos en mi backend, porqué necesito Isar?.
Incluso hoy en día, es muy común no tener conexión a internet si estás en el subterráneo o en un avión o si visitaste a tu abuela, que no tiene WiFi y muy mala señal de celular. No deberías dejar que la mala conexión afectua a tu aplicación!
### Isar versus Hive
La respuesta es fácil: Isar [inició como un reemplazo para Hive](https://github.com/hivedb/hive/issues/246) y está en un estado de madurez tal que se recomienda siempre usar Isar en lugar de Hive.
### Cláusulas `where`?!
> Porqué **_YO_** tengo que elejir qué índice usar?
Existen muchas razones. Muchas base de datos utilizan heurística para elegir el mejor índice para una determinada consulta. La base de datos necesita recolectar datos de uso adicionales (-> overhead) y aún así podría elegir un índice incorrecto. Además crear la consulta es más lento.
Nadie conoce tus datos mejor que tú, el desarrollador. Entonces tú puedes elegir el índice óptimo y decidir por ejemplo si quieres usar un índice para consultas u ordenamiento.
### Tengo que usar índices / cláusulas `where`?
No! Isar es lo suficientemente rápida si solo quieres confiar en filtros.
### Isar es lo suficientemente rápida?
Isar está entre las bases de datos más rápidas para dispositivos móbiles, por lo que debería ser lo suficientemente rápida para las mayoría de los casos de uso. Si tienes problemas de rendimiento, hay posibilidades que estés haciendo algo mal.
### Isar incrementa el tamaño de mi aplicación?
Un poco, sí. Isar incrementará el tamaño de descarga de tu aplicaicón alrededor de 1 - 1.5 MB. Isar Web agrega solo algunos KB.
### La documentación es incorrecta / hay un error de ortografía.
Oh no, lo siento. Por favor [apunta el problema](https://github.com/isar-community/isar/issues/new/choose) o, mejor aún, un PR para solucionarlo! 💪.
================================================
FILE: docs/docs/es/indexes.md
================================================
---
title: Índices
---
# Índices
Los índices son la característica más poderosa de Isar. Muchas bases de datos embebidas ofrecen índices "normales" (o nada), pero Isar también tiene índices compuestos y multi-entrada. Entender cómo funcionan los índices es esencial para optimizar el rendimiento de las consultas. Isar te permite elegir qué índice quieres usar y cómo quieres usarlo. Comenzaremos con un inicio rápido sobre qué son los índices.
## Qué son los índices?
Cuando una colección no está indexada, el orden de las filas no será identificable por la consulta como optimizada en ninguna forma, y tu consulta tendrá que buscar entonces a través de todos los objectos de forma lineal. En otras palabras, la consulta deberá buscar a través de cada objeto para encontrar los que coincidan con las condiciones. Como puedes imaginarte, eso puede tardar mucho. Buscar a través de cada objeto no es muy eficiente.
Por ejemplo, esta colección `Product` está completamente desordenada.
```dart
@collection
class Product {
Id? id;
late String name;
late int price;
}
```
**Datos:**
| id | name | price |
| --- | --------- | ----- |
| 1 | Book | 15 |
| 2 | Table | 55 |
| 3 | Chair | 25 |
| 4 | Pencil | 3 |
| 5 | Lightbulb | 12 |
| 6 | Carpet | 60 |
| 7 | Pillow | 30 |
| 8 | Computer | 650 |
| 9 | Soap | 2 |
Una consulta que intente buscar todos los productos que cuestan más de $30 tiene que buscar a través de todas las nueve filas. No es un problema para nueve filas, pero podría ser un problema para 100k filas.
```dart
final expensiveProducts = await isar.products.filter()
.priceGreaterThan(30)
.findAll();
```
Para mejorar el rendimiento de esta consulta, indexamos la propiedad `price`. Un índice es como una tabla de búsqueda ordenada:
```dart
@collection
class Product {
Id? id;
late String name;
@Index()
late int price;
}
```
**Índices generados:**
| price | id |
| -------------------- | ------------------ |
| 2 | 9 |
| 3 | 4 |
| 12 | 5 |
| 15 | 1 |
| 25 | 3 |
| 30 | 7 |
| **55** | **2** |
| **60** | **6** |
| **650** | **8** |
Ahora, la ejecución de la consulta puede ser considerablemente más rápida. El ejecutor puede saltar directamente a los últimos 3 índices y buscar los objetos correspondientes por su id.
### Ordenando
Otra cosa genial: los índices permiten ordenar súper rápido. Las consultas ordenadas son costosas porque la base de datos tiene que cargar todos los resultados en memoria antes de ordenarlos. Incluso si especificaste un offset y un límite, éstos se aplican después de ordenar.
Imaginemos que queremos encontrar los cuatro productos más baratos. Podríamos usar la siguiente consulta:
```dart
final cheapest = await isar.products.filter()
.sortByPrice()
.limit(4)
.findAll();
```
En este ejemplo, la base de datos tendría que cargar todos los objetos (!), ordenarlos por precio, y retornar los 4 productos con el menor precio.
Como puedes imaginar, ésto puede hacerse mucho más eficiente usando el índice anterior. La base de datos toma las cuatro primeras filas del índice y retorna los objetos correspondientes ya que éstos ya están en el orden correcto.
Para usar el índice para ordenar, escribiríamos la consulta como sigue:
```dart
final cheapestFast = await isar.products.where()
.anyPrice()
.limit(4)
.findAll();
```
La cláusula `where` `.anyX()` le dice a Isar que use un ídice sólo para ordenar. También puedes usar una cláusula `where` como `.priceGreaterThan()` y obtener los resultados ordenados.
## Índices únicos
Un índice único asegura que el índice no contiene valores duplicados. Puede consistir en una o múltiples propiedades. Si un índice único tiene una propiedad, los valores en esta propiedad serán únicos. Si el índice único tiene más de una pro[iedad, la combinación de los valores en estas propiedades es única.
```dart
@collection
class User {
Id? id;
@Index(unique: true)
late String username;
late int age;
}
```
Cualquier intento de insertar o actualizar datos en un índice único que provoque un duplicado resultará en un error:
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1); // -> ok
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
// try to insert user with same username
await isar.users.put(user2); // -> error: unique constraint violated
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
```
## Índices con reemplazo
A veces no es deseable arrojar un error si una condición de único es violada. En su lugar, podrías querer reemplazar el objeto existente con el nuevo. Ésto se puede lograr estableciendo la propiedad `replace` del índice a `true`.
```dart
@collection
class User {
Id? id;
@Index(unique: true, replace: true)
late String username;
}
```
Ahora cuando querramos insertar un usuario con nombre de usuario existente, Isar reemplazará el usuario existente con el nuevo.
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1);
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
print(await isar.user.where().findAll());
// > [{id: 2, username: 'user1' age: 30}]
```
Los índices con reemplazo también generan métodos `putBy()` que permiten actualizar los objetos en lugar de reemplazarlos. El id existente es reusado, **_and links are still populated_**.
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
// user does not exist so this is the same as put()
await isar.users.putByUsername(user1);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1' age: 30}]
```
Como puedes ver, el id del primer usuario insertado es reusado.
## Índices mayúsculas-minúsculas
Todos los índices en las propiedades `String` y `List` por defecto distinguen entre mayúsculas y minúsculas. Si quieres que tu índice no haga esta distinción, puedes usar la opción `caseSensitive`:
```dart
@collection
class Person {
Id? id;
@Index(caseSensitive: false)
late String name;
@Index(caseSensitive: false)
late List tags;
}
```
## Tipos de índices
Existen diferentes tipos de índices. La mayoría del tiempo, querrás usar un índice tipo `IndexType.value`, pero los índices hash son más eficientes.
### Índice valor
El índice valor es el tipo por defecto y el único posible para todas las propiedades que no sean se tipo String o List. Para construir el índice se utilizan los valores de las propiedades. En el caso de las listas, se utilizan sus elementos. De los tres tipos de índices disponibles, es el más flexible como así también el que más espacio utiliza.
:::tip
Usa `IndexType.value` para primitivas, Strings donde necesites una cláusula `startsWith()`, y listas si quieres buscar por elementos individuales.
:::
### Índice hash
Los strings y las listas pueden reducirse para disminuir significativamente el espacio en disco que requiere el índice. La desventaja es que no puede usarse para búsqueda por prefijo (cláusulas `startsWith`).
:::tip
Usa `IndexType.hash` para strings y listas si no necesitas utilizar cláusulas `startsWith` ni `elementEqualTo`.
:::
### Índice hashElements
Las listas de string pueden reducirse como un todo (usando `IndexType.hash`), o los elementos de la lista pueden reducirse individualmente (usando `IndexType.hashElements`), creando un índice multi-entrada con los elementos reducidos.
:::tip
Usa `IndexType.hashElements` para `List` sin nevesitas aplicar cláusulas `elementEqualTo`.
:::
## Índices compuestos
Un índice compuesto es un índice con múltiples propiedades. Isar te permite crear índices compuestos de hasta tres propiedades.
Los índices compuestos también son conocidos como índices multi-columna.
Probablemente sea mejor comenzar con un ejemplo. Creamos una colleción person y definimos un índice compuesto en las propiedades age y name:
```dart
@collection
class Person {
Id? id;
late String name;
@Index(composite: [CompositeIndex('name')])
late int age;
late String hometown;
}
```
**Datos:**
| id | name | age | hometown |
| --- | ------ | --- | --------- |
| 1 | Daniel | 20 | Berlin |
| 2 | Anne | 20 | Paris |
| 3 | Carl | 24 | San Diego |
| 4 | Simon | 24 | Munich |
| 5 | David | 20 | New York |
| 6 | Carl | 24 | London |
| 7 | Audrey | 30 | Prague |
| 8 | Anne | 24 | Paris |
**Índice generado:**
| age | name | id |
| --- | ------ | --- |
| 20 | Anne | 2 |
| 20 | Daniel | 1 |
| 20 | David | 5 |
| 24 | Anne | 8 |
| 24 | Carl | 3 |
| 24 | Carl | 6 |
| 24 | Simon | 4 |
| 30 | Audrey | 7 |
El índice compuesto generado contiene a todas las personas ordenadas por su edad y su nombre.
Los índices compuestos son geniales si necesitas crear consultas eficientes ordenadas por propiedades múltiples. También te pemiten utilizar cláusulas `where` avanzadas:
```dart
final result = await isar.where()
.ageNameEqualTo(24, 'Carl')
.hometownProperty()
.findAll() // -> ['San Diego', 'London']
```
La última propiedad del índice compuesto también soporta condiciones como `startsWith()` o `lessThan()`:
```dart
final result = await isar.where()
.ageEqualToNameStartsWith(20, 'Da')
.findAll() // -> [Daniel, David]
```
## Índices multi-entrada
Si indexas una lista usando `IndexType.value`, Isar automáticamente creará un índice multi-entrada, y cada elemento en la lista será indexado hacia el objeto, Funciona para cualquier tipo de lista.
Aplicaciones prácticas del uso de índices multi-entrada incluyen indexar una lista de etiquetas o crear un índice de texto completo.
```dart
@collection
class Product {
Id? id;
late String description;
@Index(type: IndexType.value, caseSensitive: false)
List get descriptionWords => Isar.splitWords(description);
}
```
`Isar.splitWords()` divide la cadena en palabras de acuerdo con la especificación [Unicode Annex #29](https://unicode.org/reports/tr29/), por lo tanto funciona correctamente para cualquier idioma.
**Data:**
| id | description | descriptionWords |
| --- | ---------------------------- | ---------------------------- |
| 1 | comfortable blue t-shirt | [comfortable, blue, t-shirt] |
| 2 | comfortable, red pullover!!! | [comfortable, red, pullover] |
| 3 | plain red t-shirt | [plain, red, t-shirt] |
| 4 | red necktie (super red) | [red, necktie, super, red] |
Entradas con palabras duplicadas paraecen sólo una vez en el índice.
**Índice generado:**
| descriptionWords | id |
| ---------------- | --------- |
| comfortable | [1, 2] |
| blue | 1 |
| necktie | 4 |
| plain | 3 |
| pullover | 2 |
| red | [2, 3, 4] |
| super | 4 |
| t-shirt | [1, 3] |
Este índice ahora puede usarse para cláusulas por prefijo (o igualdad) de las palabras individuales de la descripción.
:::tip
En lugar de guardar las palabaras directamente, considera usar los resultados de un [algoritmo de fonética](https://en.wikipedia.org/wiki/Phonetic_algorithm) como [Soundex](https://es.wikipedia.org/wiki/Soundex).
:::
================================================
FILE: docs/docs/es/limitations.md
================================================
# Limitaciones
Como ya sabes, Isar funciona en dispositivos móbiles y de escritorio corriendo en la VM así como en la web. Ambas plataformas son muy diferentes y tienen distintas limitaciones.
## Limitaciones de la VM
- Para consultas `where` de prefijo sólo se pueden usar los primeros 1024 bytes
- Los objetos pueden ser de 16MB en tamaño como máximo
## Limitaciones de la Web
Dado que Isar Web confía en IndexedDB, hay más limitaciones pero apenas son notadas mientras se usa Isar.
- No hay soporte para métodos síncronos
- Actualmente, los filtros `Isar.splitWords()` y `.matches()` aún no están implementados
- Los cambios en los esquemas no son estrechamente verificados como en la VM entonces sé cuidadoso de cumplir con las reglas
- Todos los tipos numéricos se almacenan como `double` (el único tipo numérico de js) por lo tanto `@Size32` no tiene efecto
- Lo índices se representan de forma diferente entonces los índices hash no usan menos espacio (pero funcionan de la misma manera)
- `col.delete()` y `col.deleteAll()` funcionan correctamente pero el valor retornado es incorrecto
- `col.clear()` no resetea el valor de auto incrementado
- `NaN` no está soportado como valor
================================================
FILE: docs/docs/es/links.md
================================================
---
title: Enlaces
---
# Enlaces
Los enlaces permiten establecer relaciones entre objetos, como ser el autor de un comentario (User). Con los enlaces de Isar, se pueden modelar relaciones `1:1`, `1:n`, y `n:n`. Usar enlaces es menos ergonómico que usar objetos embebidos y se deberían usar los últimos siempre que sea posible.
Piensa en el enlace como una tabla separada que contiene la relación. Es similar a las relaciones de SQL pero tiene una API y características diferentes.
## IsarLink
`IsarLink` puede contener uno o nigún objeto relacionado, y puede ser usado para expresar una relación a uno. `IsarLink` tiene una sola propiedad llamada `value` que contiene el objeto enlazado.
Los enlaces son perezosos, entonces tienes que decirle explícitamente al `IsarLink` que cargue o guarde el valor `value`. Puedes hacer esto llamando a `linkProperty.load()` y `linkProperty.save()` respectivamente.
:::tip
La propiedad id de las colecciones de origen y destino de un enlace deberían ser no final.
:::
En las plataformas no web, los enlaces se cargan automáticamente cuando los usas por primera vez. Comencemos agregando un IsarLink a la colección:
```dart
@collection
class Teacher {
Id? id;
late String subject;
}
@collection
class Student {
Id? id;
late String name;
final teacher = IsarLink();
}
```
Definimos un enlace entre maestros y estudiante. En este ejemplo, cada estudiante puede tener exactamente un maestro.
Primero, creamos el maestro y lo asignamos a un estudiante. Tendremos que insertar el maestro y guardar el enlace manualmente.
```dart
final mathTeacher = Teacher()..subject = 'Math';
final linda = Student()
..name = 'Linda'
..teacher.value = mathTeacher;
await isar.writeTxn(() async {
await isar.students.put(linda);
await isar.teachers.put(mathTeacher);
await linda.teacher.save();
});
```
Ahora podemos usar el enlace:
```dart
final linda = await isar.students.where().nameEqualTo('Linda').findFirst();
final teacher = linda.teacher.value; // > Teacher(subject: 'Math')
```
Probemos hacer los mismo usando código síncrono. No necesitamos guardar el enlace porque `.putSync()` guarda todos los enlaces automáticamente. Incluso crea el maestro por nosotros.
```dart
final englishTeacher = Teacher()..subject = 'English';
final david = Student()
..name = 'David'
..teacher.value = englishTeacher;
isar.writeTxnSync(() {
isar.students.putSync(david);
});
```
## IsarLinks
Tendría más sentido si un estudiante del ejemplo anterior pudiera tener más de un maestro. Afortunadamente, Isar tiene `IsarLinks`, que pueden tener múltiples objetos relacionados y expresar relaciones `to-many`.
`IsarLinks` extiende `Set` y expone todos los métodos que están permitidos para los sets.
El comportamiento de `IsarLinks` es similar a `IsarLink` y también es perezoso. Para cargar todos los objetos enlazados se debe llamar a `linkProperty.load()`. Para guardar los cambios, llama a `linkProperty.save()`.
Internamente ambos `IsarLink` y `IsarLinks` se representan de la misma forma. Podemos actualizar el `IsarLink` anterior a un `IsarLinks` para asignar múltiples maestros a un estudiante (sin perder datos).
```dart
@collection
class Student {
Id? id;
late String name;
final teachers = IsarLinks();
}
```
Esto funciona porque no cambiamos el nombre del enlace (`teacher`), entonces Isar lo recuerda de antes.
```dart
final biologyTeacher = Teacher()..subject = 'Biology';
final linda = isar.students.where()
.filter()
.nameEqualTo('Linda')
.findFirst();
print(linda.teachers); // {Teacher('Math')}
linda.teachers.add(biologyTeacher);
await isar.writeTxn(() async {
await linda.teachers.save();
});
print(linda.teachers); // {Teacher('Math'), Teacher('Biology')}
```
## Backlinks
Te escuché decir, "Y si necesito expresar relaciones a la inversa?". No te precupes! Te presento a los `backlinks`.
Los backlinks son enlaces en la dirección inversa. Cada enlace tiene un backlink implícito. Puedes hacer que esté disponible para tu aplicación anotando un `IsarLink` o `IsarLinks` con `@Backlink()`.
Los backlinks no requieren memoria o recursos adicionales; puedes agregarlos libremente, puedes borrarlos o renombrarlos sin perder datos.
Queremos saber qué estudiantes tiene un maestro, entonces definimos un backlink:
```dart
@collection
class Teacher {
Id id;
late String subject;
@Backlink(to: 'teacher')
final student = IsarLinks();
}
```
Necesitamos especificar el enlace al cual apunta el backlink. Es posible tener múltiples enlaces diferentes entre dos objetos.
## Inicializar enlaces
Los `IsarLink` y `IsarLinks` tienen un constructor de cero argumentos,que debería ser usado para asignar la propiedad enlace que se crea el objeto. Es buena práctica hacer que las propiedades de los enlaces sean `final`.
Cuando insertas (`put()`) tus objectos por primera vez, el enlace se inicializa con las collecciones origen y destino, y puedes llamar métodos como `load()` y `save()`. Un enlace comienza a serguir los cambios inmediatamente después de su creación, entonces puede agregar o quitar relaciones incluso antes que el enlace sea inicializado.
:::danger
Es ilegal mover un enlace a otro objeto.
:::
================================================
FILE: docs/docs/es/queries.md
================================================
---
title: Consultas
---
# Consultas
Las consultas se utilizan para buscar registros que coincidan con ciertas condiciones, por ejemplo:
- Buscar todos los contactos favoritos
- Buscar contactos con nombre distinto
- Borrar todos los contactos que no tengan definido un apellido
Dado que las consultas se ejecutan en la base de datos y no en Dart, son realmente rápidas. Si utilizas índices de manera inteligente, puedes mejorar el rendimiento de las consultas todavía más. A continuación, aprederás cómo esribir consultas y cómo lograr que sean lo más rápidas posible.
Existen dos métodos diferentes para firltrar tus registros: Filtros y cláusulas where. Comenzaremos hechando un vistazo a cómo funcionan los filtros.
## Filtros
Los filtros son fáciles de usar y de entener. Dependiendo del tipo de tus propiedades, existen operaciones de filtrado diferentes con nombres bien definidos.
Los filtros funcionan evaluando una expresión para cada objeto en la colección que está siendo filtrada. Si la expresión resuelve en verdadero, Isar incluye el objeto en los resultados. Los filtros no afectan el orden de los resultados.
Usaremos el modelo siguiente para los ejemplos:
```dart
@collection
class Shoe {
Id? id;
int? size;
late String model;
late bool isUnisex;
}
```
### Query conditions
Dependiendo del tipo de campo, tienes diferentes condiciones disponibles.
| Condición | Descripción |
| ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.equalTo(value)` | Coincide con valores que son iguales a `value`. |
| `.between(lower, upper)` | Conicide con valores que están entre `lower` y `upper`. |
| `.greaterThan(bound)` | Coincide con valores que son mayores que `bound`. |
| `.lessThan(bound)` | Coincide con valores que son menores que `bound`. Los valores `null` serán incluídos por defecto ya que `null` se considera menor que cualquier otro valor. |
| `.isNull()` | Coincide con valores `null`. |
| `.isNotNull()` | Coindice con valores que no son `null`. |
| `.length()` | Las consultas de tamaño de listas, strings y links filtran objectos basados en el número de elementos en una lista o link. |
Asumeindo que la base de datos contiene cuatro zapatos de talle 39, 40, 46 y uno con talle no asignado (`null`). A menos que utilice orden de resultados, los valores serán ordenados por su id.
```dart
isar.shoes.filter()
.sizeLessThan(40)
.findAll() // -> [39, null]
isar.shoes.filter()
.sizeLessThan(40, include: true)
.findAll() // -> [39, null, 40]
isar.shoes.filter()
.sizeBetween(39, 46, includeLower: false)
.findAll() // -> [40, 46]
```
### Operadores lógicos
Puedes encadenar expresiones usando los operadores lógicos siguientes:
| Operador | Descripción |
| ---------- | ------------------------------------------------------------------------------------ |
| `.and()` | Se evalúa como verdadero si ambas expresiones de izquierda y derecha son verdaderas. |
| `.or()` | Se evalúa como verdadero si alguna de las expresiones es verdadera. |
| `.xor()` | Se evalúa como verdadero si sólo una de las expresiones es verdadera. |
| `.not()` | Invierte (niega) el resultado de la expresión siguiente. |
| `.group()` | Agrupa condiciones y permite especificar un orden de evaluación. |
Si quieres buscar todos los zapatos de talle 46, puedes hacer lo siguiqnte:
```dart
final result = await isar.shoes.filter()
.sizeEqualTo(46)
.findAll();
```
Si quieres usar más de una condición, puedes combinar múltiples filtros usando los operadores **and** `.and()`, **or** `.or()` y **xor** `.xor()`.
```dart
final result = await isar.shoes.filter()
.sizeEqualTo(46)
.and() // Optional. Filters are implicitly combined with logical and.
.isUnisexEqualTo(true)
.findAll();
```
Esta consulta es equivalente a: `size == 46 && isUnisex == true`.
También puedes agrupar condiciones usando `.group()`:
```dart
final result = await isar.shoes.filter()
.sizeBetween(43, 46)
.and()
.group((q) => q
.modelNameContains('Nike')
.or()
.isUnisexEqualTo(false)
)
.findAll()
```
Esta consulta es equivalente a `size >= 43 && size <= 46 && (modelName.contains('Nike') || isUnisex == false)`.
Para negar una condición o grupo, usa el operador lógico **not** `.not()`:
```dart
final result = await isar.shoes.filter()
.not().sizeEqualTo(46)
.and()
.not().isUnisexEqualTo(true)
.findAll();
```
Esta consulta es equivalente a `size != 46 && isUnisex != true`.
### Condiciones sobre Strings
Adicionalmente a las condiciones mencionadas anteriormente, los valores de tipo String ofrecen algunas condiciones más. Por ejemplo, los comodines para expresiones regulares permiten mayor flexibilidad en las búsquedas.
| Condition | Description |
| -------------------- | --------------------------------------------------- |
| `.startsWith(value)` | Coincide con strings que comiencen con `value`. |
| `.contains(value)` | Coincide con strings que contengan `value`. |
| `.endsWith(value)` | Coincide con strings que terminen con `value`. |
| `.matches(wildcard)` | Coincide según la evaluación del patrón `wildcard`. |
**Sensibilidad a las mayúsculas y minúsculas**
Todas las operaciones con strings tienen un parámetro opcional `caseSensitive` para distinguir entre mayúsculas y minúsculas que por defecto está seteado en verdadero.
**Comodines:**
Una [expresión comodín](https://es.wikipedia.org/wiki/Car%C3%A1cter_comod%C3%ADn) es una cadena de texto (string) que utiliza caracteres normales combinados con dos caracteres especiales comodines:
- El comodín `*` coincide con ninguno o más de cualquier caracter.
- El comodín `?` coincide con un caracter cualquiera.
Por ejemplo, la cadena comodín `"d?g"` coincide con `"dog"`, `"dig"`, y `"dug"`, Pero no con `"ding"`, `"dg"`, o `"a dog"`.
### Modificadores de consultas
A veces es necesario construir una consulta basándose en algunas condiciones o para diferentes valores. Isar posee una herramienta muy poderosa para construir consultas condicionales:
| Modificador | Descripción |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `.optional(cond, qb)` | Extiende la consulta únicamente si la `condición` es verdadera. Esto puede usarse en cualquier lugar en una consulta, por ejemplo para aplicar ordenamiento o límites de manera condicional. |
| `.anyOf(list, qb)` | Extiende la consulta para cada valor en `values` y combina las condiciones usando el operador lógico **or**. |
| `.allOf(list, qb)` | Extiende la consulta para cada valor en `values` y combina las condiciones usando el operador lógico **and**. |
En este ejemplo, construiremos un método que puede buscar zapatos con un filtro opcional:
```dart
Future> findShoes(Id? sizeFilter) {
return isar.shoes.filter()
.optional(
sizeFilter != null, // only apply filter if sizeFilter != null
(q) => q.sizeEqualTo(sizeFilter!),
).findAll();
}
```
Si quieres buscar zapatos entre múltiples talles, puedes usar una consulta convencional o usar el modificador `anyOf()`:
```dart
final shoes1 = await isar.shoes.filter()
.sizeEqualTo(38)
.or()
.sizeEqualTo(40)
.or()
.sizeEqualTo(42)
.findAll();
final shoes2 = await isar.shoes.filter()
.anyOf(
[38, 40, 42],
(q, int size) => q.sizeEqualTo(size)
).findAll();
// shoes1 == shoes2
```
Los modificadores de consultas son especialmente útiles si quieres construir consultas dinámicas.
### Listas
Incluso se puede construir consultas sobre listas:
```dart
class Tweet {
Id? id;
String? text;
List hashtags = [];
}
```
Puedes consultar basándote en la longitud de la lista:
```dart
final tweetsWithoutHashtags = await isar.tweets.filter()
.hashtagsIsEmpty()
.findAll();
final tweetsWithManyHashtags = await isar.tweets.filter()
.hashtagsLengthGreaterThan(5)
.findAll();
```
Éstos son equivalenets al código Dart `tweets.where((t) => t.hashtags.isEmpty);` y `tweets.where((t) => t.hashtags.length > 5);`. También puedes consultar basándote en los elementos de la lista:
```dart
final flutterTweets = await isar.tweets.filter()
.hashtagsElementEqualTo('flutter')
.findAll();
```
Esto es equivalente al código Dart `tweets.where((t) => t.hashtags.contains('flutter'));`.
### Objetos embebidos
Los objetos embebidos son una de las funcionalidades más útiles de Isar. Se pueden consultar de manera muy eficiente usando las mismas condiciones disponibles para los objetos raíz. Asumiendo que tenemos el siguiente modelo:
```dart
@collection
class Car {
Id? id;
Brand? brand;
}
@embedded
class Brand {
String? name;
String? country;
}
```
Necesitamos consultar todos los autos que sean de la marca `"BMW"` y del país `"Germany"`. Podemos hacerlo con la siguiente consulta:
```dart
final germanCars = await isar.cars.filter()
.brand((q) => q
.nameEqualTo('BMW')
.and()
.countryEqualTo('Germany')
).findAll();
```
Siempre trata de agrupar las consultas anidadas. La consulta anterior es más eficiente que ésta siguiente auque el resultado es el mismo:
```dart
final germanCars = await isar.cars.filter()
.brand((q) => q.nameEqualTo('BMW'))
.and()
.brand((q) => q.countryEqualTo('Germany'))
.findAll();
```
### Enlaces
Si tus modelos contienen [links or backlinks](links) puedes filtrar tus consultas basándote en el objeto enlazado o la cantidad de objetos enlazados.
:::warning
Ten en cuenta que las consultas sobre enlaces pueden ser costosas ya que Isar necesita buscar en los objetos enlazados. Considera usar objetos embebidos en su lugar.
:::
```dart
@collection
class Teacher {
Id? id;
late String subject;
}
@collection
class Student {
Id? id;
late String name;
final teachers = IsarLinks();
}
```
Podemos buscar todos los estudiantes que tienen un maestro de Matemáticas o de Inglés:
```dart
final result = await isar.students.filter()
.teachers((q) {
return q.subjectEqualTo('Math')
.or()
.subjectEqualTo('English');
}).findAll();
```
Los filtros sobre enlaces se evalúan en verdadero si al menos unos de los objetos enlazados coincide con la condición.
Busquemos todos los estudiantes que no tienen maestro:
```dart
final result = await isar.students.filter().teachersLengthEqualTo(0).findAll();
```
o:
```dart
final result = await isar.students.filter().teachersIsEmpty().findAll();
```
## Cláusulas `where`
Las cláusulas `where` son una herramienta muy poderosa, pero puede ser algo desafiante lograr usarlas de la manera correcta.
En contraste con los filtros, las cláusulas `where` usan los índices que definiste en el esquema para verificar las condiciones de la consulta. Consultar un índice es mucho más rápido que filtrar cada registro individualmente.
➡️ Ver más en: [Índices](indexes)
:::tip
Como regla básica, deberías intentar reducir la cantidad de registros lo mayor posible usando cláusulas `where` y luego hacer el filtrado restante usando filtros.
:::
Sólo puedes combinar cláusulas `where` usando operaciones lógicas **or**. En otras palabras, puedes sumar múltiples cláusulas `where`, pero no puedes consultar la intersección de múltiples de ellas.
Agreguemos ídices a nuestra colección shoe:
```dart
@collection
class Shoe with IsarObject {
Id? id;
@Index()
Id? size;
late String model;
@Index(composite: [CompositeIndex('size')])
late bool isUnisex;
}
```
Tenemos dos índices. El índice en `size` nos permite usar cláusulas `where` como `.sizeEqualTo()`. El índice compuesto en `isUnisex` nos permite usar cláusulas como `isUnisexSizeEqualTo()`. Pero también `isUnisexEqualTo()` porque siempre puedes usar cualquier prefijo de un índice.
Ahora podemos reescribir la consulta anterior que busca zapatos unisex de talle 46 usando el índice compuesto. Esta consulta será mucho más rápida que la anterior:
```dart
final result = isar.shoes.where()
.isUnisexSizeEqualTo(true, 46)
.findAll();
```
Las cláusulas `where` tienen dos superpoderes adicionales: Te brindad ordenado "libre" y una súper rápida operación `distinct`.
### Combinando cláusulas `where` y filtros
Recuerdas la consulta `shoes.filter()`? Es en realidad un atajo para `shoes.where().filter()`. Puedes (y deberías) combinar cláusulas `where` y filtros en la misma consulta para usar los beneficios de ambos:
```dart
final result = isar.shoes.where()
.isUnisexEqualTo(true)
.filter()
.modelContains('Nike')
.findAll();
```
La cláusula `where` se aplica primero para reducir el número de objetos a ser filtrados. Luego se aplica el filtro a los objetos restantes.
## Ordenando
Puedes definir cómo se deben ordenar los resultados cuando se ejecuta una consulta usando los métodos `.sortBy()`, `.sortByDesc()`, `.thenBy()` y `.thenByDesc()`.
Para buscar todos los zapatos ordenados por nombre de modelo en orden ascendente sin usar un índice:
```dart
final sortedShoes = isar.shoes.filter()
.sortByModel()
.thenBySizeDesc()
.findAll();
```
Ordenar un gran número de resultados puede ser costoso, especialmente dado que el ordenamiento sucede antes que el salto y los límites. Los métodos de ordenamiento anteriores nunca hacen uso de índices. Afortunadamente, también podemos hacer ordenamiento usando cláusulas `where` y hacer que nuestra consulta sea rápida como un rayo aún si tenemos que ordenar un millón de objetos.
### Ordenando con cláusulas `where`
Si usas una sola cláusula `where` en tu consulta, los resultados ya están ordenados por su índice. Eso ya es mucho!
Supongamos que tenemos zapatos en talle `[43, 39, 48, 40, 42, 45]` y queremos buscar todos los zapatos de talle mayor a `42` y además los queremos ordenados por talle:
```dart
final bigShoes = isar.shoes.where()
.sizeGreaterThan(42) // also sorts the results by size
.findAll(); // -> [43, 45, 48]
```
Como puedes ver, el resultado está ordenado por el índice `size`. Si quieres invertir el orden, puedes establecer `sort` a `Sort.desc`:
```dart
final bigShoesDesc = await isar.shoes.where(sort: Sort.desc)
.sizeGreaterThan(42)
.findAll(); // -> [48, 45, 43]
```
Es posible que no quieras usa la cláusula `where` pero sí beneficiarte del ordenamiento implícito. Puedes usar la cláusula `any`:
```dart
final shoes = await isar.shoes.where()
.anySize()
.findAll(); // -> [39, 40, 42, 43, 45, 48]
```
Si usas un índice compuesto, los resultados son ordenados según todos los campos en el índice.
:::tip
Si necesitas ordenar tus resultados, considera usar índices para eso. Especialmente si trabajas con `offset()` y `limit()`.
:::
A veces no es posible o no es útil usar índices para ordenar. En esos casos, usa índices para reducir el número de resultados lo más posible.
## Valores únicos
Para retornar sólo entradas con valores únicos, utiliza el predicado `distinct`. Por ejemplo, para saber cuántos modelos diferentes de zapatos tienes en base de datos Isar:
```dart
final shoes = await isar.shoes.filter()
.distinctByModel()
.findAll();
```
También puedes encadenar múltiples condiciones `distinct` para buscar todos los zapatos con distinta combinación de modelo-talle:
```dart
final shoes = await isar.shoes.filter()
.distinctByModel()
.distinctBySize()
.findAll();
```
Sólo se retorna el primer valor de cada combinación distinta. Puedes usar cláusulas `where` y operaciones de ordenamiento para controlarlos.
### Cláusula `where` `distinct`
Si tienes un ídice que no es único, podrías querer obtener todos sus valores distintos. Podrías usar la operación `distinctBy` de la sección anterior, pero se ejecuta después del ordenamiento y filtrado, por lo que hay algunas operaciones adicionales.
Si solo usas una sola cláusula `where`, puedes por el contrario confiar en el índice para ejecutar la operación `distinct`.
```dart
final shoes = await isar.shoes.where(distinct: true)
.anySize()
.findAll();
```
:::tip
En teoría, podrías incluso usar múltiples cláusulas `where` para ordenamiento y distintos. La única restricción es que aquellas cláusulas `where` no se superpongan y usen el mismo índice. Para un correcto ordenamiento, también tienen que ser aplicadas en el orden de ordenamiento. Debes ser muy cuidadoso si utilizas estos métodos!
:::
## Offset y Límite
A menudo es buena idea limitar el número de resultados de una consulta para vistas de listas perezosas. Puedes hacer esto estableciendo un `limit()`:
```dart
final firstTenShoes = await isar.shoes.where()
.limit(10)
.findAll();
```
Estableciendo un `offset()` puedes también paginar los resultados de su consulta.
```dart
final firstTenShoes = await isar.shoes.where()
.offset(20)
.limit(10)
.findAll();
```
Dado que el instanciado de objetos Dart es a menudo la parte más costosa cuando se ejecuta una consulta, es una buena idea cargar sólo los objectos que necesitas.
## Orden de ejecución
Isar ejecuta las consultas siempre en el mismo orden:
1. Atravesar índices primarios o secundarios para buscar objetos (aplicar las cláusulas `where`)
2. Filtrar objetos
3. Ordenar resultados
4. Aplicar operaciones `distinct`
5. Aplicar `offset` y `limit` a los resultados
6. Retornar los resultados
## Operaciones de consulta
En los ejemplos anteriores, usamos `.findAll()` para recuperar todas las coincidencias de objectos. Sin embargo, hay más operaciones disponibles:
| Operaciones | Descripción |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------ |
| `.findFirst()` | Recupera el primer objeto coincidente con la consulta o `null` si no se encontró ninguna. |
| `.findAll()` | Recupera todos los objetos para la consulta. |
| `.count()` | Cuenta cuántos objetos coinciden con la consulta. |
| `.deleteFirst()` | Elimina de la colección el primer objeto coincidente con la consulta. |
| `.deleteAll()` | Elimina de la colección todos los objetos coincidentes con la consulta. |
| `.build()` | Compila la consulta para ser usada luego. Esto ahorra el costo de contruir una consulta si tienes que ejecutarla muchas veces. |
## Consulta de propiedades
Si estás interesado solamente en los valores de un propiedad simple, puedes usar consulta de propiedades. Simplemente construye una consulta regular y selecciona una propiedad:
```dart
List models = await isar.shoes.where()
.modelProperty()
.findAll();
List sizes = await isar.shoes.where()
.sizeProperty()
.findAll();
```
Usar una sola propiedad ahora tiempo durante el deserializado. Las consultas de propiedades también funcionan para los objetos embebidos y las listas.
## Agregación
Isar soporta el agregado de los valores de una consulta de propiedad. Las siguientes operaciones de agregación están disponibles:
| Operación | Descripción |
| ------------ | --------------------------------------------------------------------- |
| `.min()` | Busca el valor mínimo o `null` si ninguno coincide. |
| `.max()` | Busca el valor máximo o `null` si ninguno coincide. |
| `.sum()` | Suma todos los valores. |
| `.average()` | Calcula el promedio de todos los valores o `NaN` si ninguno coincide. |
Usar agregaciones es mucho más rápido que buscar todos los valores y realizar las operaciones de forma manual.
## Consultas dinámicas
:::danger
Esta sección no debería ser relevante. El uso de consultas dinámicas está desaconsejado a menos que sea abosulamente necesario (lo cual es poco probable).
:::
Todos los ejemplos anteriores usan el QueryBuilder y los métodos estáticos generados. Podrías querer crear una consulta dinámica o un lenguaje de consultas personalizado (como el Isar Inspector). En ese caso, puedes usar el método `buildQuery()`:
| Parámetro | Descripción |
| --------------- | -------------------------------------------------------------------------------------------------- |
| `whereClauses` | La cláusula `where` de la consulta. |
| `whereDistinct` | Si las consultas deben retornan sólo valores distintos (solo útil para consultas `where` simples). |
| `whereSort` | El orden de atravesado de la cláusula `where` (solo útil para consultas `where` simples). |
| `filter` | El filtro a aplicar al resultado. |
| `sortBy` | Una lista de propiedades para definir el orden del resultado. |
| `distinctBy` | Una lista de propiedades para aplicar `distinct`. |
| `offset` | El offset de los resultados. |
| `limit` | El número máximo de resultados a retornar. |
| `property` | Si no es null, sólo se retornan los valores de ésta propiedad. |
Creemos una consulta dinámica:
```dart
final shoes = await isar.shoes.buildQuery(
whereClauses: [
WhereClause(
indexName: 'size',
lower: [42],
includeLower: true,
upper: [46],
includeUpper: true,
)
],
filter: FilterGroup.and([
FilterCondition(
type: ConditionType.contains,
property: 'model',
value: 'nike',
caseSensitive: false,
),
FilterGroup.not(
FilterCondition(
type: ConditionType.contains,
property: 'model',
value: 'adidas',
caseSensitive: false,
),
),
]),
sortBy: [
SortProperty(
property: 'model',
sort: Sort.desc,
)
],
offset: 10,
limit: 10,
).findAll();
```
La siguiente consulta es equivalente:
```dart
final shoes = await isar.shoes.where()
.sizeBetween(42, 46)
.filter()
.modelContains('nike', caseSensitive: false)
.not()
.modelContains('adidas', caseSensitive: false)
.sortByModelDesc()
.offset(10).limit(10)
.findAll();
```
================================================
FILE: docs/docs/es/recipes/data_migration.md
================================================
---
title: Migración de datos
---
# Migración de datos
Isar migra automáticamente tus esquemas de la base de datos si agregas o quitas colecciones, campos o índices. Probablemente quieras migrar también tus datos. Isar no ofrece una solución incluída porque impondría restricciones arbitrarias a la migración. Es sencillo implementar una lógica de migración que se adecúe a tus necesidades.
En este ejemplo usaremos una versión simple de la base de datos completa. Utilizamos `SharedPreferences` para almacenar la versión actual y compararla con la versión a la cual queremos migrar. Si la versión no coincide, migramos los datos y actualizamos la versión.
:::tip
También podrías darle a cada colección su propia versión y migrarlas individualmente.
:::
Imagina que tenemos una colección de usuarios con un campo de cumpleaños. En la versión 2 de nuetra app, necesitamos agregar un campo adicional para el año de nacimiento para consultar usuarios por edad.
Version 1:
```dart
@collection
class User {
Id? id;
late String name;
late DateTime birthday;
}
```
Version 2:
```dart
@collection
class User {
Id? id;
late String name;
late DateTime birthday;
short get birthYear => birthday.year;
}
```
El problema es que el modelo existente para los usuarios tendrá un campo vacío `birthYear` porque no existía en la versión 1. Necesitamos migrar los datos para establecer el campo `birthYear`.
```dart
import 'package:isar/isar.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() async {
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[UserSchema],
directory: dir.path,
);
await performMigrationIfNeeded(isar);
runApp(MyApp(isar: isar));
}
Future performMigrationIfNeeded(Isar isar) async {
final prefs = await SharedPreferences.getInstance();
final currentVersion = prefs.getInt('version') ?? 2;
switch(currentVersion) {
case 1:
await migrateV1ToV2(isar);
break;
case 2:
// If the version is not set (new installation) or already 2, we do not need to migrate
return;
default:
throw Exception('Unknown version: $currentVersion');
}
// Update version
await prefs.setInt('version', 2);
}
Future migrateV1ToV2(Isar isar) async {
final userCount = await isar.users.count();
// We paginate through the users to avoid loading all users into memory at once
for (var i = 0; i < userCount; i += 50) {
final users = await isar.users.where().offset(i).limit(50).findAll();
await isar.writeTxn((isar) async {
// We don't need to update anything since the birthYear getter is used
await isar.users.putAll(users);
});
}
}
```
:::warning
Si tienes que migrar muchos datos, considera utilizar un isolate en segundo plano para prevenir efectos no deseados en la UI.
:::
================================================
FILE: docs/docs/es/recipes/full_text_search.md
================================================
---
title: Full-text search
---
# Full-text search
Full-text search es una manera poderosa de buscar texto en la base de datos. Ya deberías estar familiarizado con la forma en que funcionan los [índices](/es/indexes), pero vayamos sobre lo básico.
Un índice funciona como una tabla de lookup, permitiéndole al motor de consulta encontrar rápidamente registros con un cierto valor. Por ejemplo, si tienes un campo `title` en tu objeto, puedes crear un índice en ese campo para hacer más rápida la búsqueda de objetos con un título determinado.
## Porqué full-text search es útil?
Puedes buscar texto fácilmente usando filtros. Existen algunas operaciones sobre string como por ejemplo `.startsWith()`, `.contains()` y `.matches()`. El problema con los filtros es que su tiempo de ejecución es de `O(n)` donde `n` es la cantidad de registros en la colección. Operaciones sobre strings como `.matches()` son especialmente costosas.
:::tip
Full-text search es mucho más rápido que los filtros, pero los índices tienen cietas limitaciones. En este receta vamos a explorar cómo sobrepasar esas limitaciones.
:::
## Exemplo básico
La idea es siempre la misma: En lugar de indexar el texto completo, indexamos las palabras en el texto así podemos buscar por ellos individualmente.
Creamos el índice full-text más básico:
```dart
class Message {
Id? id;
late String content;
@Index()
List get contentWords => content.split(' ');
}
```
Ahora podemos buscar mensajes que contengan palabras específicas en el contenido:
```dart
final posts = await isar.messages
.where()
.contentWordsAnyEqualTo('hello')
.findAll();
```
Esta consulta es súper rápida, pero existen algunos problemas:
1. Sólo podemos buscar palabras completas
2. No consideramos puntuación
3. No soportamos otros caracteres de separación de palabras
## Separando el texto de la forma correcta
Intentemos mejorar el ejemplo anterior. Podemos intentar desarrollar una expresión regular complicada para correjir la separación de las palabras, pero sería lento e incorrecto para casos de borde.
El [Anexo Unicode #29](https://unicode.org/reports/tr29/) define cómo separar palabras correctamente para la mayoría de los idiomas. Es algo complicado, pero afortunadamente Isar hace el trabajo pesado por nosotros:
```dart
Isar.splitWords('hello world'); // -> ['hello', 'world']
Isar.splitWords('The quick (“brown”) fox can’t jump 32.3 feet, right?');
// -> ['The', 'quick', 'brown', 'fox', 'can’t', 'jump', '32.3', 'feet', 'right']
```
## Quiero más control
Pan comido! Podemos cambiar nuestro índice para soportar también coincidencia por prefijo y por mayúsculas y minúsculas:
```dart
class Post {
Id? id;
late String title;
@Index(type: IndexType.value, caseSensitive: false)
List get titleWords => title.split(' ');
}
```
De manera predeterminada, Isar almacenará las palabras como valores hash que es rápido y eficiente en cuanto a espacio. Pero los hashes nos pueden usarse para coincidencia por prefijo. Usando `IndexType.value`, podemos cambiar el índice para usar las palabras directamente. Ésto nos ofrece la cláusula `.titleWordsAnyStartsWith()`:
```dart
final posts = await isar.posts
.where()
.titleWordsAnyStartsWith('hel')
.or()
.titleWordsAnyStartsWith('welco')
.or()
.titleWordsAnyStartsWith('howd')
.findAll();
```
## También necesito `.endsWith()`
Por supuesto! Usaremos un truco para conseguir la coincidencia `.endsWith()`:
```dart
class Post {
Id? id;
late String title;
@Index(type: IndexType.value, caseSensitive: false)
List get revTitleWords {
return Isar.splitWords(title).map(
(word) => word.reversed).toList()
);
}
}
```
No olvides invertir la terminación que quieres buscar:
```dart
final posts = await isar.posts
.where()
.revTitleWordsAnyStartsWith('lcome'.reversed)
.findAll();
```
## Algoritmos de derivación
Desafortunadamente, los índices no soportan coincidencia por `.contains()` (ésto también es cierto para otras bases de datos). Pero existen algunas alternativas que vale la pena explorar. La elección depende fuertemente de su uso. Un ejemplo es indexar la raíz de la palabra en de la misma completa.
Un algoritmo de derivación (stemming algorithm) es un proceso de normalización linguística en el cual las diferentes formas de una palabra se reducen a una forma común:
```
connection
connections
connective ---> connect
connected
connecting
```
Algoritmos populares son el [Porter stemming algorithm](https://tartarus.org/martin/PorterStemmer/) y el [Snowball stemming algorithms](https://snowballstem.org/algorithms/).
Existen también formas avanzadas como [lematización](https://es.wikipedia.org/wiki/Lematizaci%C3%B3n).
## Algoritmos de fonética
Un [algoritmo de fonética](https://en.wikipedia.org/wiki/Phonetic_algorithm) es un algoritmo para indexar palabras de acuerdo a su pronunciación. Es decir, te permite encontrar palabras que "suenan" parecido a las que estás buscando.
:::warning
La mayoría de los algoritmos de fonética sólo soportan un solo idioma.
:::
### Soundex
[Soundex](https://es.wikipedia.org/wiki/Soundex) es un algoritmo de fonética para indexar nombres por sonido, como se pronuncian en inglés. El objetivo es que los homófonos se codifiquen con la misma representación para que produzcan coincidencias a pesar de alguna menor diferencia de tipeo. Es un algoritmo directo, y existen múltiples versiones mejoradas.
Usando este algoritmo, ambos `"Robert"` y `"Rupert"` retornan la cadena `"R163"` mientras que `"Rubin"` entrega `"R150"`. `"Ashcraft"` y `"Ashcroft"` ambos entregan `"A261"`.
### Metáfono doble
El algoritmo de codificado fonético [Metáfono doble](https://es.wikipedia.org/wiki/Metaphone) es la segunda generación de este tipo de algoritmos. Contiene varias mejoras fundamentales de diseño sobre el algoritmo de metáfono original.
Doble Metáfono da cuenta de varias irregularidades en inglés de eslavo, alemán, celta, griego, francés, italiano, español, chino, y otros orígenes.
================================================
FILE: docs/docs/es/recipes/multi_isolate.md
================================================
---
title: Uso de múltiples Isolates
---
# Uso de múltiples Isolates
En lugar de tareas, todo el código de Dart corre dentro de isolates. Cada isolate tiene su propia cabecera de memoria, asegurando que ningún estado de un isolate es accesible desde otro isolate.
Isar puede accederse desde múltiples isolates al mismo tiempo, e incluso los observadores funcionan entre isolates. En esta receta, veremos cómo utilizar Isar en un entorno con múltiples isolates.
## Cuándo usar múltiples isolates
Las transacciones Isar se ejecutan en paralelo incluso dentro de un mismo isolate. En algunos casos, es también beneficioso acceder a Isar desde múltiples isolates.
El motivo es que Isar tarda algo de tiempo codificando y decodificando datos desde y hacia objetos Dart. Puedes pensarlo como codificando y decodificando JSON (sólo que más eficiente). Estas operaciones corren dentro del isolate en el cual se acceden los datos y naturalmente bloquean otro código en el isolate. En otras palabras: Isar ejecuta parte del trabajo dentro de tu isolate Dart.
Si sólo necesitas leer y escribir algunos cientos de objetos de una vez, hacerlo dentro del mismo isolate que la UI no es un problema. Pero para transacciones muy grandes o si el isolate de la UI ya está bastante ocupado, deberías considerar usar un isolate separado.
## Ejemplo
Lo primero que debemos hacer es abrir Isar en el nuevo isolate. Dado que la instancia de Isar ya está abierta en el isolate principal, `Isar.open()` retornará la misma instancia.
:::warning
Asegúrate de proveer los mismos esquemas que en el isolate principal. De lo contrario, obtendrás un error.
:::
`compute()` inicia un nuevo isolate en Flutter y ejecuta la función dada en él.
```dart
void main() {
// Open Isar in the UI isolate
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[MessageSchema],
directory: dir.path,
name: 'myInstance',
);
// listen to changes in the database
isar.messages.watchLazy(() {
print('omg the messages changed!');
});
// start a new isolate and create 10000 messages
compute(createDummyMessages, 10000).then(() {
print('isolate finished');
});
// after some time:
// > omg the messages changed!
// > isolate finished
}
// function that will be executed in the new isolate
Future createDummyMessages(int count) async {
// we don't need the path here because the instance is already open
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[PostSchema],
directory: dir.path,
name: 'myInstance',
);
final messages = List.generate(count, (i) => Message()..content = 'Message $i');
// we use a synchronous transactions in isolates
isar.writeTxnSync(() {
isar.messages.insertAllSync(messages);
});
}
```
Existen algunos aspectos interesantes a notar en este ejemplo:
- `isar.messages.watchLazy()` se llama en el isolate de la UI y es notificado de los cambios desde otro isolate.
- Las instancias son referenciadas por nombre. El nombre por defecto es `default`, pero en este ejemplo, utilizamos `myInstance`.
- Utilizamos una transacción síncrona para crear los mensajes. Bloquear el nuevo isolate no es un problema, y las transacciones síncronas son algo más rápidas.
================================================
FILE: docs/docs/es/recipes/string_ids.md
================================================
---
title: Ids de texto
---
# Ids de texto
Esta es uno de los pedidos más frecuentes para Isar, por eso les dejamos este tutorial sobre como usar ids de texto.
Isar no soporta ids de texto de forma nativa, y existe una buena razón para eso: los ids de enteros son mucho más eficientes y rápidos. Especialmente para enlaces, el gasto de un id de texto es demasiado significativo.
Es probable que necesites almacenar datos externos que usen UUIDs o otro id no entero. Se recomienda almacenar el id de texto como una propiedad del objeto y usar una función rápida de hash para generar un entero de 64 bits que pueda ser usado como Id.
```dart
@collection
class User {
String? id;
Id get isarId => fastHash(id!);
String? name;
int? age;
}
```
De esta maneras obtienes lo mejor de dos mundos: Ids enteros eficientes para los enlaces y la posibilidad de usar ids de texto.
## Función rápida de hash
Idealmente, tu función de hash debería ser rápida y de alta calidad (sin colisiones). La siguiente es una implementación recomendada:
```dart
/// FNV-1a 64bit hash algorithm optimized for Dart Strings
int fastHash(String string) {
var hash = 0xcbf29ce484222325;
var i = 0;
while (i < string.length) {
final codeUnit = string.codeUnitAt(i++);
hash ^= codeUnit >> 8;
hash *= 0x100000001b3;
hash ^= codeUnit & 0xFF;
hash *= 0x100000001b3;
}
return hash;
}
```
Si eliges una función de hash diferente, asegúrate de que retorne un entero de 64-bit. Evita usar funciones hash criptográficas porque son mucho más lentas.
:::warning
Evita usar `string.hashCode` porque no está garantizada la estabilidad entre distintas plataformas y versiones de Dart.
:::
================================================
FILE: docs/docs/es/schema.md
================================================
---
title: Esquema
---
# Esquema
Cuando usas Isar para almacenar los datos de tu aplicación, estás tratando con colecciones. Una colección es como una tabla en la base de datos Isar asociada y sólo puede contener un tipo de objeto Dart. Cada objeto de la colección representa una línea de datos en la tabla correspondiente.
La definición de una colección es llamada "esquema" ("schema" en inglés). El generador Isar hará el trabajo pesado por ti y generará la mayoría del código que necesitas para usar tu colección.
## Anatomía de una colección
Cada colección Isar se define anotando una clase con `@collection` o `@Collection()`. Una colección Isar incluye campos para cada columna en la tabla correspondiente en la base de datos, incluyendo uno que corresponde a la clave primaria.
El código siguiente es un ejemplo de una colección simple que define una table `User` con columnas para ID, nombre, y apellido:
```dart
@collection
class User {
Id? id;
String? firstName;
String? lastName;
}
```
:::tip
Para almacenar un campo, Isar debe tener acceso al mismo. Puedes asegurarte que Isar tiene acceso a un campo haciéndolo público o proporcionando métodos getter y setter.
:::
Existen algunos parámetros opcionales para personalizar la colección:
| Configuración | Descripción |
| ------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `inheritance` | Controla si los campos de la clase padre y mixins serán almacenados en Isar. Habilitado por defecto. |
| `accessor` | Permite renombrar el punto de acceso por defecto de la colección (por ejemplo `isar.contacts` para la colección `Contact`). |
| `ignore` | Permite ignorar ciertas propiedades. Éstas también son respetadas para las super clases. |
### Isar Id
Cada clase que defina una colección Isar, debe definir una propiedad id y debe ser de tipo `Id` identificando inequívocamente un objecto. `Id` es simplemente un alias para `int` que le permite al generador Isar reconocer la propiedad id.
Isar indexa automáticamente los campos id, que permite obtener y modificar objectos de manera eficiente basándose en su id.
Puedes establecer tus propios ids o pedir a Isar que asigne un id auto-incrementable. Si el campo `id` es `null` y no `final`, Isar asignará un id auto-incrementable. Si quieres un id auto-incrementable y no-null, puedes usar `Isar.autoIncrement` en lugar de `null`.
:::tip
Los ids auto incrementables no se reusan cuando un objeto es eliminado. La única manera de reiniciar los ids auto incrementables es borrando la base de datos.
:::
### Renombrando colecciones y campos
Por defecto, Isar usa el nombre de la clase como nombre de la colección. De manera similar, Isar usa los nombres de los campos como nombres de las columnas en la base de datos. Si quieres que una colección o campo tenga un nombre diferente, agrega la anotación `@Name`. El ejemplo siguiente demuestra el uso de nombres personalizados para colecciones y campos:
```dart
@collection
@Name("User")
class MyUserClass1 {
@Name("id")
Id myObjectId;
@Name("firstName")
String theFirstName;
@Name("lastName")
String familyNameOrWhatever;
}
```
Específicamente si quieres renombrar objectos Dart o clases que ya están almacenados en la base de datos, deberías considerar usar la anotación `@Name`. De otra manera, la base de datos eliminiará y creará nuevamente el campo o la colección.
### Ignorando campos
Isar almacena todos los campos públicos de una clase que defina una colección. Anotando una propiedad o getter con `@ignore`, puedes excluir dicha propiedad del almacenamiento, como se muestra en el siguiente extracto de código:
```dart
@collection
class User {
Id? id;
String? firstName;
String? lastName;
@ignore
String? password;
}
```
En los casos donde una colección hereda los campos de una colección padre, es generalmente más fácil usar la propiedad `ignore` de la anotación `@Collection`:
```dart
@collection
class User {
Image? profilePicture;
}
@Collection(ignore: {'profilePicture'})
class Member extends User {
Id? id;
String? firstName;
String? lastName;
}
```
Si una colección contiene un campo con un tipo de dato no soportado por Isar, éste campo debe ser ignorado.
:::warning
Ten en cuenta que no es una buena práctica guardar información en objectos Isar que no serán almacenados.
:::
## Tipos de datos soportados
Isar soporta los siguientes tipos de datos:
- `bool`
- `byte`
- `short`
- `int`
- `float`
- `double`
- `DateTime`
- `String`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
Adicionalmente, Isar soporta objetos embebidos y enums. Explicaremos éstos más adelante.
## byte, short, float
Para muchos casos de uso, no es necesario el rango completo de 64-bits de un entero o punto flotante (int o double). Isar contiene soporte para tipos adicionales que te permiten ahorrar espacio y memoria cuando se almacenan número más pequeños.
| Tipo | Tamaño en bytes | Rango |
| ---------- | --------------- | ------------------------------------------------------ |
| **byte** | 1 | 0 a 255 |
| **short** | 4 | -2,147,483,647 a 2,147,483,647 |
| **int** | 8 | -9,223,372,036,854,775,807 a 9,223,372,036,854,775,807 |
| **float** | 4 | -3.4e38 a 3.4e38 |
| **double** | 8 | -1.7e308 a 1.7e308 |
Los tipos numéricos adicionales con sólo aliases de los tipos de datos nativos de Dart, por lo que usar `short`, por ejemplo, funciona de la misma manera que usando `int`.
El siguiente es un ejemplo de una colección que contiene todos los tipos de datos vistos anteriormente:
```dart
@collection
class TestCollection {
Id? id;
late byte byteValue;
short? shortValue;
int? intValue;
float? floatValue;
double? doubleValue;
}
```
Todos los tipos numéricos también pueden ser usados en listas. Para almacenar bytes, deberías usar `List`.
## Tipos nulables
Entender cómo funciona la nulabilidad en Isar en esencial: los tipos numéricos **NO** tienen una representación `null` dedicada. Por el contrario, un valor específico es usado:
| Tipo | VM |
| ---------- | ------------- |
| **short** | `-2147483648` |
| **int** | `int.MIN` |
| **float** | `double.NaN` |
| **double** | `double.NaN` |
`bool`, `String`, y `List` tienen una representación `null` por separado.
Este comportamiento habilita mejoras en el rendimiento, y te permite cambiar libremente la nulabilidad de tus campos sin requerir una migración o código especial para lidiar con valores `null`.
:::warning
El tipo `byte` no soporta valores null.
:::
## DateTime
Isar no almacena información de zonas horarias en tus campos DateTime. En su lugar, convierte `DateTime`s a UTC antes de almacenarlos. Isar devuelve todas las fechas en hora local.
Los `DateTime`s se almacenan con presición de microsegundos. En navegadores web, sólo se soporta presición de milisegundos debido a una limitación de Javascript.
## Enum
Isar permite almacenar y usar enums como cualquier otro tipo de dato Isar. Sin embargo, tendrás que decidir cómo Isar debería representar el enum en el disco. Isar soporta cuatro estrategias diferentes:
| EnumType | Descripción |
| ----------- | ------------------------------------------------------------------------------------------------ |
| `ordinal` | El índice del emun se almacena como `byte`. Esto es muy eficiente pero no permite enums nulables |
| `ordinal32` | El índice del enum se almacena como `short` (entero de 4 bytes). |
| `name` | El nombre del enum se almacena como `String`. |
| `value` | Para recuperar el valor del enum se utiliza una propiedad personalizada. |
:::warning
`ordinal` y `ordinal32` dependen del orden de los valores en el enum. Si cambias el orden, bases de datos existentes retornarán valores incorrectos.
:::
Veamos un ejemplo para cada estrategia.
```dart
@collection
class EnumCollection {
Id? id;
@enumerated // same as EnumType.ordinal
late TestEnum byteIndex; // cannot be nullable
@Enumerated(EnumType.ordinal)
late TestEnum byteIndex2; // cannot be nullable
@Enumerated(EnumType.ordinal32)
TestEnum? shortIndex;
@Enumerated(EnumType.name)
TestEnum? name;
@Enumerated(EnumType.value, 'myValue')
TestEnum? myValue;
}
enum TestEnum {
first(10),
second(100),
third(1000);
const TestEnum(this.myValue);
final short myValue;
}
```
Por supuesto, los Enums pueden usarse también en listas.
## Objetos Embebidos
Con frecuencia es útil tener objetos anidados en tus colecciones. No hay límite en cuanto a la profundidad que un objeto anidado puede tener. Sin embargo, es necesario tener en cuenta que actualizar un objeto anidado requerirá escribir el árbol completo del objeto en la base de datos.
```dart
@collection
class Email {
Id? id;
String? title;
Recepient? recipient;
}
@embedded
class Recepient {
String? name;
String? address;
}
```
Los objectos embebidos pueden ser nulables y extender otros objectos. El único requerimiento es que sean anotados con `@embedded` y que tengan un constructor predeterminado sin parámetros requeridos.
================================================
FILE: docs/docs/es/transactions.md
================================================
---
title: Transacciones
---
# Transacciones
En Isar, las transacciones combinan múltiples operaciones en una sola unidad de trabajo. La mayoría de las interacciones con Isar utilizan transacciones de forma implícita. El acceso de lectura y escritura en Isar cumple está conforme con [ACID](https://es.wikipedia.org/wiki/ACID). Las transacciones se retroceden automáticamente en caso de error.
## Transacciones explícitas
En una transacción explícita, obtienes una instantánea consistente de la base de datos. Intenta minimizar la duración de las transacciones. En una transacción stá prohibido hacer llamadas de red u otras operaciones de largo procesamiento.
Las transacciones (especialmente las de escritura) tienen un costo, y siempre deberías agrupar operaciones sucesivas en una sola transacción.
Las transacciones puede ser tanto síncronas como asíncronas. En las transacciones síncronas, sólo puedes utilizar operaciones síncronas. En las transacciones asíncronas, sólo operaciones asíncronas.
| | Lectura | Lectura y Escritura |
| ---------- | ------------ | ------------------- |
| Síncronas | `.txnSync()` | `.writeTxnSync()` |
| Asíncronas | `.txn()` | `.writeTxn()` |
### Transacciones de lectura
Las transacciones explícitas de lectura son opcionales, pero te permiten hacer lecturas atómicas y confiar en que el estado de la base de datos dentro de la transacción será consistente. Internamente Isar utiliza transacciones de lectura implícitas para todas las operaciones de lectura.
:::tip
Las transacciones de lectura asíncronas se ejecutan en paralelo con otras transacciones de lectura y escritura. Genial verdad?
:::
### Transacciones de escritura
A diferencia de las operaciones de lectura, las operaciones de escritura en Isar deben ser agrupadas en una transacción explícita.
Cuando una transacción de escritura finaliza exitosamente, automáticamente es aplicada, y todos los cambios se escriben al disco. En case de error, se aborta y los cambios retroceden. Las transacciones son "todo o nada": o todas las escrituras de la transacción suceden, o ninguna de ellas tiene efecto, para garantizar la consitencia de los datos.
:::warning
Cuando una operación de la base de datos falla, la transacción se aborta y ya no debe ser utilizada. Incluso si capturaste el error en Dart.
:::
```dart
@collection
class Contact {
Id? id;
String? name;
}
// GOOD
await isar.writeTxn(() async {
for (var contact in getContacts()) {
await isar.contacts.put(contact);
}
});
// BAD: move loop inside transaction
for (var contact in getContacts()) {
await isar.writeTxn(() async {
await isar.contacts.put(contact);
});
}
```
================================================
FILE: docs/docs/es/tutorials/quickstart.md
================================================
---
title: Inicio rápido
---
# Inicio rápido
Increíble!, estás aquí! Vamos a empezar a usar la base de datos más genial que existe para Flutter...
Vamos a ser cortos en palabras para ir inmediatamente al código en esta guía de inicio rápido.
## 1. Agrega las dependencias
Antes de empezar la parte divertida, necesitamos agregar algunos paquetes al `pubspec.yaml`. Podemos usar pub para hacer el trabajo pesado por nosotros.
```bash
dart pub add isar:^0.0.0-placeholder isar_flutter_libs:^0.0.0-placeholder --hosted-url=https://pub.isar-community.dev
dart pub add dev:isar_generator:^0.0.0-placeholder --hosted-url=https://pub.isar-community.dev
```
## 2. Anota las clases
Anota tus clases de colecciones con `@collection` y elige un campo `Id`.
```dart
part 'user.g.dart';
@collection
class User {
Id id = Isar.autoIncrement; // you can also use id = null to auto increment
String? name;
int? age;
}
```
Los Ids identifican inequívocamente los objetos en una colección y te permiten luego buscarlos nuevamente.
## 3. Ejecuta el generador de código
Ejecuta el siguiente comando para iniciar el `build_runner`:
```
dart run build_runner build
```
Si estás usando Flutter, puedes usar el siguiente:
```
flutter pub run build_runner build
```
## 4. Abre una instancia Isar
Abre una nueva instalcia Isar y pásale todos los esquemas de tu colección. Opcionalmente puedes especificar un nombre para la instancia y un directorio.
```dart
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[UserSchema],
directory: dir.path,
);
```
## 5. Lee y escribe
Una vez que tu base de datos está abierta, puedes comenzar a usar tus colecciones.
Todas las operaciones CRUD básicas están disponibles a través del `IsarCollection`.
```dart
final newUser = User()..name = 'Jane Doe'..age = 36;
await isar.writeTxn(() async {
await isar.users.put(newUser); // insert & update
});
final existingUser = await isar.users.get(newUser.id); // get
await isar.writeTxn(() async {
await isar.users.delete(existingUser.id!); // delete
});
```
## Otros recursos
Gustas de aprender de manera visual? Dale un vistazo a estos videos para empezar con Isar (Advertencia, material en Inglés):
================================================
FILE: docs/docs/es/watchers.md
================================================
---
title: Watchers
---
# Watchers
Isar te permite suscribirte a los cambios en la base de datos. Puedes "observar" los cambios en un objeto específico, una colección entera, o una consulta.
Los watchers te permiten reaccionar a los cambios en la base de datos de manera eficiente. Puedes por ejemplo refrescar la interfaz de usuario cuando se agrega un contacto, enviar una consulta de red cuando un documento se actualiza, etc.
Un watcher es notificado después que una transacción finaliza exitosamente y el objeto realmente cambia.
## Observando objetos
Si quieres ser notificado cuando un objeto específico se crea, actualiza o elimina, debes "observar" un objeto:
```dart
Stream userChanged = isar.users.watchObject(5);
userChanged.listen((newUser) {
print('User changed: ${newUser?.name}');
});
final user = User(id: 5)..name = 'David';
await isar.users.put(user);
// prints: User changed: David
final user2 = User(id: 5)..name = 'Mark';
await isar.users.put(user);
// prints: User changed: Mark
await isar.users.delete(5);
// prints: User changed: null
```
Como puedes ver en el ejemplo anterior, el objeto no necesita existir aún. El watcher será notificado cuando se crea.
Existe un parámetro adicional `fireImmediately`. Si lo seteas en `true`, Isar agregará inmediatamente el valor actual del objeto al stream.
### Lazy watching
Tal vez no necesitas recibir el nuevo valor pero sólo ser notificado sobre el cambio. Esto evita que Isar tenga get obtener el objeto:
```dart
Stream userChanged = isar.users.watchObjectLazy(5);
userChanged.listen(() {
print('User 5 changed');
});
final user = User(id: 5)..name = 'David';
await isar.users.put(user);
// prints: User 5 changed
```
## Observando collections
En lugar de observar un solo objeto, puedes hacerlo con una colección completa y ser notificado cuando cualquier objeto se agrega, actualiza o elimina:
```dart
Stream userChanged = isar.users.watchLazy();
userChanged.listen(() {
print('A User changed');
});
final user = User()..name = 'David';
await isar.users.put(user);
// prints: A User changed
```
## Observando consultas
Incluso es posible observar consultas. Isar lo hace mejor incluso al notificarte sólo si el resultado de la consulta en realidad cambia. No serás notificado de los cambios provocados por un enlace. Observa una colección si quieres ser notificado acerca de los cambios en enlaces.
```dart
Query usersWithA = isar.users.filter()
.nameStartsWith('A')
.build();
Stream> queryChanged = usersWithA.watch(fireImmediately: true);
queryChanged.listen((users) {
print('Users with A are: $users');
});
// prints: Users with A are: []
await isar.users.put(User()..name = 'Albert');
// prints: Users with A are: [User(name: Albert)]
await isar.users.put(User()..name = 'Monika');
// no print
await isar.users.put(User()..name = 'Antonia');
// prints: Users with A are: [User(name: Albert), User(name: Antonia)]
```
:::warning
Si en tus consultas usas offset y límite o distinct, Isar incluso te notificará cuando los objetos coinciden con el filtro pero caen fuera de la consulta.
:::
Al igual que `watchObject()`, puedes usar `watchLazy()` para ser notificado cuando el resultado de la consulta cambia pero sin obtenerlos.
:::danger
Ejecutar consultas repetidamente para cada cambio es muy ineficiente. Sería mejor si usaras un watcher perezoso sobre la colección.
:::
================================================
FILE: docs/docs/faq.md
================================================
---
title: FAQ
---
# Frequently Asked Questions
A random collection of frequently asked questions about Isar and Flutter databases.
### Why do I need a database?
> I store my data in a backend database, why do I need Isar?.
Even today, it is very common to have no data connection if you are in a subway or a plane or if you visit your grandma, who has no Wi-Fi and a very bad cell signal. You shouldn't let bad connection cripple your app!
### Isar vs Hive
The answer is easy: Isar was [started as a replacement for Hive](https://github.com/hivedb/hive/issues/246) and is now at a state where I recommend always using Isar over Hive.
### Where clauses?!
> Why do **_I_** have to choose which index to use?
There are multiple reasons. Many databases use heuristics to choose the best index for a given query. The database needs to collect additional usage data (-> overhead) and might still choose the wrong index. It also makes creating a query slower.
Nobody knows your data better than you, the developer. So you can choose the optimal index and decide for example whether you want to use an index for querying or sorting.
### Do I have to use indexes / where clauses?
Nope! Isar is most likely fast enough if you only rely on filters.
### Is Isar fast enough?
Isar is among the fastest databases for mobile, so it should be fast enough for most use cases. If you run into performance issues, chances are that you are doing something wrong.
### Does Isar increase the size of my app?
A little bit, yes. Isar will increase the download size of your app by about 1 - 1.5 MB. Isar Web adds only a few KB.
### The docs are incorrect / there is a typo.
Oh no, sorry. Please [open an issue](https://github.com/isar-community/isar/issues/new/choose) or, even better, a PR to fix it 💪.
================================================
FILE: docs/docs/fr/README.md
================================================
---
home: true
title: Acceuil
heroImage: /isar.svg
actions:
- text: Commençons !
link: /fr/tutorials/quickstart.html
type: primary
features:
- title: 💙 Fait pour Flutter
details: Configuration minimale, facilité d'utilisation. Il suffit d'ajouter quelques lignes de code pour commencer.
- title: 🚀 Hautement extensible
details: Stockez des centaines de milliers d'entrées dans une seule base de données NoSQL et filtrer-les de manière efficace et asynchrone.
- title: 🍭 Riche en fonctionnalités
details: Isar dispose d'un riche ensemble de fonctionnalités pour vous aider à gérer vos données. Index composés et multi-entrées, modificateurs de requête, support JSON, etc.
- title: 🔎 Recherche plein texte
details: Isar dispose d'une recherche plein texte intégrée. Créez un index à entrées multiples et recherchez facilement des entrées.
- title: 🧪 Sémantique ACID
details: Isar est conforme à la norme ACID et gère les transactions automatiquement. Il annule les modifications si une erreur se produit.
- title: 💃 Types statiques
details: Les requêtes d'Isar sont typées statiquement et vérifiées à la compilation. Pas besoin de se soucier des erreurs d'exécution.
- title: 📱 Multiplatforme
details: Support pour iOS, Android, Desktop et WEB!
- title: ⏱ Asynchrone
details: Opérations de requête parallèles et support multi-Isolate prêts à l'emploi.
- title: 🦄 Open Source
details: Tout est open source et gratuit pour toujours!
footer: Apache Licensed | Copyright © 2022 Simon Leier
---
================================================
FILE: docs/docs/fr/crud.md
================================================
---
title: Création, lecture, modification, suppression
---
# Création, lecture, modification, suppression
Maintenant que nous avons défini nos collections, apprenons à les manipuler!
## Ouverture de Isar
Avant de pouvoir faire quoi que ce soit, nous avons besoin d'une instance Isar. Chaque instance nécessite un répertoire avec droits d'écriture où le fichier de la base de données peut être stocké. Si vous ne spécifiez pas de répertoire, Isar trouvera un répertoire par défaut selon la plateforme actuelle.
Fournissez tous les schémas que vous souhaitez utiliser avec l'instance Isar. Si nous ouvrons plusieurs instances, nous devons toujours fournir les mêmes schémas à chaque instance.
```dart
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[ContactSchema],
directory: dir.path,
);
```
Nous pouvons utiliser la configuration par défaut ou fournir certains des paramètres suivants:
| Config | Description |
|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `name` | Ouvrez plusieurs instances avec des noms distincts. Par défaut, `"default"` est utilisé. |
| `directory` | L'emplacement de stockage de cette instance. Nous pouvons passer un chemin relatif ou absolu. Par défaut, `NSDocumentDirectory` est utilisé pour iOS et `getDataDirectory` pour Android. Non requis pour Web. |
| `relaxedDurability` | Assouplit la garantie de durabilité pour augmenter les performances d'écriture. En cas de crash du système (pas de crash de l'application), il est possible de perdre la dernière transaction validée. La corruption n'est pas possible. |
| `compactOnLaunch` | Conditions pour vérifier si la base de données doit être compactée lors de l'ouverture de l'instance. |
| `inspector` | Active l'inspecteur en mode debug. Cette option est ignorée en mode profile et release. |
Si une instance est déjà ouverte, l'appel à `Isar.open()` donnera l'instance existante sans tenir compte des paramètres spécifiés. Utile pour utiliser Isar dans un isolat.
:::tip
Envisagez d'utiliser le package [path_provider](https://pub.dev/packages/path_provider) pour obtenir un chemin valide sur toutes les plateformes.
:::
L'emplacement de stockage du fichier de la base de données est `répertoire/nom.isar`.
## Lecture de la base de données
Utilisez les instances de `IsarCollection` pour trouver, filtrer et créer de nouveaux objets d'un type donné dans Isar.
Pour les exemples ci-dessous, nous supposons que nous avons une collection `Recipe` définie comme suit:
```dart
@collection
class Recipe {
Id? id;
String? name;
DateTime? lastCooked;
bool? isFavorite;
}
```
### Obtenir une collection
Toutes nos collections vivent dans l'instance Isar. Nous pouvons obtenir la collection avec:
```dart
final recipes = isar.recipes;
```
N'oubliez pas d'importer les méthodes d'extension afin d'accéder à la collection depuis l'instance isar.
C'était facile! Si vous ne voulez pas utiliser les accesseurs de collection, vous pouvez aussi utiliser la méthode `collection()`:
```dart
final recipes = isar.collection();
```
### Obtenir un objet (par id)
Nous n'avons pas encore de données dans la collection, mais faisons comme si c'était le cas afin de récupérer un objet imaginaire avec l'identifiant `123`.
```dart
final recipe = await recipes.get(123);
```
`get()` renvoie une `Future` avec soit l'objet, soit `null` s'il n'existe pas. Toutes les opérations d'Isar sont asynchrones par défaut, et la plupart d'entre elles ont un équivalent synchrone:
```dart
final recipe = recipes.getSync(123);
```
:::warning
Vous devriez utiliser la version asynchrone des méthodes par défaut dans votre isolat d'interface utilisateur. Comme Isar est très rapide, il est souvent acceptable d'utiliser la version synchrone.
:::
Si nous voulons récupérer plusieurs objets à la fois, nous pouvons utiliser `getAll()` ou `getAllSync()`:
```dart
final recipe = await recipes.getAll([1, 2]);
```
### Recherche d'objets
Au lieu de récupérer les objets par leur identifiant, nous pouvons également obtenir une liste d'objets répondant à certaines conditions en utilisant `.where()` et `.filter()`:
```dart
final allRecipes = await recipes.where().findAll();
final favouires = await recipes.filter()
.isFavoriteEqualTo(true)
.findAll();
```
➡️ En savoir plus: [Requêtes](queries)
## Modifier la base de données
Il est enfin temps de modifier notre collection! Pour créer, mettre à jour ou supprimer des objets, utilisez les opérations respectives dans une transaction d'écriture:
```dart
await isar.writeTxn(() async {
final recipe = await recipes.get(123)
recipe.isFavorite = false;
await recipes.put(recipe); // Effectuer des opérations de mise à jour
await recipes.delete(123); // Ou des opérations de suppression
});
```
➡️ En savoir plus: [Transactions](transactions)
### Insertion d'objets
Pour faire persister un objet dans Isar, insérons-le dans une collection. La méthode `put()` d'Isar va soit insérer, soit mettre à jour l'objet selon s'il existe déjà dans la collection ou non.
Si le champ id est `null` ou `Isar.autoIncrement`, Isar utilisera un id auto-incrémenté.
```dart
final pancakes = Recipe()
..name = 'Pancakes'
..lastCooked = DateTime.now()
..isFavorite = true;
await isar.writeTxn(() async {
await recipes.put(pancakes);
})
```
Isar attribuera automatiquement l'id à l'objet si le champ `id` est non final.
Il est tout aussi facile d'insérer plusieurs objets à la fois:
```dart
await isar.writeTxn(() async {
await recipes.putAll([pancakes, pizza]);
})
```
### Mise à jour d'objets
La création et la mise à jour fonctionnent toutes deux avec `collection.put(object)`. Si l'id est `null` (ou n'existe pas), l'objet est créé; sinon, il est mis à jour.
Donc si nous voulons défavoriser nos crêpes, nous pouvons faire ce qui suit:
```dart
await isar.writeTxn(() async {
pancakes.isFavorite = false;
await recipes.put(pancakes);
});
```
### Suppression d'objets
Vous voulez vous débarrasser d'un objet dans Isar ? Utilisez `collection.delete(id)`. La méthode `delete` retourne si un objet avec l'identifiant spécifié a été trouvé et supprimé. Si nous désirons supprimer l'objet avec l'identifiant `123`, par exemple, nous pouvons faire:
```dart
await isar.writeTxn(() async {
final success = await recipes.delete(123);
print('Recipe deleted: $success');
});
```
Comme pour les opérations `get` et `put`, il existe également une opération de suppression en vrac qui renvoie le nombre d'objets supprimés:
```dart
await isar.writeTxn(() async {
final count = await recipes.deleteAll([1, 2, 3]);
print('We deleted $count recipes');
});
```
Si nous ne connaissons pas les identifiants des objets que nous voulons supprimer, nous pouvons utiliser une requête:
```dart
await isar.writeTxn(() async {
final count = await recipes.filter()
.isFavoriteEqualTo(false)
.deleteAll();
print('We deleted $count recipes');
});
```
================================================
FILE: docs/docs/fr/faq.md
================================================
---
title: FAQ
---
# Foire aux questions
Une compilation de questions fréquemment posées sur les bases de données Isar et Flutter.
### Pourquoi ai-je besoin d'une base de données?
> Je stocke mes données dans une base de données backend, pourquoi ai-je besoin d'Isar?
Aujourd'hui encore, il est très courant de ne pas avoir de connexion internet si vous êtes dans le métro, dans l'avion, ou si vous rendez visite à votre grand-mère, qui n'a pas de WiFi et un très mauvais signal cellulaire. Vous ne devez pas laisser une mauvaise connexion paralyser votre application!
### Isar vs Hive
La réponse est simple: Isar a été [lancé comme un remplacement de Hive](https://github.com/hivedb/hive/issues/246) et est maintenant à un stade où on recommande de toujours utiliser Isar plutôt que Hive.
### Clauses `where`?!
> Pourquoi est-ce que **_je_** dois choisir quel index utiliser?
Il y a plusieurs raisons. De nombreuses bases de données utilisent des heuristiques pour choisir le meilleur index pour une requête donnée. La base de données doit collecter des données d'utilisation supplémentaires (-> temps de traitement plus grand) et peut toujours choisir le mauvais index. Cela rend également la création d'une requête plus lente.
Personne ne connaît mieux vos données que vous, le développeur. Vous pouvez donc choisir l'index optimal et décider, par exemple, si vous voulez utiliser un index pour la requête ou le tri.
### Dois-je utiliser des index / clauses `where`?
Non! Isar est très probablement assez rapide si vous ne comptez que sur les filtres.
### Isar est-il suffisamment rapide ?
Isar est l'une des bases de données les plus rapides pour les appareils mobiles, et devrait donc être suffisamment rapide pour la plupart des cas d'utilisation. Si vous rencontrez des problèmes de performances, il y a de fortes chances que vous fassiez quelque chose de mal.
### Isar augmente-t-il la taille de mon application?
Un peu, oui. Isar augmentera la taille de téléchargement de votre application d'environ 1 à 1,5 Mo. Isar Web n'ajoute que quelques Ko.
### La documentation est incorrecte / il y a une erreur de frappe.
Oh non, désolé. Veuillez [ouvrir un ticket](https://github.com/isar-community/isar/issues/new/choose) ou, mieux encore, un PR pour le résoudre 💪.
================================================
FILE: docs/docs/fr/indexes.md
================================================
---
title: Indices
---
# Indices
Les indices (`index`) sont la fonctionnalité la plus puissante d'Isar. De nombreuses bases de données embarquées proposent des index "normaux" (voire aucun), mais Isar dispose également d'index composés et à entrées multiples. Il est essentiel de comprendre le fonctionnement des index pour optimiser les performances des requêtes. Isar vous permet de choisir l'index que vous voulez utiliser et comment vous voulez l'utiliser. Nous allons commencer par une introduction rapide à ce que sont les index.
## Que sont les indices?
Lorsqu'une collection n'est pas indexée, l'ordre des lignes ne pourra probablement pas être discerné par la requête comme étant optimisé de quelconques manières, et votre requête devra donc rechercher les objets de façon linéaire. En d'autres termes, la requête devra parcourir chaque objet pour trouver ceux qui correspondent aux conditions. Comme vous pouvez l'imaginer, cela peut prendre du temps. La recherche dans chaque objet n'est pas très efficace.
Par exemple, cette collection `Product` est entièrement non ordonnée.
```dart
@collection
class Product {
Id? id;
late String name;
late int price;
}
```
**Données:**
| id | name | price |
|-----|-----------|-------|
| 1 | Book | 15 |
| 2 | Table | 55 |
| 3 | Chair | 25 |
| 4 | Pencil | 3 |
| 5 | Lightbulb | 12 |
| 6 | Carpet | 60 |
| 7 | Pillow | 30 |
| 8 | Computer | 650 |
| 9 | Soap | 2 |
Une requête qui tente de trouver tous les produits dont le prix est supérieur à 30 € doit parcourir les neuf rangées. Ce n'est pas un problème pour neuf lignes, mais cela peut le devenir pour 100 000 lignes.
```dart
final expensiveProducts = await isar.products.filter()
.priceGreaterThan(30)
.findAll();
```
Pour améliorer les performances de cette requête, nous indexons la propriété `price`. Un index est comme une table de recherche triée:
```dart
@collection
class Product {
Id? id;
late String name;
@Index()
late int price;
}
```
**Index généré:**
| price | id |
| -------------------- | ------------------ |
| 2 | 9 |
| 3 | 4 |
| 12 | 5 |
| 15 | 1 |
| 25 | 3 |
| 30 | 7 |
| **55** | **2** |
| **60** | **6** |
| **650** | **8** |
Maintenant, la requête peut être exécutée beaucoup plus rapidement. L'exécuteur peut directement sauter aux trois dernières lignes d'index et trouver les objets correspondants par leur id.
### Triage
Autre point intéressant: les index peuvent effectuer des tris très rapides. Les requêtes triées sont coûteuses, car la base de données doit charger tous les résultats en mémoire avant de les trier. Même si vous spécifiez un décalage ou une limite, ils sont appliqués après le tri.
Imaginons que nous voulions trouver les quatre produits les moins chers. Nous pourrions utiliser la requête suivante:
```dart
final cheapest = await isar.products.filter()
.sortByPrice()
.limit(4)
.findAll();
```
Dans cet exemple, la base de données devrait charger tous (!) les objets, les trier par prix et renvoyer les quatre produits dont le prix est le plus bas.
Comme vous pouvez probablement l'imaginer, cette opération peut être réalisée de manière beaucoup plus efficace avec l'index précédent. La base de données prend les quatre premières lignes de l'index et renvoie les objets correspondants puisqu'ils sont déjà dans le bon ordre.
Pour utiliser l'index pour le tri, nous devons écrire la requête comme suit:
```dart
final cheapestFast = await isar.products.where()
.anyPrice()
.limit(4)
.findAll();
```
La clause `where` `.anyX()` indique à Isar d'utiliser un index uniquement pour le tri. Nous pouvons également utiliser une clause `where` comme `.priceGreaterThan()` et obtenir des résultats triés.
## Indices uniques
Un index unique garantit que l'index ne contient pas de valeurs en double. Il peut être composé d'une ou plusieurs propriétés. Si un index unique a une propriété, les valeurs de cette propriété seront uniques. Si l'index unique a plus d'une propriété, la combinaison des valeurs dans ces propriétés est unique.
```dart
@collection
class User {
Id? id;
@Index(unique: true)
late String username;
late int age;
}
```
Toute tentative d'insertion ou de mise à jour de données dans l'index unique qui provoque un doublon entraînera une erreur:
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1); // -> ok
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
// Essayons d'insérer un utilisateur avec le même nom d'utilisateur
await isar.users.put(user2); // -> error: unique constraint violated
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
```
## Remplacement d'indices
Il n'est parfois pas préférable d'envoyer une erreur si une contrainte unique n'est pas respectée. Au lieu de cela, nous pouvons vouloir remplacer l'objet existant par le nouvel objet. Pour cela, il suffit de mettre la propriété `replace` de l'index à `true`.
```dart
@collection
class User {
Id? id;
@Index(unique: true, replace: true)
late String username;
}
```
Maintenant, lorsque nous essayons d'insérer un utilisateur avec un nom déjà existant, Isar va remplacer l'utilisateur existant par le nouveau.
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1);
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
print(await isar.user.where().findAll());
// > [{id: 2, username: 'user1' age: 30}]
```
Les indices de remplacement génèrent également des méthodes `putBy()` qui nous permettent de mettre à jour les objets au lieu de les remplacer. L'identifiant existant est réutilisé, et les liens sont toujours présents.
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
// L'utilisateur n'existe pas, donc c'est la même chose que put()
await isar.users.putByUsername(user1);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1' age: 30}]
```
Comme nous pouvons le constater, l'identifiant du premier utilisateur inséré est réutilisé.
## Index insensibles à la casse
Tous les index sur les propriétés `String` et `List` sont sensibles à la casse par défaut. Si nous voulons créer un index insensible à la casse, nous pouvons utiliser l'option `caseSensitive`:
```dart
@collection
class Person {
Id? id;
@Index(caseSensitive: false)
late String name;
@Index(caseSensitive: false)
late List tags;
}
```
## Type d'indice
Il existe différents types d'index. La plupart du temps, nous voudrons utiliser un index `IndexType.value`, mais les index de hachage sont plus efficaces.
### Index `value`
Les index de valeurs sont le type par défaut et le seul autorisé pour toutes les propriétés qui ne contiennent pas de chaînes de caractères ou de listes. Les valeurs des propriétés sont utilisées pour construire l'index. Dans le cas des listes, ce sont les éléments de la liste qui sont utilisés. Il s'agit du type d'index le plus flexible mais aussi le plus gourmand en espace parmi les trois types d'index.
:::tip
Utilisez `IndexType.value` pour les types primitifs, les chaînes de caractères lorsque vous avez besoin de clauses `startsWith()` et les listes si vous voulez rechercher des éléments individuels.
:::
### Index `hash`
Les chaînes de caractères et les listes peuvent être hachées pour réduire de manière significative le stockage requis par l'index. L'inconvénient des index de hachage est qu'ils ne peuvent pas être utilisés pour les scans de préfixe (clauses `where` `startsWith`).
:::tip
Utilisez `IndexType.hash` pour les chaînes de caractères et les listes si vous n'avez pas besoin des clauses `where` `startsWith` et `elementEqualTo`.
:::
### Index `hashElements`
Les listes de chaînes peuvent être hachées dans leur ensemble (à l'aide de `IndexType.hash`), ou les éléments de la liste peuvent être hachés séparément (à l'aide de `IndexType.hashElements`), créant ainsi un index à entrées multiples avec des éléments hachés.
:::tip
Utilisez `IndexType.hashElements` pour les `List` où vous avez besoin de clauses `where` `elementEqualTo`.
:::
## Indices composés
Un index composite est un index sur plusieurs propriétés. Isar nous permet de créer des index composites sur un maximum de trois propriétés.
Les index composés sont également connus sous le nom d'index à colonnes multiples.
Il est probablement préférable de commencer par un exemple. Nous créons une collection de personnes et définissons un index composé sur les propriétés âge et nom:
```dart
@collection
class Person {
Id? id;
late String name;
@Index(composite: [CompositeIndex('name')])
late int age;
late String hometown;
}
```
**Données:**
| id | name | age | hometown |
|-----|--------|-----|-----------|
| 1 | Daniel | 20 | Berlin |
| 2 | Anne | 20 | Paris |
| 3 | Carl | 24 | San Diego |
| 4 | Simon | 24 | Munich |
| 5 | David | 20 | New York |
| 6 | Carl | 24 | London |
| 7 | Audrey | 30 | Prague |
| 8 | Anne | 24 | Paris |
**Index généré:**
| age | name | id |
|-----|--------|-----|
| 20 | Anne | 2 |
| 20 | Daniel | 1 |
| 20 | David | 5 |
| 24 | Anne | 8 |
| 24 | Carl | 3 |
| 24 | Carl | 6 |
| 24 | Simon | 4 |
| 30 | Audrey | 7 |
L'indice composé généré contient toutes les personnes triées par leur âge et leur nom.
Les index composés sont parfaits si nous souhaitons créer des requêtes efficaces triées par plusieurs propriétés. Ils permettent également d'utiliser des clauses `where` avancées avec plusieurs propriétés :
```dart
final result = await isar.where()
.ageNameEqualTo(24, 'Carl')
.hometownProperty()
.findAll() // -> ['San Diego', 'London']
```
La dernière propriété d'un index composé supporte également des conditions telles que `startsWith()` ou `lessThan()` :
```dart
final result = await isar.where()
.ageEqualToNameStartsWith(20, 'Da')
.findAll() // -> [Daniel, David]
```
## Indices à entrées multiples
Si nous indexons une liste en utilisant `IndexType.value`, Isar va automatiquement créer un index à entrées multiples, et chaque élément de la liste est indexé vers l'objet. Cela fonctionne pour tous les types de listes.
Les applications pratiques des index à entrées multiples comprennent l'indexation d'une liste de balises ou la création d'un index en texte intégral.
```dart
@collection
class Product {
Id? id;
late String description;
@Index(type: IndexType.value, caseSensitive: false)
List get descriptionWords => Isar.splitWords(description);
}
```
`Isar.splitWords()` divise une chaîne de caractères en mots selon la spécification [Unicode Annex #29](https://unicode.org/reports/tr29/), ce qui fait qu'il fonctionne correctement pour presque toutes les langues.
**Data:**
| id | description | descriptionWords |
| --- | ---------------------------- | ---------------------------- |
| 1 | comfortable blue t-shirt | [comfortable, blue, t-shirt] |
| 2 | comfortable, red pullover!!! | [comfortable, red, pullover] |
| 3 | plain red t-shirt | [plain, red, t-shirt] |
| 4 | red necktie (super red) | [red, necktie, super, red] |
Les entrées comportant des mots en double n'apparaissent qu'une seule fois dans l'index.
**Index généré:**
| descriptionWords | id |
| ---------------- | --------- |
| comfortable | [1, 2] |
| blue | 1 |
| necktie | 4 |
| plain | 3 |
| pullover | 2 |
| red | [2, 3, 4] |
| super | 4 |
| t-shirt | [1, 3] |
Cet index peut maintenant être utilisé pour les clauses de préfixe (ou d'égalité) des mots individuels de la description.
:::tip
Au lieu de stocker les mots directement, vous pouvez également envisager d'utiliser le résultat d'un [algorithme phonétique](https://fr.wikipedia.org/wiki/Algorithme_phon%C3%A9tique) comme [Soundex](https://fr.wikipedia.org/wiki/Soundex).
:::
================================================
FILE: docs/docs/fr/limitations.md
================================================
# Limitations
Comme vous le savez, Isar fonctionne sur les appareils mobiles et les ordinateurs de bureau fonctionnant sur la VM ainsi que sur le Web. Ces deux plateformes sont très différentes, et ont donc des limitations différentes.
## Limitations de la VM
- Seuls les 1024 premiers octets d'une chaîne peuvent être utilisés pour un préfixe de clause `where`.
- Les objets ne peuvent avoir une taille supérieure à 16 Mo
## Limitations Web
Comme Isar Web est basé sur `IndexedDB`, il y a plus de limitations, mais elles sont à peine perceptibles lors de l'utilisation d'Isar.
- Les méthodes synchrones ne sont pas supportées
- Les filtres `Isar.splitWords()` et `.matches()` ne sont pas encore implémentés
- Les changements de schémas ne sont pas autant vérifiés que dans la VM, il faut donc faire attention à respecter les règles
- Tous les types de nombres sont stockés en tant que double (le seul type de nombre js), donc `@Size32` n'a aucun effet
- Les index sont représentés différemment, donc les index de hachage n'utilisent pas moins d'espace (ils fonctionnent toujours de la même manière)
- `col.delete()` et `col.deleteAll()` fonctionnent correctement, mais la valeur de retour n'est pas correcte
- `col.clear()` ne réinitialise pas la valeur d'auto-incrémentation
- `NaN` n'est pas supporté comme valeur
================================================
FILE: docs/docs/fr/links.md
================================================
---
title: Liens
---
# Liens
Les liens nous permettent d'exprimer des relations entre objets, comme l'auteur d'un commentaire (`User`). Nous pouvons modéliser des relations `1:1`, `1:n`, et `n:n` avec les liens Isar. L'utilisation de liens est moins ergonomique que l'utilisation d'objets embarqués. Il est donc préférable d'utiliser des objets embarqués lorsque possible.
Considérez le lien comme une table séparée qui contient la relation. Elle est similaire aux relations SQL, mais possède un ensemble de fonctionnalités et une API différente.
## IsarLink
`IsarLink` peut contenir un ou plusieurs objets liés et peut être utilisé pour exprimer une relation de type "un-à-un". `IsarLink` a une seule propriété appelée `value` qui contient l'objet lié.
Les liens ne sont pas chargés par default. Vous devez donc dire à `IsarLink` de charger ou de sauvegarder la `value` explicitement. Vous pouvez le faire en appelant `linkProperty.load()` et `linkProperty.save()`.
:::tip
La propriété `id` des collections source et cible d'un lien doit être non finale.
:::
Pour les plateformes autres que web, les liens sont chargés automatiquement lorsque vous les utilisez pour la première fois. Commençons par ajouter un `IsarLink` à une collection:
```dart
@collection
class Teacher {
Id? id;
late String subject;
}
@collection
class Student {
Id? id;
late String name;
final teacher = IsarLink();
}
```
Nous avons défini un lien entre les enseignants et les élèves. Dans cet exemple, chaque élève peut avoir exactement un professeur.
D'abord, nous créons le professeur et l'assignons à un étudiant. Nous devons `.put()` le professeur et sauvegarder le lien manuellement.
```dart
final mathTeacher = Teacher()..subject = 'Math';
final linda = Student()
..name = 'Linda'
..teacher.value = mathTeacher;
await isar.writeTxn(() async {
await isar.students.put(linda);
await isar.teachers.put(mathTeacher);
await linda.teacher.save();
});
```
Nous pouvons maintenant utiliser le lien :
```dart
final linda = await isar.students.where().nameEqualTo('Linda').findFirst();
final teacher = linda.teacher.value; // > Teacher(subject: 'Math')
```
Essayons la même chose avec du code synchrone. Nous n'avons pas besoin de sauvegarder le lien manuellement, car `.putSync()` sauvegarde automatiquement tous les liens. Il crée même le professeur pour nous.
```dart
final englishTeacher = Teacher()..subject = 'English';
final david = Student()
..name = 'David'
..teacher.value = englishTeacher;
isar.writeTxnSync(() {
isar.students.putSync(david);
});
```
## IsarLinks
Il serait plus logique que l'étudiant de l'exemple précédent puisse avoir plusieurs professeurs. Heureusement, Isar a `IsarLinks`, qui permet de contenir plusieurs objets liés et d'exprimer une relation de type "à plusieurs".
`IsarLinks` implémente `Set` et expose toutes les méthodes qui sont autorisées pour les ensembles.
`IsarLinks` se comporte comme `IsarLink` et n'est également pas changé par défaut. Pour charger tous les objets liés, nous devons utiliser `linkProperty.load()`. Pour persister les changements, `linkProperty.save()`.
La représentation interne de `IsarLink` et `IsarLinks` est la même. Nous pouvons faire évoluer le `IsarLink` d'avant en un `IsarLinks` pour assigner plusieurs professeurs à un seul étudiant (sans perdre de données).
```dart
@collection
class Student {
Id? id;
late String name;
final teachers = IsarLinks();
}
```
Cela fonctionne étant donné que nous n'avons pas changé le nom du lien (`teacher`), donc Isar s'en souvient d'avant.
```dart
final biologyTeacher = Teacher()..subject = 'Biology';
final linda = isar.students.where()
.filter()
.nameEqualTo('Linda')
.findFirst();
print(linda.teachers); // {Teacher('Math')}
linda.teachers.add(biologyTeacher);
await isar.writeTxn(() async {
await linda.teachers.save();
});
print(linda.teachers); // {Teacher('Math'), Teacher('Biology')}
```
## Backlinks
Je vous entends demander: "Et si nous voulions exprimer des relations inverses?". Ne vous inquiétez pas, nous allons maintenant introduire les `Backlinks`.
Les backlinks sont des liens en sens inverse. Chaque lien a toujours un backlink implicite. Nous pouvons le rendre disponible à notre application en annotant un `IsarLink` ou un `IsarLinks` avec `@Backlink()`.
Les backlinks ne nécessitent pas de mémoire ou de ressources supplémentaires; nous pouvons librement les ajouter, les supprimer et les renommer sans perdre de données.
Pour savoir quels sont les étudiants d'un enseignant spécifique, nous définissons donc un lien retour:
```dart
@collection
class Teacher {
Id id;
late String subject;
@Backlink(to: 'teacher')
final student = IsarLinks();
}
```
Il faut préciser le lien vers lequel pointe le backlink. Il est possible d'avoir plusieurs liens différents entre deux objets.
## Initialisation des liens
`IsarLink` et `IsarLinks` ont un constructeur sans argument, qui devrait être utilisé pour assigner la propriété de lien quand l'objet est créé. C'est une bonne pratique de rendre les propriétés de lien `final`.
Lorsque nous sauvegardons (`put()`) notre objet pour la première fois, le lien est initialisé avec la collection source et cible, et nous pouvons appeler des méthodes comme `load()` et `save()`. Un lien commence à suivre les changements immédiatement après sa création, donc nous pouvons ajouter et supprimer des relations avant même que le lien soit initialisé.
:::danger
Il est illégal de déplacer un lien vers un autre objet.
:::
================================================
FILE: docs/docs/fr/queries.md
================================================
---
title: Requêtes
---
# Requêtes
Les requêtes nous permettent de trouver des enregistrements correspondant à certaines conditions, par exemple:
- Trouver tous les contacts favoris.
- Trouver des prénoms distincts dans les contacts.
- Supprimez tous les contacts dont le nom de famille n'est pas défini.
Comme les requêtes sont exécutées sur la base de données et non dans Dart, elles sont très rapides. Si nous utilisons intelligemment les index, nous pouvons encore améliorer les performances des requêtes. Dans ce qui suit, nous apprendrons comment écrire des requêtes et comment les rendre le plus rapide possible.
Il existe deux méthodes différentes pour filtrer les enregistrements: les filtres et les indexes. Nous allons commencer par examiner le fonctionnement des filtres.
## Filtres
Les filtres sont faciles à utiliser et à comprendre. Selon le type des champs, il existe différentes opérations de filtrage disponibles, dont la plupart ont des noms explicites.
Les filtres fonctionnent en évaluant une expression pour chaque objet de la collection à filtrer. Si l'expression donne un résultat "vrai" (`true`), Isar l'inclura dans les résultats. Les filtres n'affectent pas l'ordre des résultats.
Nous utiliserons le modèle suivant pour les exemples ci-dessous:
```dart
@collection
class Shoe {
Id? id;
int? size;
late String model;
late bool isUnisex;
}
```
### Conditions de requête
Selon le type de champ, il existe différentes conditions.
| Condition | Description |
|--------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `.equalTo(value)` | Recherche les valeurs qui sont égales à `value`. |
| `.between(lower, upper)` | Recherche les valeurs qui se situent entre `lower` et `upper`. |
| `.greaterThan(bound)` | Recherche les valeurs qui sont supérieures à `bound`. |
| `.lessThan(bound)` | Recherche les valeurs qui sont inférieures à `bound`. Les valeurs `null` seront incluses par défaut car `null` est considéré comme plus petit que toute autre valeur. |
| `.isNull()` | Recherche les valeurs qui sont `null`. |
| `.isNotNull()` | Recherche les valeurs qui ne sont pas `null`. |
| `.length()` | Les requêtes sur la longueur des listes, Strings et liens filtrent les objets en fonction du nombre d'éléments dans une liste ou un lien. |
Supposons que la base de données contienne quatre chaussures de tailles 39, 40, 46 et une de taille non définie (`null`). Si nous n'effectuons pas de tri, les valeurs seront retournées trier par id.
```dart
isar.shoes.filter()
.sizeLessThan(40)
.findAll() // -> [39, null]
isar.shoes.filter()
.sizeLessThan(40, include: true)
.findAll() // -> [39, null, 40]
isar.shoes.filter()
.sizeBetween(39, 46, includeLower: false)
.findAll() // -> [40, 46]
```
### Opérateurs logiques
Nous pouvons composer des prédicats à l'aide des opérateurs logiques suivants:
| Opérateur | Description |
|------------|------------------------------------------------------------------------------|
| `.and()` | Évalue à `true` si les expressions de gauche et de droite évaluent à `true`. |
| `.or()` | Évalue à `true` si l'une des deux expressions évalue à `true`. |
| `.xor()` | Évalue à `true` si exactement une expression évalue à `true`. |
| `.not()` | Négativise le résultat de l'expression suivante. |
| `.group()` | Regroupe les conditions et permet de spécifier l'ordre d'évaluation. |
Si nous voulons trouver toutes les chaussures de taille 46, nous pouvons utiliser la requête suivante:
```dart
final result = await isar.shoes.filter()
.sizeEqualTo(46)
.findAll();
```
Si nous voulons utiliser plus d'une condition, nous pouvons combiner plusieurs filtres à l'aide du **et** (`.and()`) logique, **ou** (`.or()`) logique et **xor** (`.xor()`) logique.
```dart
final result = await isar.shoes.filter()
.sizeEqualTo(46)
.and() // Facultatif. Les filtres sont implicitement combinés avec des et logiques.
.isUnisexEqualTo(true)
.findAll();
```
Cette requête est équivalente à `size == 46 && isUnisex == true`.
Nous pouvons également regrouper des conditions en utilisant `.group()`:
```dart
final result = await isar.shoes.filter()
.sizeBetween(43, 46)
.and()
.group((q) => q
.modelNameContains('Nike')
.or()
.isUnisexEqualTo(false)
)
.findAll()
```
Cette requête est équivalente à `size >= 43 && size <= 46 && (modelName.contains('Nike') || isUnisex == false)`.
Pour nier une condition ou un groupe, utilisons l’opérateur logique **not** (`.not()`):
```dart
final result = await isar.shoes.filter()
.not().sizeEqualTo(46)
.and()
.not().isUnisexEqualTo(true)
.findAll();
```
Cette requête est équivalente à `size != 46 && isUnisex != true`.
### Conditions de chaîne de caractères
En plus des conditions de recherche ci-dessus, les valeurs de type `String` offrent quelques conditions supplémentaires que nous pouvons utiliser. Les caractères génériques de type Regex, par exemple, permettent une plus grande flexibilité dans la recherche.
| Condition | Description |
|----------------------|-----------------------------------------------------------------------|
| `.startsWith(value)` | Recherche les valeurs qui commencent par la valeur `value` fournie. |
| `.contains(value)` | Recherche les valeurs qui contiennent la valeur `value` fournie. |
| `.endsWith(value)` | Recherche les valeurs qui se terminent par la valeur `value` fournie. |
| `.matches(wildcard)` | Recherche les valeurs qui correspondent au motif `wildcard` fourni. |
**Sensibilité à la casse**
Toutes les opérations sur les chaînes de caractères ont un paramètre optionnel `caseSensitive` dont la valeur par défaut est `true`.
**Motifs:**
Une [expression de métacaractère](https://fr.wikipedia.org/wiki/M%C3%A9tacaract%C3%A8re) est une chaîne de caractères qui utilise des caractères normaux avec deux caractères génériques spéciaux:
- Le caractère générique `*` correspond à zéro ou plus de n'importe quel caractère.
- Le caractère générique `?` correspond à n'importe quel caractère.
Par exemple, la chaîne générique `"d?g"` correspond à `"dog"`, `"dig"` et `"dug"`, mais pas à `"ding"`, `"dg"` ou `"a dog"`.
### Modificateurs de requête
Il est parfois nécessaire de construire une requête basée sur certaines conditions ou pour différentes valeurs. Isar dispose d'un outil très puissant pour construire des requêtes conditionnelles:
| Modificateur | Description |
|-----------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `.optional(cond, qb)` | Étend la requête uniquement si la `condition` est `true`. Cela peut être utilisé presque partout dans une requête, par exemple pour la trier ou la limiter de manière conditionnelle. |
| `.anyOf(list, qb)` | Étend la requête pour chaque valeur de `values` et combine les conditions en utilisant l’opérateur **ou**. |
| `.allOf(list, qb)` | Étend la requête pour chaque valeur de `values` et combine les conditions en utilisant les **et** logiques. |
Dans cet exemple, nous construisons une méthode qui trouve des chaussures avec un filtre optionnel:
```dart
Future> findShoes(Id? sizeFilter) {
return isar.shoes.filter()
.optional(
sizeFilter != null, // Seulement appliquer le filtre si sizeFilter != null
(q) => q.sizeEqualTo(sizeFilter!),
).findAll();
}
```
Si nous voulons trouver toutes les chaussures qui ont une ou plusieurs tailles, nous pouvons soit écrire une requête classique, soit utiliser le modificateur `anyOf()`:
```dart
final shoes1 = await isar.shoes.filter()
.sizeEqualTo(38)
.or()
.sizeEqualTo(40)
.or()
.sizeEqualTo(42)
.findAll();
final shoes2 = await isar.shoes.filter()
.anyOf(
[38, 40, 42],
(q, int size) => q.sizeEqualTo(size)
).findAll();
// shoes1 == shoes2
```
Les modificateurs de requête sont particulièrement utiles lorsque nous souhaitons construire des requêtes dynamiques.
### Listes
Même les listes peuvent être utilisées dans les requêtes:
```dart
class Tweet {
Id? id;
String? text;
List hashtags = [];
}
```
Nous pouvons effectuer des requêtes en fonction de la longueur de la liste:
```dart
final tweetsWithoutHashtags = await isar.tweets.filter()
.hashtagsIsEmpty()
.findAll();
final tweetsWithManyHashtags = await isar.tweets.filter()
.hashtagsLengthGreaterThan(5)
.findAll();
```
Ces requêtes sont équivalentes au code Dart `tweets.where((t) => t.hashtags.isEmpty)` et `tweets.where((t) => t.hashtags.length > 5)`. Nous pouvons également effectuer des requêtes sur les éléments de la liste:
```dart
final flutterTweets = await isar.tweets.filter()
.hashtagsElementEqualTo('flutter')
.findAll();
```
Cette requête est équivalente au code Dart `tweets.where((t) => t.hashtags.contains('flutter'))`.
### Objets embarqués
Les objets embarqués sont l'une des fonctionnalités les plus utiles d'Isar. Ils peuvent être filtrés très efficacement en utilisant les mêmes conditions que celles disponibles pour les objets de niveau supérieur. Supposons que nous ayons le modèle suivant:
```dart
@collection
class Car {
Id? id;
Brand? brand;
}
@embedded
class Brand {
String? name;
String? country;
}
```
Nous voulons filtrer toutes les voitures qui ont une marque avec le nom `"BMW"` et le pays `"Allemagne"`. Nous pouvons le faire en utilisant la requête suivante:
```dart
final germanCars = await isar.cars.filter()
.brand((q) => q
.nameEqualTo('BMW')
.and()
.countryEqualTo('Germany')
).findAll();
```
Essayez toujours de regrouper les requêtes imbriquées. La requête ci-dessus est plus efficace que la suivante, même si le résultat est le même:
```dart
final germanCars = await isar.cars.filter()
.brand((q) => q.nameEqualTo('BMW'))
.and()
.brand((q) => q.countryEqualTo('Germany'))
.findAll();
```
### Liens
Si nos modèles contiennent des [liens](links), nous pouvons filtrer sur les objets liés ou le nombre d'objets liés.
:::warning
Gardez en tête que les requêtes de liens peuvent être coûteuses, car Isar doit rechercher les objets liés. Pensez à utiliser des objets embarqués à la place.
:::
```dart
@collection
class Teacher {
Id? id;
late String subject;
}
@collection
class Student {
Id? id;
late String name;
final teachers = IsarLinks();
}
```
Nous voulons trouver tous les élèves qui ont un professeur de mathématiques ou d'anglais:
```dart
final result = await isar.students.filter()
.teachers((q) {
return q.subjectEqualTo('Math')
.or()
.subjectEqualTo('English');
}).findAll();
```
Les filtres de liens sont évalués à `true` si au moins un objet lié correspond aux conditions.
Cherchons tous les élèves qui n'ont pas de professeur:
```dart
final result = await isar.students.filter().teachersLengthEqualTo(0).findAll();
```
ou sinon:
```dart
final result = await isar.students.filter().teachersIsEmpty().findAll();
```
## Clauses `Where`
Les clauses `where` sont un outil très puissant, mais il n'est pas toujours facile de les utiliser correctement.
Contrairement aux filtres, les clauses `where` utilisent les index que nous définissons dans le schéma pour évaluer les conditions de la requête. La requête d'un index est beaucoup plus rapide que le filtrage individuel de chaque entrée.
➡️ En savoir plus: [Indices](indexes)
:::tip
En règle générale, vous devriez toujours essayer de réduire les entrées autant que possible à l'aide de clauses `where`, et effectuer le reste du filtrage à l'aide de filtres.
:::
Nous pouvons uniquement combiner les clauses `where` en utilisant des **ou** logiques. En d'autres termes, nous pouvons additionner plusieurs clauses `where`, mais nous ne pouvons pas effectuer une requête sur l'intersection de plusieurs clauses `where`.
Ajoutons des index à la collection `Shoe`:
```dart
@collection
class Shoe with IsarObject {
Id? id;
@Index()
Id? size;
late String model;
@Index(composite: [CompositeIndex('size')])
late bool isUnisex;
}
```
Il y a deux index. L'index sur `size` nous permet d'utiliser des clauses `where` comme `.sizeEqualTo()`. L'index composé sur `isUnisex` permet d'utiliser des clauses `where` comme `isUnisexSizeEqualTo()`, mais aussi `isUnisexEqualTo()`, car nous pouvons toujours utiliser n'importe quel préfixe d'un index.
Nous pouvons maintenant réécrire la requête précédente qui trouve des chaussures unisexes de taille 46 en utilisant l'index composé. Cette requête sera beaucoup plus rapide que la précédente:
```dart
final result = isar.shoes.where()
.isUnisexSizeEqualTo(true, 46)
.findAll();
```
Les clauses `where` ont deux autres superpouvoirs: elles vous offrent un tri "gratuit" et une opération distincte super rapide.
### Combinaison de clauses `where` et de filtres
Vous vous souvenez des requêtes `shoes.filter()`? Il s'agit en fait d'un raccourci pour `shoes.where().filter()`. Nous pouvons (et devrions) combiner les clauses `where` et les filtres dans une même requête pour bénéficier des avantages des deux:
```dart
final result = isar.shoes.where()
.isUnisexEqualTo(true)
.filter()
.modelContains('Nike')
.findAll();
```
La clause `where` est d'abord appliquée pour réduire le nombre d'objets à filtrer. Ensuite, le filtre est appliqué aux objets restants.
## Triage
Nous pouvons définir comment les résultats doivent être triés lors de l'exécution de la requête en utilisant les méthodes `.sortBy()`, `.sortByDesc()`, `.thenBy()` et `.thenByDesc()`.
Pour trouver toutes les chaussures triées par nom de modèle en ordre croissant et par taille en ordre décroissant sans utiliser d'index:
```dart
final sortedShoes = isar.shoes.filter()
.sortByModel()
.thenBySizeDesc()
.findAll();
```
Le tri de nombreux résultats peut s'avérer coûteux, d'autant plus que le tri intervient avant le `offset` et `limit`. Les méthodes de tri ci-dessus ne font jamais appel aux index. Heureusement, nous pouvons à nouveau utiliser le tri par clause `where` et rendre notre requête rapide comme l'éclair, même si nous devons trier un million d'objets.
### Tri par clause `where`
Si nous utilisons une clause `where` **simple** dans notre requête, les résultats sont déjà triés par l'index. Ce n'est pas rien!
Supposons que nous avons des chaussures de taille `[43, 39, 48, 40, 42, 45]` et que nous voulons trouver toutes les chaussures dont la taille est supérieure à `42` et les trier par taille:
```dart
final bigShoes = isar.shoes.where()
.sizeGreaterThan(42) // Trie également les résultats par taille
.findAll(); // -> [43, 45, 48]
```
Comme nous pouvons le constater, le résultat est trié par l'index `size`. Si nous voulons inverser l'ordre de tri de la clause `where`, nous pouvons donner à `sort` la valeur `Sort.desc` :
```dart
final bigShoesDesc = await isar.shoes.where(sort: Sort.desc)
.sizeGreaterThan(42)
.findAll(); // -> [48, 45, 43]
```
Parfois, nous ne voulons pas utiliser des clauses `where`, mais nous pouvons tout de même bénéficier du tri implicite. Nous pouvons utiliser la clause `where` `any`:
```dart
final shoes = await isar.shoes.where()
.anySize()
.findAll(); // -> [39, 40, 42, 43, 45, 48]
```
Si nous utilisons un index composé, les résultats sont triés par tous les champs de l'index.
:::tip
Si vous avez besoin que les résultats soient triés, pensez à utiliser un index dans ce but. Surtout si vous utilisez avec `offset()` et `limit()`.
:::
Parfois, il n'est pas possible ou utile d'utiliser un index pour le tri. Dans ce cas, nous devons utiliser des index pour réduire autant que possible le nombre d'entrées résultantes.
## Valuers uniques
Pour ne renvoyer que les entrées ayant des valeurs uniques, utilisez le prédicat `distinct`. Par exemple, pour savoir combien de modèles de chaussures différents nous avons dans votre base de données Isar:
```dart
final shoes = await isar.shoes.filter()
.distinctByModel()
.findAll();
```
Nous pouvons également chaîner plusieurs conditions distinctes pour trouver toutes les chaussures avec des combinaisons modèle-taille distinctes:
```dart
final shoes = await isar.shoes.filter()
.distinctByModel()
.distinctBySize()
.findAll();
```
Seul le premier résultat de chaque combinaison distincte est retourné. Nous pouvons utiliser des clauses `where` et des opérations de tri pour le contrôler.
### Clause `where` distincte
Si nous avons un index non-unique, nous pouvons vouloir obtenir toutes ses valeurs distinctes. Nous pouvons utiliser l'opération `distinctBy` de la section précédente, mais elle est effectuée après le tri et les filtres, ce qui entraîne une certaine lourdeur.
Si nous n'utilisons qu'une seule clause `where`, nous pouvons nous fier à l'index pour effectuer l'opération de distinction.
```dart
final shoes = await isar.shoes.where(distinct: true)
.anySize()
.findAll();
```
:::tip
En théorie, nous pouvons même utiliser plusieurs clauses `where` pour le tri et la distinction. La seule restriction est que ces clauses `where` ne doivent pas se chevaucher et utiliser le même index. Pour un tri correct, elles doivent également être appliquées dans l'ordre de tri. Soyez très prudent si vous vous fiez à cela!
:::
## Décalage et limite
C'est souvent une bonne idée de limiter le nombre de résultats d'une requête pour les listes "lazy". Nous pouvons le faire en définissant un `limit()`:
```dart
final firstTenShoes = await isar.shoes.where()
.limit(10)
.findAll();
```
En définissant un `offset()`, nous pouvons également paginer les résultats de notre requête.
```dart
final firstTenShoes = await isar.shoes.where()
.offset(20)
.limit(10)
.findAll();
```
L'instanciation des objets Dart étant souvent la partie la plus coûteuse de l'exécution d'une requête, il est judicieux de ne charger que les objets dont nous avons de besoin.
## Ordre d'exécution
Isar exécute les requêtes toujours dans le même ordre :
1. Traverser l'index primaire ou secondaire pour trouver des objets (appliquer des clauses `where`)
2. Filtrer les objets
3. Trier les résultats
4. Appliquer l'opération distincte
5. Décalage et limite des résultats
6. Retour des résultats
## Opérations de requêtes
Dans les exemples précédents, nous avons utilisé `.findAll()` pour récupérer tous les objets correspondants. Cependant, d'autres opérations sont disponibles:
| Opération | Description |
|------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `.findFirst()` | Retourne seulement le premier objet correspondant ou `null` si aucun ne correspond. |
| `.findAll()` | Retourne tous les objets correspondants. |
| `.count()` | Compte le nombre d'objets correspondant à la requête. |
| `.deleteFirst()` | Supprime le premier objet correspondant de la collection. |
| `.deleteAll()` | Supprime tous les objets correspondants de la collection. |
| `.build()` | Compile la requête pour la réutiliser plus tard. Cela permet d'économiser le coût de construction d'une requête si nous souhaitons l'exécuter plusieurs fois. |
## Requêtes de propriété
Si nous ne sommes intéressés que par les valeurs d'une seule propriété, nous pouvons utiliser une requête de propriété. Il suffit de construire une requête ordinaire et de sélectionner une propriété:
```dart
List models = await isar.shoes.where()
.modelProperty()
.findAll();
List sizes = await isar.shoes.where()
.sizeProperty()
.findAll();
```
L'utilisation d'une seule propriété permet de gagner du temps lors de la désérialisation. Les requêtes de propriétés fonctionnent également pour les objets embarqués et les listes.
## Agrégation
Isar supporte l'agrégation des valeurs d'une requête de propriété. Les opérations d'agrégation disponibles sont les suivantes :
| Opération | Description |
|--------------|----------------------------------------------------------------------------|
| `.min()` | Trouve la valeur minimale ou `null` si aucune ne correspond. |
| `.max()` | Trouve la valeur maximale ou `null` si aucune ne correspond. |
| `.sum()` | Additionne toutes les valeurs. |
| `.average()` | Calcule la moyenne de toutes les valeurs ou `NaN` si aucune ne correspond. |
L'utilisation des agrégations est beaucoup plus rapide que la recherche de tous les objets correspondants et l'exécution manuelle de l'agrégation.
## Requêtes dynamiques
:::danger
Cette section n'est probablement pas pertinente pour vous. Il est déconseillé d'utiliser des requêtes dynamiques, sauf si vous en avez absolument besoin (ce qui est rarement le cas).
:::
Tous les exemples ci-dessus ont utilisé le `QueryBuilder` et les méthodes d'extension statiques générées. Peut-être voulez-vous créer des requêtes dynamiques ou un langage de requête personnalisé (comme l'inspecteur Isar). Dans ce cas, nous pouvons utiliser la méthode `buildQuery()` :
| Paramètre | Description |
|-----------------|----------------------------------------------------------------------------------------------------------------------|
| `whereClauses` | Les clauses `where` de la requête. |
| `whereDistinct` | Si les clauses `where` doivent retourner des valeurs distinctes (utile uniquement pour les clauses `where` uniques). |
| `whereSort` | L'ordre de passage des clauses `where` (utile uniquement pour les clauses `where` uniques). |
| `filter` | Le filtre à appliquer aux résultats. |
| `sortBy` | Une liste de propriétés à trier. |
| `distinctBy` | Une liste de propriétés à distinguer par. |
| `offset` | Le décalage des résultats. |
| `limit` | Le nombre maximum de résultats à retourner. |
| `property` | Si non-nulle, seules les valeurs de cette propriété sont renvoyées. |
Créons une requête dynamique:
```dart
final shoes = await isar.shoes.buildQuery(
whereClauses: [
WhereClause(
indexName: 'size',
lower: [42],
includeLower: true,
upper: [46],
includeUpper: true,
)
],
filter: FilterGroup.and([
FilterCondition(
type: ConditionType.contains,
property: 'model',
value: 'nike',
caseSensitive: false,
),
FilterGroup.not(
FilterCondition(
type: ConditionType.contains,
property: 'model',
value: 'adidas',
caseSensitive: false,
),
),
]),
sortBy: [
SortProperty(
property: 'model',
sort: Sort.desc,
)
],
offset: 10,
limit: 10,
).findAll();
```
La requête suivante est équivalente:
```dart
final shoes = await isar.shoes.where()
.sizeBetween(42, 46)
.filter()
.modelContains('nike', caseSensitive: false)
.not()
.modelContains('adidas', caseSensitive: false)
.sortByModelDesc()
.offset(10).limit(10)
.findAll();
```
================================================
FILE: docs/docs/fr/recipes/data_migration.md
================================================
---
title: Migration des données
---
# Migration des données
Isar migre automatiquement les schémas de notre base de données si nous ajoutons ou supprimons des collections, champs ou index. Il peut arriver que nous souhaitions également migrer des données. Isar n'offre pas de solution intégrée, car cela imposerait des restrictions de migration arbitraires. Il est facile d'implémenter une logique de migration adaptée à nos besoins.
Dans cet exemple, nous voulons utiliser une seule version pour l'ensemble de la base de données. Nous utilisons `shared_preferences` pour stocker la version actuelle et la comparer à la version vers laquelle nous désirons migrer. Si les versions ne correspondent pas, nous migrons les données et mettons à jour la version.
:::tip
Vous pouvez également donner à chaque collection sa propre version et les migrer individuellement.
:::
Imaginons que nous avons une collection d'utilisateurs avec un champ d'anniversaire. Dans la version 2 de notre application, nous avons besoin d'un champ supplémentaire pour l'année de naissance afin de rechercher des utilisateurs en fonction de leur âge.
Version 1:
```dart
@collection
class User {
Id? id;
late String name;
late DateTime birthday;
}
```
Version 2:
```dart
@collection
class User {
Id? id;
late String name;
late DateTime birthday;
short get birthYear => birthday.year;
}
```
Le problème est que les modèles d'utilisateurs existants auront un champ `birthYear` vide, car il n'existait pas dans la version 1. Nous devons migrer les données pour définir le champ `birthYear`.
```dart
import 'package:isar/isar.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() async {
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[UserSchema],
directory: dir.path,
);
await performMigrationIfNeeded(isar);
runApp(MyApp(isar: isar));
}
Future performMigrationIfNeeded(Isar isar) async {
final prefs = await SharedPreferences.getInstance();
final currentVersion = prefs.getInt('version') ?? 2;
switch(currentVersion) {
case 1:
await migrateV1ToV2(isar);
break;
case 2:
// Si la version n'est pas définie (nouvelle installation) ou si elle est déjà à 2, il n'est pas nécessaire de migrer.
return;
default:
throw Exception('Unknown version: $currentVersion');
}
// Mise à jour de la version
await prefs.setInt('version', 2);
}
Future migrateV1ToV2(Isar isar) async {
final userCount = await isar.users.count();
// Nous paginons à travers les utilisateurs pour éviter de tous les charger en mémoire en même temps
for (var i = 0; i < userCount; i += 50) {
final users = await isar.users.where().offset(i).limit(50).findAll();
await isar.writeTxn((isar) async {
// Nous n'avons pas besoin de mettre à jour quoi que ce soit puisque le `getter` `birthYear` est utilisé.
await isar.users.putAll(users);
});
}
}
```
:::warning
Si vous devez migrer un grand nombre de données, envisagez d'utiliser un isolat en arrière plan pour éviter de surcharger le thread de l'interface utilisateur.
:::
================================================
FILE: docs/docs/fr/recipes/full_text_search.md
================================================
---
title: Recherche plein texte
---
# Recherche plein texte
La recherche plein texte est un moyen puissant de rechercher du texte dans la base de données. Vous devriez déjà être familiarisé avec le fonctionnement des [indices](../indexes), mais passons en revue les principes de base.
Un index fonctionne comme une table de recherche, permettant au moteur de recherche de trouver rapidement les enregistrements ayant une valeur donnée. Par exemple, si nous avons un champ "titre" dans notre objet, nous pouvons créer un index sur ce champ afin de trouver plus rapidement les objets ayant un titre donné.
## Pourquoi la recherche plein texte est-elle utile?
Nous pouvons facilement rechercher du texte en utilisant des filtres. Il existe plusieurs opérations de chaînes de caractères, par exemple `.startsWith()`, `.contains()` et `.matches()`. Le problème avec les filtres est que leur temps d'exécution est de `O(n)`, où `n` est le nombre d'enregistrements dans la collection. Les opérations sur chaînes de caractères comme `.matches()` sont particulièrement coûteuses.
:::tip
La recherche plein texte est beaucoup plus rapide que les filtres, mais les index ont certaines limites. Dans cette recette, nous allons explorer comment contourner ces limites.
:::
## Exemple de base
L'idée est toujours la même: au lieu d'indexer l'ensemble du texte, nous indexons les mots du texte afin de pouvoir les rechercher individuellement.
Créons l'index plein texte le plus basique:
```dart
class Message {
Id? id;
late String content;
@Index()
List get contentWords => content.split(' ');
}
```
Nous pouvons maintenant rechercher des messages dont le contenu contient des mots spécifiques:
```dart
final posts = await isar.messages
.where()
.contentWordsAnyEqualTo('hello')
.findAll();
```
Cette requête est super rapide, mais il y a quelques problèmes:
1. Nous ne pouvons rechercher que des mots entiers
2. Nous ne tenons pas compte de la ponctuation
3. Nous ne prenons pas en charge les autres caractères d'espacement
## Diviser le texte de la bonne manière
Essayons d'améliorer l'exemple précédent. Nous pourrions essayer de développer une regex compliquée pour corriger le découpage de mots, mais cela sera probablement lent et incorrect dans certains cas.
Le [Unicode Annex #29](https://unicode.org/reports/tr29/) définit comment diviser correctement un texte en mots pour presque toutes les langues. C'est assez compliqué, mais heureusement, Isar fait le gros du travail pour nous:
```dart
Isar.splitWords('hello world'); // -> ['hello', 'world']
Isar.splitWords('The quick (“brown”) fox can’t jump 32.3 feet, right?');
// -> ['The', 'quick', 'brown', 'fox', 'can’t', 'jump', '32.3', 'feet', 'right']
```
## Je veux plus de contrôle
C'est simple et facile! Nous pouvons également modifier notre index pour supporter la comparaison des préfixes et la correspondance insensible à la casse:
```dart
class Post {
Id? id;
late String title;
@Index(type: IndexType.value, caseSensitive: false)
List get titleWords => title.split(' ');
}
```
Par défaut, Isar stocke les mots sous forme de valeurs hachées, ce qui est rapide et peu encombrant. Mais les valeurs hachées ne peuvent pas être utilisées pour la comparaison des préfixes. En utilisant `IndexType.value`, nous pouvons changer l'index pour utiliser directement les mots à la place. Cela nous donne la clause `where` `.titleWordsAnyStartsWith()`:
```dart
final posts = await isar.posts
.where()
.titleWordsAnyStartsWith('hel')
.or()
.titleWordsAnyStartsWith('welco')
.or()
.titleWordsAnyStartsWith('howd')
.findAll();
```
## Je veux aussi `.endsWith()`
Bien sûr! Nous allons utiliser une astuce pour réaliser la comparaison `.endsWith()`:
```dart
class Post {
Id? id;
late String title;
@Index(type: IndexType.value, caseSensitive: false)
List get revTitleWords {
return Isar.splitWords(title).map(
(word) => word.reversed).toList()
);
}
}
```
N'oublions pas d'inverser la terminaison que nous voulons rechercher:
```dart
final posts = await isar.posts
.where()
.revTitleWordsAnyStartsWith('lcome'.reversed)
.findAll();
```
## Algorithmes de racinisation (`stemming`)
Malheureusement, les index ne supportent pas la comparaison `.contains()` (ceci est vrai pour d'autres bases de données également). Mais il y a quelques alternatives qui valent la peine d'être explorées. Le choix dépend fortement de votre utilisation. Un exemple est l'indexation des racines de mots au lieu du mot entier.
Un algorithme de racinisation est un processus de normalisation linguistique dans lequel les différentes formes d'un mot sont réduites à une forme commune :
```
connexion
connexions
connectif ---> connect
connecté
connecter
```
Les algorithmes les plus populaires sont [Porter stemming algorithm](https://tartarus.org/martin/PorterStemmer/) et [Snowball stemming algorithms](https://snowballstem.org/algorithms/).
Il existe également des formes plus avancées comme la [Lemmatisation](https://fr.wikipedia.org/wiki/Lemmatisation).
## Algorithmes phonétiques
Un [algorithme phonétique](https://fr.wikipedia.org/wiki/Algorithme_phon%C3%A9tique) est un algorithme permettant d'indexer les mots en fonction de leur prononciation. En d'autres termes, il nous permet de trouver des mots dont la sonorité est similaire à celle des mots que nous voulons recherchez.
:::warning
La plupart des algorithmes phonétiques ne supportent qu'une seule langue.
:::
### Soundex
[Soundex](https://fr.wikipedia.org/wiki/Soundex) est un algorithme phonétique d'indexation des noms par le son, tel qu'il est prononcé en anglais. Le but est que les homophones soient encodés dans la même représentation, afin qu'ils puissent être mis en relation malgré des différences mineures dans l'orthographe. Il s'agit d'un algorithme simple, et il existe de nombreuses versions améliorées.
En utilisant cet algorithme, `"Robert"` et `"Rupert"` renvoient tous deux la chaîne `"R163"`, tandis que `"Rubin"` donne `"R150"`. `"Ashcraft"` et `"Ashcroft"` donnent tous deux `"A261"`.
### Double Metaphone
L'algorithme de codage phonétique [Double Metaphone](https://fr.wikipedia.org/wiki/Metaphone) est la deuxième génération de cet algorithme. Il apporte plusieurs améliorations fondamentales à la conception de l'algorithme Metaphone original.
Double Metaphone prend en compte diverses irrégularités de l'anglais d'origine slave, germanique, celtique, grecque, française, italienne, espagnole, chinoise et autres.
================================================
FILE: docs/docs/fr/recipes/multi_isolate.md
================================================
---
title: Utilisation multi-isolats
---
# Utilisation multi-isolats
Au lieu de threads, tout code Dart s'exécute dans des isolats. Chaque isolat possède son propre espace mémoire, ce qui garantit qu'aucun des états d'un isolat n'est accessible depuis un autre isolat.
Il est possible d'accéder à Isar à partir de plusieurs isolats en même temps. Même les observateurs fonctionnent à travers les isolats. Dans cette recette, nous allons voir comment utiliser Isar dans un environnement multi-isolats.
## Quand utiliser plusieurs isolats
Les transactions Isar sont exécutées en parallèle, même si elles sont exécutées dans le même isolat. Dans certains cas, il est toujours utile d'accéder à Isar à partir de plusieurs isolats.
La raison en est qu'Isar passe un certain temps à encoder et décoder des données depuis et vers des objets Dart. Nous pouvons imaginer que c'est comme coder et décoder en JSON (en plus efficace). Ces opérations s'exécutent à l'intérieur de l'isolat à partir duquel on accède aux données et bloquent naturellement les autres codes de l'isolat. En d'autres termes: Isar effectue une partie du travail dans votre isolat Dart.
Si nous n'avons besoin de lire ou d'écrire que quelques centaines d'objets à la fois, le faire dans l'isolat de l'interface utilisateur ne pose pas de problème. Mais pour les transactions importantes ou si le thread de l'interface utilisateur est déjà occupé, nous devrions envisager d'utiliser un isolat séparé.
## Exemple
La première chose que nous devons faire est d'ouvrir Isar dans le nouvel isolat. Puisque l'instance de Isar est déjà ouverte dans l'isolat principal, `Isar.open()` retournera la même instance.
:::warning
Assurez-vous de fournir les mêmes schémas que dans l'isolat principal. Sinon, vous obtiendrez une erreur.
:::
`compute()` démarre un nouvel isolat dans Flutter et y exécute la fonction donnée.
```dart
void main() {
// Ouvre Isar dans l'isolat de l'interface utilisateur
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[MessageSchema],
directory: dir.path,
name: 'myInstance',
);
// Écoute les changements dans la base de données
isar.messages.watchLazy(() {
print('omg the messages changed!');
});
// Démarre un nouvel isolat et crée 10000 messages
compute(createDummyMessages, 10000).then(() {
print('isolate finished');
});
// Après quelque temps:
// > omg the messages changed!
// > isolate finished
}
// Fonction qui sera exécutée dans le nouvel isolat
Future createDummyMessages(int count) async {
// Nous n'avons pas besoin du chemin du dossier ici étant donné que l'instance est déjà ouverte.
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[PostSchema],
directory: dir.path,
name: 'myInstance',
);
final messages = List.generate(count, (i) => Message()..content = 'Message $i');
// Nous utilisons une transaction synchrone en isolat
isar.writeTxnSync(() {
isar.messages.insertAllSync(messages);
});
}
```
Il y a quelques éléments intéressants à noter dans l'exemple ci-dessus:
- `isar.messages.watchLazy()` est appelé dans l'isolat UI et est notifié des changements provenant d'un autre isolat.
- Les instances sont référencées par leur nom. Le nom par défaut est `default`, mais dans cet exemple, nous l'avons défini comme `myInstance`.
- Nous avons utilisé une transaction synchrone pour créer les mesasges. Bloquer notre nouvel isolat n'est pas un problème, et les transactions synchrones sont un peu plus rapides.
================================================
FILE: docs/docs/fr/recipes/string_ids.md
================================================
---
title: Identifiants en chaîne de caractères
---
# Identifiants en chaîne de caractères
C'est l'une des demandes les plus fréquemment reçues. Voici donc un tutoriel sur l'utilisation des ids en `String`.
Isar ne supporte pas nativement les ids `String`, et il y a une bonne raison à cela: les ids entiers sont beaucoup plus efficaces et rapides. En particulier pour les liens, la complexité d'un identifiant de type `String` est trop importante.
Il arrive parfois que l'on doive stocker des données externes qui utilisent des UUID ou autres identifiants non entiers. Il est recommandé de stocker la chaîne id comme une propriété de votre objet et d'utiliser une implémentation de hachage rapide pour générer un int 64 bits qui peut être utilisé comme Id.
```dart
@collection
class User {
String? id;
Id get isarId => fastHash(id!);
String? name;
int? age;
}
```
Avec cette approche, nous obtenons le meilleur des deux mondes: des identifiants entiers efficaces pour les liens et la possibilité d'utiliser des identifiants de type `String`.
## Fonction de hachage rapide
Idéalement, notre fonction de hachage devrait avoir une haute qualité (nous ne voulons pas de collisions) et être rapide. Il est recommandé d'utiliser l'implémentation suivante:
```dart
/// Algorithme de hachage FNV-1a 64 bits optimisé pour les chaînes de caractères Dart
int fastHash(String string) {
var hash = 0xcbf29ce484222325;
var i = 0;
while (i < string.length) {
final codeUnit = string.codeUnitAt(i++);
hash ^= codeUnit >> 8;
hash *= 0x100000001b3;
hash ^= codeUnit & 0xFF;
hash *= 0x100000001b3;
}
return hash;
}
```
Si vous choisissez une fonction de hachage différente, assurez-vous qu'elle renvoie un int 64 bits et évitez d'utiliser une fonction de hachage cryptographique, car elle est beaucoup plus lente.
:::warning
Évitez d'utiliser `string.hashCode`, car sa stabilité n'est pas garantie sur les différentes plateformes et versions de Dart.
:::
================================================
FILE: docs/docs/fr/schema.md
================================================
---
title: Schéma
---
# Schéma
Lorsque vous utilisez Isar pour stocker les données de votre application, vous devez utiliser des collections. Une collection est comme une table de base de données, et ne peut contenir qu'un seul type d'objet Dart. Chaque objet de collection représente une entrée de données dans la collection correspondante.
La définition d'une collection s'appelle "schéma". Le générateur Isar fera le gros du travail pour nous et générera la plupart du code dont nous avons besoin pour utiliser la collection.
## Anatomie d'une collection
Nous définissons chaque collection Isar en annotant une classe avec `@collection` ou `@Collection()`. Une collection Isar comprend des champs pour chaque colonne de la table correspondante dans la base de données, y compris un champ qui comprend la clé primaire.
Le code suivant est un exemple d'une collection simple qui définit une table `User` avec des colonnes pour l'ID, le prénom et le nom :
```dart
@collection
class User {
Id? id;
String? firstName;
String? lastName;
}
```
:::tip
Pour faire persister un champ, Isar doit y avoir accès. Vous pouvez vous assurer que Isar y a accès en le rendant public ou en fournissant des méthodes `getter` et `setter`.
:::
Il existe quelques paramètres facultatifs permettant de personnaliser la collection:
| Config | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------- |
| `inheritance` | Contrôle si les champs des classes parentes et des mixins seront stockés dans Isar. Activé par défaut. |
| `accessor` | Permet de renommer l'accesseur de collection par défaut (par exemple `isar.contacts` pour la collection `Contact`). |
| `ignore` | Permet d'ignorer certaines propriétés de la classe. Celles-ci sont également respectées pour les classes parentes. |
### Id Isar
Chaque classe de collection doit définir une propriété id de type `Id`, qui identifie de façon unique un objet. `Id` est un alias pour `int` qui permet au générateur Isar de reconnaître la propriété id.
Isar indexe automatiquement les champs id, ce qui nous permet d'obtenir et de modifier les objets en fonction de leur id de manière efficace.
Vous pouvez soit définir les ids vous-même, soit demander à Isar d'attribuer un id auto-incrémenté. Si le champ `id` est `null` et non `final`, Isar assignera un id auto-incrémenté. Si vous voulez un identifiant auto-incrémenté non nul, vous pouvez utiliser `Isar.autoIncrement` au lieu de `null`.
:::tip
Les identifiants d'auto-incrémentation ne sont pas réutilisés lorsqu'un objet est supprimé. La seule façon de réinitialiser les identifiants d'auto-incrémentation est d'effacer la collection ou la base de données.
:::
### Renommer les collections et champs
Par défaut, Isar utilise le nom de la classe comme nom de collection. De même, Isar utilise les noms de champs comme noms de colonnes dans la base de données. Si vous voulez qu'une collection ou un champ ait un nom différent, ajoutez l'annotation `@Name()`. L'exemple suivant montre des noms personnalisés pour les collections et les champs :
```dart
@collection
@Name("User")
class MyUserClass1 {
@Name("id")
Id myObjectId;
@Name("firstName")
String theFirstName;
@Name("lastName")
String familyNameOrWhatever;
}
```
Vous devriez envisager d'utiliser l'annotation `@Name()` si vous voulez renommer des champs ou des classes Dart qui sont déjà stockés dans la base de données. Sinon, la base de données supprimera et recréera le champ ou la collection.
### Ignorer des champs
Isar persiste tous les champs publics d'une classe de collection. En annotant une propriété ou un `getter` avec `@ignore`, vous pouvez l'exclure de la persistance, comme le montre l'extrait de code suivant:
```dart
@collection
class User {
Id? id;
String? firstName;
String? lastName;
@ignore
String? password;
}
```
Dans les cas où une collection hérite de champs d'une collection parente, il est généralement plus facile d'utiliser la propriété `ignore` de l'annotation `@Collection`:
```dart
@collection
class User {
Image? profilePicture;
}
@Collection(ignore: {'profilePicture'})
class Member extends User {
Id? id;
String? firstName;
String? lastName;
}
```
Si une collection contient un champ dont le type n'est pas supporté par Isar, vous devez ignorer ce champ.
:::warning
Gardez en tête qu'il n'est pas recommandé de stocker des informations dans des objets Isar qui ne sont pas persistants.
:::
## Types supportés
Isar supporte les types de données suivants :
- `bool`
- `byte`
- `short`
- `int`
- `float`
- `double`
- `DateTime`
- `String`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
De plus, les objets embarqués (`embedded`) et les enums sont supportés. Nous les aborderons ci-dessous.
## byte, short, float
Pour de nombreux cas d'utilisation, vous n'avez pas besoin de l'étendue complète d'un nombre entier ou double de 64 bits. Isar supporte des types supplémentaires qui vous permettent d'économiser de l'espace et de la mémoire lorsque vous stockez des nombres plus petits.
| Type | Size in bytes | Range |
| ---------- | ------------- | ------------------------------------------------------ |
| **byte** | 1 | 0 à 255 |
| **short** | 4 | -2,147,483,647 à 2,147,483,647 |
| **int** | 8 | -9,223,372,036,854,775,807 à 9,223,372,036,854,775,807 |
| **float** | 4 | -3.4e38 à 3.4e38 |
| **double** | 8 | -1.7e308 à 1.7e308 |
Les types supplémentaires sont simplement des alias pour les types natifs de Dart, donc utiliser `short`, par exemple, fonctionne de la même manière que `int`.
Voici un exemple de collection contenant les types décrit ci-dessus:
```dart
@collection
class TestCollection {
Id? id;
late byte byteValue;
short? shortValue;
int? intValue;
float? floatValue;
double? doubleValue;
}
```
Tous les types de nombres peuvent également être utilisés dans des listes. Pour stocker des octets (`bytes`), vous devriez utiliser `List`.
## Types nullables
Il est essentiel de comprendre comment la nullité fonctionne dans Isar: Les types de nombres n'ont **PAS** de représentation `null` dédiée. À la place, une valeur spécifique est utilisée:
| Type | VM |
| ---------- | ------------- |
| **short** | `-2147483648` |
| **int** | `int.MIN` |
| **float** | `double.NaN` |
| **double** | `double.NaN` |
`bool`, `String`, et `List` ont une représentation `null` séparée.
Ce comportement permet d'améliorer les performances, et il vous permet de modifier librement la nullité de vos champs sans nécessiter de migration ou de code spécial pour gérer les valeurs "nulles".
:::warning
Le type `byte` ne supporte pas les valeurs nulles.
:::
## DateTime
Isar ne stocke pas les informations de fuseau horaire de vos dates. À la place, il les convertit en UTC avant de les stocker. Isar retourne toutes les dates en heure locale.
Les `DateTime` sont stockés avec une précision de l'ordre de la microseconde. Dans les navigateurs, seule la précision de la milliseconde est supportée en raison des limitations de JavaScript.
## Enum
Isar permet de stocker et d'utiliser les enums comme tous les autres types Isar. Vous devez cependant choisir comment Isar doit représenter l'enum sur disque. Isar supporte quatre stratégies différentes :
| EnumType | Description |
| ----------- | ------------------------------------------------------------------------------------------------ |
| `ordinal` | L'index de l'enum est stocké comme `byte`. Très efficace mais ne permet pas les enums nullables. |
| `ordinal32` | L'index de l'enum est stocké comme `short` (entier de 4 octets). |
| `name` | Le nom de l'enum est stocké comme `String`. |
| `value` | Une propriété personnalisée est utilisée pour récupérer la valeur de l'enum. |
:::warning
`ordinal` et `ordinal32` dépendent de l'ordre des valeurs de l'enum. Si vous changez l'ordre, les bases de données existantes renverront des valeurs incorrectes.
:::
Voici un exemple pour chaque stratégie:
```dart
@collection
class EnumCollection {
Id? id;
@enumerated // Même chose que EnumType.ordinal
late TestEnum byteIndex; // Ne peut pas être nulle
@Enumerated(EnumType.ordinal)
late TestEnum byteIndex2; // Ne peut pas être nulle
@Enumerated(EnumType.ordinal32)
TestEnum? shortIndex;
@Enumerated(EnumType.name)
TestEnum? name;
@Enumerated(EnumType.value, 'myValue')
TestEnum? myValue;
}
enum TestEnum {
first(10),
second(100),
third(1000);
const TestEnum(this.myValue);
final short myValue;
}
```
Bien entendu, les enums peuvent également être utilisés dans des listes.
## Objets embarqués
Il est souvent utile d'avoir des objets imbriqués dans votre modèle de collection. Il n'y a pas de limite à la profondeur à laquelle vous pouvez imbriquer des objets. Gardez cependant à l'esprit que la mise à jour d'un objet profondément imbriqué nécessitera l'écriture de l'arbre d'objets complet dans la base de données.
```dart
@collection
class Email {
Id? id;
String? title;
Recepient? recipient;
}
@embedded
class Recepient {
String? name;
String? address;
}
```
Les objets embarqués peuvent être nullables et hériter d'autres objets. La seule condition est qu'ils soient annotés avec `@embedded` et qu'ils aient un constructeur par défaut sans paramètres requis.
================================================
FILE: docs/docs/fr/transactions.md
================================================
---
title: Transactions
---
# Transactions
Dans Isar, les transactions combinent plusieurs opérations de base de données en une seule unité de travail. La plupart des interactions avec Isar utilisent implicitement des transactions. L'accès en lecture et en écriture dans Isar est conforme à la norme [ACID](http://en.wikipedia.org/wiki/ACID). Les transactions sont automatiquement annulées en cas d'erreur.
## Transactions explicites
Dans une transaction explicite, vous obtenez un instantané cohérent de la base de données. Essayez de minimiser la durée des transactions. Il est interdit d'effectuer des appels réseau ou d'autres opérations de longue durée dans une transaction.
Les transactions (en particulier les transactions d'écriture) ont un coût, et nous devrions toujours essayer de regrouper les opérations successives en une seule transaction.
Les transactions peuvent être soit synchrones ou asynchrones. Dans les transactions synchrones, nous ne pouvons utiliser que les opérations synchrones. Dans les transactions asynchrones, uniquement les opérations asynchrones.
| | Lecture | Lecture et écriture |
|-------------|--------------|---------------------|
| Synchrones | `.txnSync()` | `.writeTxnSync()` |
| Asynchrones | `.txn()` | `.writeTxn()` |
### Transactions de lecture
Les transactions de lecture explicites sont facultatives, mais elles nous permettent d'effectuer des lectures atomiques et de compter sur un état cohérent de la base de données à l'intérieur de la transaction. À l'interne, Isar utilise toujours des transactions de lecture implicites pour toutes les opérations de lecture.
:::tip
Les transactions de lecture asynchrones s'exécutent en parallèle avec d'autres transactions de lecture et d'écriture. Plutôt cool, non?
:::
### Transactions d'écriture
Contrairement aux opérations de lecture, les opérations d'écriture dans Isar doivent être enveloppées dans une transaction explicite.
Lorsqu'une transaction d'écriture se termine avec succès, elle est automatiquement validée et toutes les modifications sont écrites sur disque. Si une erreur se produit, la transaction est abandonnée et toutes les modifications sont annulées. Les transactions sont "tout ou rien": soit toutes les écritures d'une transaction réussissent, soit aucune d'entre elles ne prend effet pour garantir la cohérence des données.
:::warning
Lorsqu'une opération de base de données échoue, la transaction est interrompue et ne doit plus être utilisée. Même si vous attrapez l'erreur dans Dart.
:::
```dart
@collection
class Contact {
Id? id;
String? name;
}
// BON
await isar.writeTxn(() async {
for (var contact in getContacts()) {
await isar.contacts.put(contact);
}
});
// MAUVAIS : déplacer la boucle à l'intérieur de la transaction
for (var contact in getContacts()) {
await isar.writeTxn(() async {
await isar.contacts.put(contact);
});
}
```
================================================
FILE: docs/docs/fr/tutorials/quickstart.md
================================================
---
title: Démarrage rapide
---
# Démarrage rapide
Vous revoilà! Commençons à utiliser la base de données Flutter la plus cool qui soit...
Nous allons être brefs en mots et rapides en code dans ce démarrage rapide.
## 1. Ajout des dépendances
Avant de débuter, nous devons ajouter quelques dépendances au fichier `pubspec.yaml`. Nous pouvons utiliser la commande `pub` pour faire le gros du travail à notre place.
```bash
dart pub add isar:^0.0.0-placeholder isar_flutter_libs:^0.0.0-placeholder --hosted-url=https://pub.isar-community.dev
dart pub add dev:isar_generator:^0.0.0-placeholder --hosted-url=https://pub.isar-community.dev
```
## 2. Annotation de classes
Annotez vos classes de collection avec `@collection` et choisissez un champ `Id`.
```dart
part 'user.g.dart';
@collection
class User {
Id id = Isar.autoIncrement; // Vous pouvez aussi utiliser id = null pour l'auto incrémentation
String? name;
int? age;
}
```
Les Ids identifient de manière unique les objets d'une collection et vous permettent de les retrouver ultérieurement.
## 3. Exécuter le générateur de code
Exécutez la commande suivante pour démarrer le `build_runner`:
```sh
dart run build_runner build
```
Si vous utilisez Flutter:
```sh
flutter pub run build_runner build
```
## 4. Ouverture l'instance Isar
Ouvrez une nouvelle instance d'Isar et passez tous vos schémas de collection. En option, vous pouvez spécifier un nom d'instance et un dossier.
```dart
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[UserSchema],
directory: dir.path,
);
```
## 5. Écriture et lecture
Une fois que votre instance est ouverte, vous pouvez commencer à utiliser les collections.
Toutes les opérations CRUD de base sont disponibles via `IsarCollection`.
```dart
final newUser = User()..name = 'Jane Doe'..age = 36;
await isar.writeTxn(() async {
await isar.users.put(newUser); // Insertion & modification
});
final existingUser = await isar.users.get(newUser.id); // Obtention
await isar.writeTxn(() async {
await isar.users.delete(existingUser.id!); // Suppression
});
```
## Autre ressources
Vous apprenez mieux visuellement ? Regardez ces vidéos pour commencer avec Isar:
================================================
FILE: docs/docs/fr/watchers.md
================================================
---
title: Observateurs
---
# Observateurs
Isar nous permet de nous abonner aux changements dans la base de données. Nous pouvons "observer" les modifications apportées à un objet spécifique, à une collection entière ou à une requête.
Les observateurs (`Watchers`) nous permettent de réagir efficacement aux changements dans la base de données. Nous pouvons par exemple reconstruire une interface utilisateur lorsqu'un contact est ajouté, envoyer une requête réseau lorsqu'un document est mis à jour, etc.
Un observateur est notifié lorsqu'une transaction est validée avec succès et que la cible est réellement modifiée.
## Observation d'objets
Si nous voulons être notifié lorsqu'un objet spécifique est créé, mis à jour ou supprimé, nous devons observer un objet:
```dart
Stream userChanged = isar.users.watchObject(5);
userChanged.listen((newUser) {
print('User changed: ${newUser?.name}');
});
final user = User(id: 5)..name = 'David';
await isar.users.put(user);
// prints: User changed: David
final user2 = User(id: 5)..name = 'Mark';
await isar.users.put(user);
// prints: User changed: Mark
await isar.users.delete(5);
// prints: User changed: null
```
Comme nous pouvons le voir dans l'exemple ci-dessus, l'objet ne doit pas encore exister. L'observateur sera notifié lorsqu'il sera créé.
Il existe un paramètre supplémentaire, `fireImmediately`. Si nous le mettons à `true`, Isar ajoutera immédiatement la valeur courante de l'objet au flux.
### Observation paresseuse
Peut-être n'avez-vous pas besoin de recevoir la nouvelle valeur, mais seulement d'être notifié du changement? Cela évite à Isar d'avoir à aller chercher l'objet:
```dart
Stream userChanged = isar.users.watchObjectLazy(5);
userChanged.listen(() {
print('User 5 changed');
});
final user = User(id: 5)..name = 'David';
await isar.users.put(user);
// prints: User 5 changed
```
## Observation de collections
Au lieu d'observer un seul objet, nous pouvons observer une collection entière et être notifié lorsqu'un objet est ajouté, mis à jour ou supprimé:
```dart
Stream userChanged = isar.users.watchLazy();
userChanged.listen(() {
print('A User changed');
});
final user = User()..name = 'David';
await isar.users.put(user);
// prints: A User changed
```
## Observation de requêtes
Il est même possible d'observer des requêtes entières. Isar fait de son possible pour nous notifier uniquement lorsque les résultats de la requête changent réellement. Nous ne serons pas notifiés si des liens entraînent une modification de la requête. Utilisez un observateur de collection si vous avez besoin d'être informé des changements de liens.
```dart
Query usersWithA = isar.users.filter()
.nameStartsWith('A')
.build();
Stream> queryChanged = usersWithA.watch(fireImmediately: true);
queryChanged.listen((users) {
print('Users with A are: $users');
});
// prints: Users with A are: []
await isar.users.put(User()..name = 'Albert');
// prints: Users with A are: [User(name: Albert)]
await isar.users.put(User()..name = 'Monika');
// no print
await isar.users.put(User()..name = 'Antonia');
// prints: Users with A are: [User(name: Albert), User(name: Antonia)]
```
:::warning
Si vous utilisez des requêtes `offset`, `limit` ou `distinct`, Isar vous notifiera même si les changements y sont en dehors.
:::
Tout comme `watchObject()`, nous pouvons utiliser `watchLazy()` pour être notifié lorsque les résultats de la requête changent, mais ne pas aller les chercher.
:::danger
Relancer les requêtes à chaque modification est très inefficace. Il serait préférable d'utiliser un observateur de collection paresseux (`lazy`) à la place.
:::
================================================
FILE: docs/docs/indexes.md
================================================
---
title: Indexes
---
# Indexes
Indexes are Isar's most powerful feature. Many embedded databases offer "normal" indexes (if at all), but Isar also has composite and multi-entry indexes. Understanding how indexes work is essential to optimize query performance. Isar lets you choose which index you want to use and how you want to use it. We'll start with a quick introduction to what indexes are.
## What are indexes?
When a collection is unindexed, the order of the rows will likely not be discernible by the query as optimized in any way, and your query will therefore have to search through the objects linearly. In other words, the query will have to search through every object to find the ones matching the conditions. As you can imagine, that can take some time. Looking through every single object is not very efficient.
For example, this `Product` collection is entirely unordered.
```dart
@collection
class Product {
Id? id;
late String name;
late int price;
}
```
**Data:**
| id | name | price |
| --- | --------- | ----- |
| 1 | Book | 15 |
| 2 | Table | 55 |
| 3 | Chair | 25 |
| 4 | Pencil | 3 |
| 5 | Lightbulb | 12 |
| 6 | Carpet | 60 |
| 7 | Pillow | 30 |
| 8 | Computer | 650 |
| 9 | Soap | 2 |
A query that tries to find all products that cost more than €30 has to search through all nine rows. That's not an issue for nine rows, but it might become a problem for 100k rows.
```dart
final expensiveProducts = await isar.products.filter()
.priceGreaterThan(30)
.findAll();
```
To improve the performance of this query, we index the `price` property. An index is like a sorted lookup table:
```dart
@collection
class Product {
Id? id;
late String name;
@Index()
late int price;
}
```
**Generated index:**
| price | id |
| -------------------- | ------------------ |
| 2 | 9 |
| 3 | 4 |
| 12 | 5 |
| 15 | 1 |
| 25 | 3 |
| 30 | 7 |
| **55** | **2** |
| **60** | **6** |
| **650** | **8** |
Now, the query can be executed a lot faster. The executor can directly jump to the last three index rows and find the corresponding objects by their id.
### Sorting
Another cool thing: indexes can do super fast sorting. Sorted queries are costly because the database has to load all results in memory before sorting them. Even if you specify an offset or limit, they are applied after sorting.
Let's imagine we want to find the four cheapest products. We could use the following query:
```dart
final cheapest = await isar.products.filter()
.sortByPrice()
.limit(4)
.findAll();
```
In this example, the database would have to load all (!) objects, sort them by price, and return the four products with the lowest price.
As you can probably imagine, this can be done much more efficiently with the previous index. The database takes the first four rows of the index and returns the corresponding objects since they are already in the correct order.
To use the index for sorting, we would write the query like this:
```dart
final cheapestFast = await isar.products.where()
.anyPrice()
.limit(4)
.findAll();
```
The `.anyX()` where clause tells Isar to use an index just for sorting. You can also use a where clause like `.priceGreaterThan()` and get sorted results.
## Unique indexes
A unique index ensures the index does not contain any duplicate values. It may consist of one or multiple properties. If a unique index has one property, the values in this property will be unique. If the unique index has more than one property, the combination of values in these properties is unique.
```dart
@collection
class User {
Id? id;
@Index(unique: true)
late String username;
late int age;
}
```
Any attempt to insert or update data into the unique index that causes a duplicate will result in an error:
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1); // -> ok
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
// try to insert user with same username
await isar.users.put(user2); // -> error: unique constraint violated
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
```
## Replace indexes
It is sometimes not preferable to throw an error if a unique constraint is violated. Instead, you may want to replace the existing object with the new one. This can be achieved by setting the `replace` property of the index to `true`.
```dart
@collection
class User {
Id? id;
@Index(unique: true, replace: true)
late String username;
}
```
Now when we try to insert a user with an existing username, Isar will replace the existing user with the new one.
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1);
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
print(await isar.user.where().findAll());
// > [{id: 2, username: 'user1' age: 30}]
```
Replace indexes also generate `putBy()` methods that allow you to update objects instead of replacing them. The existing id is reused, and links are still populated.
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
// user does not exist so this is the same as put()
await isar.users.putByUsername(user1);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1' age: 30}]
```
As you can see, the id of the first inserted user is reused.
## Case-insensitive indexes
All indexes on `String` and `List` properties are case-sensitive by default. If you want to create a case-insensitive index, you can use the `caseSensitive` option:
```dart
@collection
class Person {
Id? id;
@Index(caseSensitive: false)
late String name;
@Index(caseSensitive: false)
late List tags;
}
```
## Index type
There are different types of indexes. Most of the time, you'll want to use an `IndexType.value` index, but hash indexes are more efficient.
### Value index
Value indexes are the default type and the only one allowed for all properties that don't hold Strings or Lists. Property values are used to build the index. In the case of lists, the elements of the list are used. It is the most flexible but also space-consuming of the three index types.
:::tip
Use `IndexType.value` for primitives, Strings where you need `startsWith()` where clauses, and Lists if you want to search for individual elements.
:::
### Hash index
Strings and Lists can be hashed to reduce the storage required by the index significantly. The disadvantage of hash indexes is that they can't be used for prefix scans (`startsWith` where clauses).
:::tip
Use `IndexType.hash` for Strings and Lists if you don't need `startsWith`, and `elementEqualTo` where clauses.
:::
### HashElements index
String lists can be hashed as a whole (using `IndexType.hash`), or the elements of the list can be hashed separately (using `IndexType.hashElements`), effectively creating a multi-entry index with hashed elements.
:::tip
Use `IndexType.hashElements` for `List` where you need `elementEqualTo` where clauses.
:::
## Composite indexes
A composite index is an index on multiple properties. Isar allows you to create composite indexes of up to three properties.
Composite indexes are also known as multiple-column indexes.
It's probably best to start with an example. We create a person collection and define a composite index on the age and name properties:
```dart
@collection
class Person {
Id? id;
late String name;
@Index(composite: [CompositeIndex('name')])
late int age;
late String hometown;
}
```
**Data:**
| id | name | age | hometown |
| --- | ------ | --- | --------- |
| 1 | Daniel | 20 | Berlin |
| 2 | Anne | 20 | Paris |
| 3 | Carl | 24 | San Diego |
| 4 | Simon | 24 | Munich |
| 5 | David | 20 | New York |
| 6 | Carl | 24 | London |
| 7 | Audrey | 30 | Prague |
| 8 | Anne | 24 | Paris |
**Generated index:**
| age | name | id |
| --- | ------ | --- |
| 20 | Anne | 2 |
| 20 | Daniel | 1 |
| 20 | David | 5 |
| 24 | Anne | 8 |
| 24 | Carl | 3 |
| 24 | Carl | 6 |
| 24 | Simon | 4 |
| 30 | Audrey | 7 |
The generated composite index contains all persons sorted by their age their name.
Composite indexes are great if you want to create efficient queries sorted by multiple properties. They also enable advanced where clauses with multiple properties:
```dart
final result = await isar.where()
.ageNameEqualTo(24, 'Carl')
.hometownProperty()
.findAll() // -> ['San Diego', 'London']
```
The last property of a composite index also supports conditions like `startsWith()` or `lessThan()`:
```dart
final result = await isar.where()
.ageEqualToNameStartsWith(20, 'Da')
.findAll() // -> [Daniel, David]
```
## Multi-entry indexes
If you index a list using `IndexType.value`, Isar will automatically create a multi-entry index, and each item in the list is indexed toward the object. It works for all types of lists.
Practical applications for multi-entry indexes include indexing a list of tags or creating a full-text index.
```dart
@collection
class Product {
Id? id;
late String description;
@Index(type: IndexType.value, caseSensitive: false)
List get descriptionWords => Isar.splitWords(description);
}
```
`Isar.splitWords()` splits a string into words according to the [Unicode Annex #29](https://unicode.org/reports/tr29/) specification, so it works for almost all languages correctly.
**Data:**
| id | description | descriptionWords |
| --- | ---------------------------- | ---------------------------- |
| 1 | comfortable blue t-shirt | [comfortable, blue, t-shirt] |
| 2 | comfortable, red pullover!!! | [comfortable, red, pullover] |
| 3 | plain red t-shirt | [plain, red, t-shirt] |
| 4 | red necktie (super red) | [red, necktie, super, red] |
Entries with duplicate words only appear once in the index.
**Generated index:**
| descriptionWords | id |
| ---------------- | --------- |
| comfortable | [1, 2] |
| blue | 1 |
| necktie | 4 |
| plain | 3 |
| pullover | 2 |
| red | [2, 3, 4] |
| super | 4 |
| t-shirt | [1, 3] |
This index can now be used for prefix (or equality) where clauses of the individual words of the description.
:::tip
Instead of storing the words directly, also consider using the result of a [phonetic algorithm](https://en.wikipedia.org/wiki/Phonetic_algorithm) like [Soundex](https://en.wikipedia.org/wiki/Soundex).
:::
================================================
FILE: docs/docs/it/README.md
================================================
---
home: true
title: Home
heroImage: /isar.svg
actions:
- text: Iniziamo!
link: /it/tutorials/quickstart.html
type: primary
features:
- title: 💙 Creato per Flutter
details: Setup minimo, facile da usare, nessuna configurazione, niente codice boilerplate. Basta aggiungere poche linee di codice per iniziare.
- title: 🚀 Altamente scalabile
details: Salva centinaia di migliaia di records in un singolo NoSQL database ed interrogalo in maniera efficiente ed asincrona.
- title: 🍭 Ricco di funzionalità
details: Isa ha un insieme ricco di funzionalità per aiutare nella gestione dei tuoi dati. Indici composti & multi-entry, modificatori di query, supporto al JSON, e molto altro.
- title: 🔎 Ricerca full-text
details: Isar ha un sistema di ricerca built-in basato su full-text. Crea un inidice multi-entry e ricerca i record facilmente.
- title: 🧪 Semantica ACID
details: Isar è conforme con ACID e gestisce automaticamente le transazioni. In caso di errore effettua roll-back automaticamente.
- title: 💃 Staticamente tipizzato
details: Le query Isar sono tipizzate staticamente e controllate in fase di compilazione. Non è necessario preoccuparsi degli errori di runtime.
- title: 📱 Multipiattaform
details: iOS, Android, Desktop e PIENO SUPPORTO AL WEB!
- title: ⏱ Asincrono
details: Operazioni di query parallele e supporto per isolamento multiplo pronto all'uso
- title: 🦄 Open Source
details: Tutto è open source e gratuito per sempre!
footer: Apache Licensed | Copyright © 2022 Simon Leier
---
================================================
FILE: docs/docs/it/crud.md
================================================
---
title: Create, Read, Update, Delete
---
# Create, Read, Update, Delete
Quando hai definito le tue collezioni, impara a manipolarle!
## Apertura di Isar
Prima che tu possa fare qualsiasi cosa, abbiamo bisogno di un'istanza Isar. Ogni istanza richiede una directory con autorizzazione di scrittura in cui è possibile archiviare il file di database. Se non specifichi una directory, Isar troverà una directory predefinita adatta per la piattaforma corrente.
Fornisci tutti gli schemi che desideri utilizzare con l'istanza Isar. Se apri più istanze, devi comunque fornire gli stessi schemi a ciascuna istanza.
```dart
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[ContactSchema],
directory: dir.path,
);
```
È possibile utilizzare la configurazione predefinita o fornire alcuni dei seguenti parametri:
| Config. | Descrizione |
| -------| -------------|
| `name` | Apri più istanze con nomi distinti. Per impostazione predefinita, viene utilizzato `"predefinito"`. |
| `directory` | Il percorso di archiviazione per questa istanza. Puoi passare un percorso relativo o assoluto. Per impostazione predefinita, `NSDocumentDirectory` viene utilizzato per iOS e `getDataDirectory` per Android. Non richiesto per il web. |
| `relaxedDurability` | Rilassa la garanzia di durata per aumentare le prestazioni di scrittura. In caso di arresto anomalo del sistema (non arresto anomalo dell'app), è possibile perdere l'ultima transazione impegnata. La corruzione non è possibile |
| `compactOnLaunch` | Condizioni per verificare se il database deve essere compattato all'apertura dell'istanza. |
| `inspector` | Abilita l'Inspector per le build di debug. Per le build di profili e versioni questa opzione viene ignorata. |
Se un'istanza è già aperta, la chiamata a `Isar.open()` fornirà l'istanza esistente indipendentemente dai parametri specificati. È utile per usare Isar in un isolate.
:::tip
Prendi in considerazione l'utilizzo del pacchetto [path_provider](https://pub.dev/packages/path_provider) per ottenere un percorso valido su tutte le piattaforme.
:::
Il percorso di archiviazione del file di database è `directory/name.isar`.
## Lettura dal database
Usa le istanze di `IsarCollection` per trovare, interrogare e creare nuovi oggetti di un determinato tipo in Isar.
Per gli esempi seguenti, assumiamo di avere una raccolta "Ricetta" definita come segue:
```dart
@collection
class Recipe {
Id? id;
String? name;
DateTime? lastCooked;
bool? isFavorite;
}
```
### Ottieni una raccolta
Tutte le tue raccolte vivono nell'istanza Isar. Puoi ottenere la raccolta di ricette con:
```dart
final recipes = isar.recipes;
```
È stato facile! Se non vuoi usare le funzioni di accesso alla raccolta, puoi anche usare il metodo `collection()`:
```dart
final recipes = isar.collection();
```
### Ottieni un oggetto (per ID)
Non abbiamo ancora dati nella raccolta, ma facciamo finta di farlo in modo da poter ottenere un oggetto immaginario con l'id `123`
```dart
final recipe = await recipes.get(123);
```
`get()` restituisce un `Future` con l'oggetto o `null` se non esiste. Tutte le operazioni Isar sono asincrone per impostazione predefinita e la maggior parte di esse ha una controparte sincrona:
```dart
final recipe = recipes.getSync(123);
```
:::warning
Per impostazione predefinita, dovresti utilizzare la versione asincrona dei metodi nell'isolato dell'interfaccia utente. Poiché Isar è molto veloce, è spesso accettabile utilizzare la versione sincrona.
:::
Se vuoi ottenere più oggetti contemporaneamente, usa `getAll()` o `getAllSync()`:
```dart
final recipe = await recipes.getAll([1, 2]);
```
### Interroga gli oggetti
Invece di ottenere oggetti per id puoi anche interrogare un elenco di oggetti che soddisfano determinate condizioni usando `.where()` e `.filter()`:
```dart
final allRecipes = await recipes.where().findAll();
final favouires = await recipes.filter()
.isFavoriteEqualTo(true)
.findAll();
```
➡️ Scopri di più: [Queries](queries)
## Modifica del database
È finalmente arrivato il momento di modificare la nostra collezione! Per creare, aggiornare o eliminare oggetti, utilizzare le rispettive operazioni racchiuse in una transazione di scrittura:
```dart
await isar.writeTxn(() async {
final recipe = await recipes.get(123)
recipe.isFavorite = false;
await recipes.put(recipe); // perform update operations
await recipes.delete(123); // or delete operations
});
```
➡️ Scopri di più: [Transactions](transactions)
### Inserimento
Per rendere persistente un oggetto in Isar, inserirlo in una collezione. Il metodo `put()` di Isar inserirà o aggiornerà l'oggetto a seconda che esista già nella raccolta.
Se il campo id è `null` o `Isar.autoIncrement`, Isar utilizzerà un id di incremento automatico.
```dart
final pancakes = Recipe()
..name = 'Pancakes'
..lastCooked = DateTime.now()
..isFavorite = true;
await isar.writeTxn(() async {
await recipes.put(pancakes);
})
```
Isar assegnerà automaticamente l'id all'oggetto se il campo `id` non è definitivo.
Inserire più oggetti contemporaneamente è altrettanto facile:
```dart
await isar.writeTxn(() async {
await recipes.putAll([pancakes, pizza]);
})
```
### Aggiornamento
Sia la creazione che l'aggiornamento funzionano con `collection.put(object)`. Se l'id è `null` (o non esiste), l'oggetto viene inserito; in caso contrario, viene aggiornato.
Quindi, se vogliamo eliminare i nostri pancake dai preferiti, possiamo fare quanto segue:
```dart
await isar.writeTxn(() async {
pancakes.isFavorite = false;
await recipes.put(recipe);
});
```
### Eliminazione
Vuoi sbarazzarti di un oggetto in Isar? Usa `collection.delete(id)`. Il metodo delete restituisce se un oggetto con l'ID specificato è stato trovato ed eliminato. Se vuoi eliminare l'oggetto con id `123`, ad esempio, puoi fare:
```dart
await isar.writeTxn(() async {
final success = await recipes.delete(123);
print('Recipe deleted: $success');
});
```
Allo stesso modo per get e put, esiste anche un'operazione di eliminazione in blocco che restituisce il numero di oggetti eliminati:
```dart
await isar.writeTxn(() async {
final count = await recipes.deleteAll([1, 2, 3]);
print('We deleted $count recipes');
});
```
Se non conosci gli ID degli oggetti che desideri eliminare, puoi utilizzare una query:
```dart
await isar.writeTxn(() async {
final count = await recipes.filter()
.isFavoriteEqualTo(false)
.deleteAll();
print('We deleted $count recipes');
});
```
================================================
FILE: docs/docs/it/faq.md
================================================
---
title: FAQ
---
# Domande frequenti
Una raccolta casuale di domande frequenti sui database Isar e Flutter.
### Perché ho bisogno di un database?
> Conservo i miei dati in un database back-end, perché ho bisogno di Isar?.
Ancora oggi è molto comune non avere connessione dati se sei in metropolitana o in aereo o se vai a trovare tua nonna, che non ha WiFi e un segnale cellulare pessimo. Non dovresti lasciare che una cattiva connessione paralizzi la tua app!
### Isar vs Hive
La risposta è semplice: Isar è stato [nato come sostituto di Hive](https://github.com/hivedb/hive/issues/246) e ora si trova in uno stato in cui consiglio di utilizzare sempre Isar invece di Hive.
### Dove sono le clausole?!
> Perché **_Io_** devo scegliere quale indice utilizzare?
Ci sono più ragioni. Molti database utilizzano l'euristica per scegliere l'indice migliore per una determinata query. Il database deve raccogliere dati di utilizzo aggiuntivi (-> sovraccarico) e potrebbe comunque scegliere l'indice errato. Inoltre, rende più lenta la creazione di una query.
Nessuno conosce i tuoi dati meglio di te, lo sviluppatore. Quindi puoi scegliere l'indice ottimale e decidere, ad esempio, se desideri utilizzare un indice per eseguire query o ordinare.
### Devo usare gli indici oppure le clausole where?
No! Molto probabilmente Isar è abbastanza veloce se ti affidi solo ai filtri.
### Isar è abbastanza veloce?
Isar è tra i database più veloci per dispositivi mobili, quindi dovrebbe essere abbastanza veloce per la maggior parte dei casi d'uso. Se riscontri problemi di prestazioni, è probabile che tu stia facendo qualcosa di sbagliato.
### Isar aumenta le dimensioni della mia app?
Un po', sì. Isar aumenterà la dimensione del download della tua app di circa 1 - 1,5 MB. Isar Web aggiunge solo pochi KB.
### La documentazione non è corretta / c'è un errore di battitura.
Oh no, mi dispiace. Per favore [apri un problema](https://github.com/isar-community/isar/issues/new/choose) o, ancora meglio, un PR per risolverlo 💪.
================================================
FILE: docs/docs/it/indexes.md
================================================
---
title: Indici
---
# Indici
Gli indici sono la caratteristica più potente di Isar. Molti database incorporati offrono indici "normali" (se non del tutto), ma Isar ha anche indici compositi e multi-voce. Comprendere il funzionamento degli indici è essenziale per ottimizzare le prestazioni delle query. Isar ti permette di scegliere quale indice vuoi usare e come usarlo. Inizieremo con una rapida introduzione a cosa sono gli indici.
## Cosa sono gli indici?
Quando una raccolta non è indicizzata, è probabile che l'ordine delle righe non sia distinguibile dalla query in quanto non ottimizzato e la query dovrà quindi cercare tra gli oggetti in maniera lineare. In altre parole, la query dovrà cercare in ogni oggetto per trovare quelli che soddisfano le condizioni. Come puoi immaginare, può volerci del tempo. Guardare attraverso ogni singolo oggetto non è molto efficiente.
Ad esempio, questa raccolta `Product` non è ordinata.
```dart
@collection
class Product {
Id? id;
late String name;
late int price;
}
```
**Dati:**
| id | nome | prezzo |
| --- | --------- | ----- |
| 1 | Book | 15 |
| 2 | Table | 55 |
| 3 | Chair | 25 |
| 4 | Pencil | 3 |
| 5 | Lightbulb | 12 |
| 6 | Carpet | 60 |
| 7 | Pillow | 30 |
| 8 | Computer | 650 |
| 9 | Soap | 2 |
Una query che tenti di trovare tutti i prodotti che costano più di € 30 deve cercare in tutte e nove le righe. Questo non è un problema per nove righe, ma potrebbe diventare un problema per 100.000 righe.
```dart
final expensiveProducts = await isar.products.filter()
.priceGreaterThan(30)
.findAll();
```
Per migliorare le prestazioni di questa query, indicizziamo la proprietà `price`. Un indice è come una tabella di ricerca ordinata:
```dart
@collection
class Product {
Id? id;
late String name;
@Index()
late int price;
}
```
**Indici generati:**
| price | id |
| -------------------- | ------------------ |
| 2 | 9 |
| 3 | 4 |
| 12 | 5 |
| 15 | 1 |
| 25 | 3 |
| 30 | 7 |
| **55** | **2** |
| **60** | **6** |
| **650** | **8** |
Ora, la query può essere eseguita molto più velocemente. L'esecutore può saltare direttamente alle ultime tre righe dell'indice e trovare gli oggetti corrispondenti in base al loro ID.
### Ordinamento
Un'altra cosa interessante: gli indici possono eseguire un ordinamento super veloce. Le query ordinate sono costose perché il database deve caricare tutti i risultati in memoria prima di ordinarli. Anche se si specifica un offset o un limite, vengono applicati dopo l'ordinamento.
Immaginiamo di voler trovare i quattro prodotti più economici. Potremmo usare la seguente query:
```dart
final cheapest = await isar.products.filter()
.sortByPrice()
.limit(4)
.findAll();
```
In questo esempio, il database dovrebbe caricare tutti (!) gli oggetti, ordinarli per prezzo e restituire i quattro prodotti con il prezzo più basso.
Come probabilmente puoi immaginare, questo può essere fatto in modo molto più efficiente con l'indice precedente. Il database prende le prime quattro righe dell'indice e restituisce gli oggetti corrispondenti poiché sono già nell'ordine corretto.
Per utilizzare l'indice per l'ordinamento, scriveremo la query in questo modo:
```dart
final cheapestFast = await isar.products.where()
.anyPrice()
.limit(4)
.findAll();
```
La clausola `.anyX()` indica a Isar di usare un indice solo per l'ordinamento. Puoi anche usare una clausola where come `.priceGreaterThan()` e ottenere risultati ordinati.
## Indici univoci
Un indice univoco garantisce che l'indice non contenga valori duplicati. Può essere costituito da una o più proprietà. Se un indice univoco ha una proprietà, i valori in questa proprietà saranno univoci. Se l'indice univoco ha più di una proprietà, la combinazione di valori in queste proprietà è univoca.
```dart
@collection
class User {
Id? id;
@Index(unique: true)
late String username;
late int age;
}
```
Qualsiasi tentativo di inserire o aggiornare i dati nell'indice univoco che causa un duplicato risulterà in un errore:
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1); // -> ok
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
// try to insert user with same username
await isar.users.put(user2); // -> error: unique constraint violated
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
```
## Sostituisci gli indici
A volte non è preferibile generare un errore se viene violato un vincolo univoco. Invece, potresti voler sostituire l'oggetto esistente con quello nuovo. Questo può essere ottenuto impostando la proprietà `replace` dell'indice su `true`.
```dart
@collection
class User {
Id? id;
@Index(unique: true, replace: true)
late String username;
}
```
Ora quando proviamo a inserire un utente con un nome utente esistente, Isar sostituirà l'utente esistente con quello nuovo.
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1);
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
print(await isar.user.where().findAll());
// > [{id: 2, username: 'user1' age: 30}]
```
Sostituire gli indici genera anche metodi `putBy()` che ti consentono di aggiornare gli oggetti invece di sostituirli. L'ID esistente viene riutilizzato e i collegamenti continuano a essere popolati.
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
// user does not exist so this is the same as put()
await isar.users.putByUsername(user1);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1' age: 30}]
```
Come puoi vedere, l'id del primo utente inserito viene riutilizzato.
## Indici senza distinzione tra maiuscole e minuscole
Tutti gli indici sulle proprietà `String` e `List` fanno distinzione tra maiuscole e minuscole per impostazione predefinita. Se desideri creare un indice senza distinzione tra maiuscole e minuscole, puoi utilizzare l'opzione `caseSensitive`:
```dart
@collection
class Person {
Id? id;
@Index(caseSensitive: false)
late String name;
@Index(caseSensitive: false)
late List tags;
}
```
## Tipo di indice
Esistono diversi tipi di indici. La maggior parte delle volte, vorrai usare un indice `IndexType.value`, ma gli indici hash sono più efficienti.
### Indice di valore
Gli indici di valore sono il tipo predefinito e l'unico consentito per tutte le proprietà che non contengono stringhe o elenchi. I valori delle proprietà vengono utilizzati per creare l'indice. Nel caso di elenchi, vengono utilizzati gli elementi dell'elenco. È il più flessibile ma anche dispendioso in termini di spazio dei tre tipi di indice.
:::tip
Usa `IndexType.value` per le primitive, String dove hai bisogno della clausole-where `startsWith()` e List se vuoi cercare singoli elementi.
:::
### Indice hash
È possibile eseguire l'hashing di stringhe ed elenchi per ridurre significativamente lo spazio di archiviazione richiesto dall'indice. Lo svantaggio degli indici hash è che non possono essere usati per scansioni di prefissi (clausole-where `startsWith`).
:::tip
Usa `IndexType.hash` per stringhe ed elenchi se non hai bisogno di clausole-where `startsWith` e `elementEqualTo`.
:::
### Indice HashElements
Gli elenchi di stringhe possono essere sottoposti a hash per intero (usando `IndexType.hash`), oppure gli elementi dell'elenco possono essere sottoposti a hash separatamente (usando `IndexType.hashElements`), creando in modo efficace un indice multi-voce con elementi hash.
:::tip
Usa `IndexType.hashElements` per `List` dove hai bisogno di clausole-where `elementEqualTo`.
:::
## Indici compositi
Un indice composito è un indice su più proprietà. Isar consente di creare indici compositi fino a tre proprietà.
Gli indici compositi sono anche noti come indici a più colonne.
Probabilmente è meglio iniziare con un esempio. Creiamo una collezione di persone e definiamo un indice composito sulle proprietà di età e nome:
```dart
@collection
class Person {
Id? id;
late String name;
@Index(composite: [CompositeIndex('name')])
late int age;
late String hometown;
}
```
**Dati:**
| id | nome | età | città natale |
| --- | ------ | --- | --------- |
| 1 | Daniel | 20 | Berlin |
| 2 | Anne | 20 | Paris |
| 3 | Carl | 24 | San Diego |
| 4 | Simon | 24 | Munich |
| 5 | David | 20 | New York |
| 6 | Carl | 24 | London |
| 7 | Audrey | 30 | Prague |
| 8 | Anne | 24 | Paris |
**Indici generati:**
| età | nome | id |
| --- | ------ | --- |
| 20 | Anne | 2 |
| 20 | Daniel | 1 |
| 20 | David | 5 |
| 24 | Anne | 8 |
| 24 | Carl | 3 |
| 24 | Carl | 6 |
| 24 | Simon | 4 |
| 30 | Audrey | 7 |
L'indice composito generato contiene tutte le persone ordinate per età e nome.
Gli indici compositi sono ottimi se desideri creare query efficienti ordinate in base a più proprietà. Consentono anche clausole dove avanzate con più proprietà:
```dart
final result = await isar.where()
.ageNameEqualTo(24, 'Carl')
.hometownProperty()
.findAll() // -> ['San Diego', 'London']
```
L'ultima proprietà di un indice composito supporta anche condizioni come `startsWith()` o `lessThan()`:
```dart
final result = await isar.where()
.ageEqualToNameStartsWith(20, 'Da')
.findAll() // -> [Daniel, David]
```
## Indici a più voci
Se indicizzi una lista usando 'IndexType.value', Isar creerà automaticamente un indice multi-voce e ogni voce nella lista viene indicizzata verso l'oggetto. Funziona per tutti i tipi di liste.
Le applicazioni pratiche per gli indici a voci multiple includono l'indicizzazione di un elenco di tag o la creazione di un indice full-text.
```dart
@collection
class Product {
Id? id;
late String description;
@Index(type: IndexType.value, caseSensitive: false)
List get descriptionWords => Isar.splitWords(description);
}
```
`Isar.splitWords()` divide una stringa in parole secondo la specifica [Unicode Annex #29](https://unicode.org/reports/tr29/), quindi funziona correttamente per quasi tutte le lingue.
**Dati:**
| id | description | descriptionWords |
| --- | ---------------------------- | ---------------------------- |
| 1 | comfortable blue t-shirt | [comfortable, blue, t-shirt] |
| 2 | comfortable, red pullover!!! | [comfortable, red, pullover] |
| 3 | plain red t-shirt | [plain, red, t-shirt] |
| 4 | red necktie (super red) | [red, necktie, super, red] |
Le voci con parole duplicate vengono visualizzate solo una volta nell'indice.
**Indici generati:**
| descriptionWords | id |
| ---------------- | --------- |
| comfortable | [1, 2] |
| blue | 1 |
| necktie | 4 |
| plain | 3 |
| pullover | 2 |
| red | [2, 3, 4] |
| super | 4 |
| t-shirt | [1, 3] |
Questo indice può ora essere usato per prefisso (o uguaglianza) dove clausole delle singole parole della descrizione.
:::tip
Invece di memorizzare direttamente le parole, considera anche l'utilizzo del risultato di un [algoritmo fonetico](https://en.wikipedia.org/wiki/Algoritmo_fonetico) come [Soundex](https://en.wikipedia.org/wiki/ Soundex).
:::
================================================
FILE: docs/docs/it/limitations.md
================================================
# Limitazioni
Come sapete, Isar funziona su dispositivi mobili e desktop in esecuzione su VM oltre che su Web. Entrambe le piattaforme sono molto diverse e hanno limitazioni diverse.
## Limitazioni VM
- Solo i primi 1024 byte di una stringa possono essere usati per un prefisso clausola-where
- Gli oggetti possono avere una dimensione di soli 16 MB
## Limitazioni Web
Poiché Isar Web si basa su IndexedDB, ci sono più limitazioni ma sono appena percettibili durante l'utilizzo di Isar.
- I metodi sincroni non sono supportati
- Attualmente, i filtri `Isar.splitWords()` e `.matches()` non sono ancora implementati
- Le modifiche allo schema non vengono controllate rigorosamente come nella VM, quindi fai attenzione a rispettare le regole
- Tutti i tipi di numeri sono memorizzati come double (l'unico tipo di numero js) quindi `@Size32` non ha alcun effetto
- Gli indici sono rappresentati in modo diverso, quindi gli indici hash non utilizzano meno spazio (funzionano comunque allo stesso modo)
- `col.delete()` e `col.deleteAll()` funzionano correttamente ma il valore restituito non è corretto
- `col.clear()` non reimposta il valore di incremento automatico
- `NaN` non è supportato come valore
================================================
FILE: docs/docs/it/links.md
================================================
---
title: Collegamenti
---
# Collegamenti
I collegamenti consentono di esprimere relazioni tra oggetti, come l'autore di un commento (Utente). Puoi modellare le relazioni `1:1`, `1:n` e `n:n` con i collegamenti Isar. L'uso dei collegamenti è meno ergonomico rispetto all'utilizzo di oggetti incorporati e dovresti utilizzare oggetti incorporati quando possibile.
Pensa al collegamento come a una tabella separata che contiene la relazione. È simile alle relazioni SQL ma ha un set di funzionalità e un'API diversi.
## IsarLink
`IsarLink` può contenere nessuno o un oggetto correlato e può essere utilizzato per esprimere una relazione a uno. `IsarLink` ha una singola proprietà chiamata `value` che contiene l'oggetto collegato.
I collegamenti sono pigri, quindi è necessario dire a `IsarLink` di caricare o salvare il `valore` in modo esplicito. Puoi farlo chiamando `linkProperty.load()` e `linkProperty.save()`.
:::tip
La proprietà id delle raccolte di origine e di destinazione di un collegamento deve essere non definitiva.
:::
Per i target non web, i link vengono caricati automaticamente quando li usi per la prima volta. Iniziamo aggiungendo un IsarLink a una collezione:
```dart
@collection
class Teacher {
Id? id;
late String subject;
}
@collection
class Student {
Id? id;
late String name;
final teacher = IsarLink();
}
```
Abbiamo definito un legame tra insegnanti e studenti. Ogni studente può avere esattamente un insegnante in questo esempio.
Innanzitutto, creiamo l'insegnante e lo assegniamo a uno studente. Dobbiamo effettuare un `.put()` per l'insegnante e salvare il collegamento manualmente.
```dart
final mathTeacher = Teacher()..subject = 'Math';
final linda = Student()
..name = 'Linda'
..teacher.value = mathTeacher;
await isar.writeTxn(() async {
await isar.students.put(linda);
await isar.teachers.put(mathTeacher);
await linda.teacher.save();
});
```
Ora possiamo usare il link:
```dart
final linda = await isar.students.where().nameEqualTo('Linda').findFirst();
final teacher = linda.teacher.value; // > Teacher(subject: 'Math')
```
Proviamo la stessa cosa con il codice sincrono. Non è necessario salvare il collegamento manualmente perché `.putSync()` salva automaticamente tutti i collegamenti. Crea anche l'insegnante per noi.
```dart
final englishTeacher = Teacher()..subject = 'English';
final david = Student()
..name = 'David'
..teacher.value = englishTeacher;
isar.writeTxnSync(() {
isar.students.putSync(david);
});
```
## IsarLinks
Avrebbe più senso se lo studente dell'esempio precedente potesse avere più insegnanti. Fortunatamente, Isar ha `IsarLinks`, che può contenere più oggetti correlati ed esprimere una relazione a molti.
`IsarLinks` estende `Set` ed espone tutti i metodi consentiti per gli insiemi.
`IsarLinks` si comporta in modo molto simile a `IsarLink` ed è anche pigro. Per caricare tutti gli oggetti collegati chiama `linkProperty.load()`. Per rendere persistenti le modifiche, chiama `linkProperty.save()`.
Internamente sia "IsarLink" che "IsarLinks" sono rappresentati allo stesso modo. Possiamo aggiornare `IsarLink` da prima a un `IsarLinks` per assegnare più insegnanti a un singolo studente (senza perdere dati).
```dart
@collection
class Student {
Id? id;
late String name;
final teachers = IsarLinks();
}
```
Funziona perché non abbiamo cambiato il nome del collegamento (`teacher`), quindi Isar lo ricorda da prima.
```dart
final biologyTeacher = Teacher()..subject = 'Biology';
final linda = isar.students.where()
.filter()
.nameEqualTo('Linda')
.findFirst();
print(linda.teachers); // {Teacher('Math')}
linda.teachers.add(biologyTeacher);
await isar.writeTxn(() async {
await linda.teachers.save();
});
print(linda.teachers); // {Teacher('Math'), Teacher('Biology')}
```
## Backlink
Vi sento chiedere: "E se volessimo esprimere relazioni inverse?". Non preoccuparti; ora introdurremo i backlink.
I backlink sono collegamenti nella direzione inversa. Ogni link ha sempre un backlink implicito. Puoi renderlo disponibile per la tua app annotando un `IsarLink` o `IsarLinks` con `@Backlink()`.
I backlink non richiedono memoria o risorse aggiuntive; puoi aggiungerli, rimuoverli e rinominarli liberamente senza perdere dati.
Vogliamo sapere quali studenti ha un insegnante specifico, quindi definiamo un backlink:
```dart
@collection
class Teacher {
Id id;
late String subject;
@Backlink(to: 'teacher')
final student = IsarLinks();
}
```
Dobbiamo specificare il collegamento a cui punta il backlink. È possibile avere più collegamenti diversi tra due oggetti.
## Inizializza i collegamenti
`IsarLink` e `IsarLinks` hanno un costruttore arg zero, che dovrebbe essere usato per assegnare la proprietà link quando l'oggetto viene creato. È buona norma rendere le proprietà del collegamento "finali".
Quando `metti()` il tuo oggetto per la prima volta, il collegamento viene inizializzato con la raccolta di origine e destinazione e puoi chiamare metodi come `load()` e `save()`. Un collegamento inizia a tenere traccia delle modifiche subito dopo la sua creazione, quindi puoi aggiungere e rimuovere relazioni anche prima che il collegamento venga inizializzato.
:::danger
È vietato spostare un collegamento a un altro oggetto.
:::
================================================
FILE: docs/docs/it/queries.md
================================================
---
title: Query
---
# Query
La query è il modo in cui trovi i record che soddisfano determinate condizioni, ad esempio:
- Trova tutti i contatti speciali
- Trova nomi distinti nei contatti
- Elimina tutti i contatti che non hanno il cognome definito
Poiché le query vengono eseguite sul database e non in Dart, sono molto veloci. Quando usi in modo intelligente gli indici, puoi migliorare ulteriormente le prestazioni delle query. Di seguito, imparerai come scrivere query e come renderle il più velocemente possibile.
Esistono due diversi metodi per filtrare i record: i filtri e le clausole where. Inizieremo dando un'occhiata a come funzionano i filtri.
## Filtri
I filtri sono facili da usare e da capire. A seconda del tipo di proprietà, sono disponibili diverse operazioni di filtro, la maggior parte delle quali ha nomi autoesplicativi.
I filtri funzionano valutando un'espressione per ogni oggetto della raccolta che viene filtrata. Se l'espressione si risolve in `true`, Isar include l'oggetto nei risultati. I filtri non influiscono sull'ordine dei risultati.
Utilizzeremo il seguente modello per gli esempi seguenti:
```dart
@collection
class Shoe {
Id? id;
int? size;
late String model;
late bool isUnisex;
}
```
### Condizioni di query
A seconda del tipo di campo, sono disponibili diverse condizioni.
| Condizione | Descrizione |
| ----------| ------------|
| `.equalTo(value)` | Corrisponde a valori uguali al `value` specificato. |
| `.between(lower, upper)` | Corrisponde ai valori compresi tra `lower` and `upper`. |
| `.greaterThan(bound)` | Corrisponde a valori maggiori di `bound`. |
| `.lessThan(bound)` | Corrisponde a valori inferiori a `bound`. I valori `null` verranno inclusi per impostazione predefinita perché `null` è considerato inferiore a qualsiasi altro valore. |
| `.isNull()` | Corrisponde a valori `null'.|
| `.isNotNull()` | Corrisponde a valori che non sono `null'.|
| `.length()` | Le query su List, String e lunghezza del collegamento filtrano gli oggetti in base al numero di elementi in un elenco o in un collegamento. |
Supponiamo che il database contenga quattro scarpe con le taglie 39, 40, 46 e una con una taglia non impostata (`null`). A meno che non si esegua l'ordinamento, i valori verranno restituiti ordinati per id.
```dart
isar.shoes.filter()
.sizeLessThan(40)
.findAll() // -> [39, null]
isar.shoes.filter()
.sizeLessThan(40, include: true)
.findAll() // -> [39, null, 40]
isar.shoes.filter()
.sizeBetween(39, 46, includeLower: false)
.findAll() // -> [40, 46]
```
### Operatori logici
È possibile comporre predicati utilizzando i seguenti operatori logici:
| Operatore | Descrizione |
| ---------- | ----------- |
| `.and()` | Valuta come `true` se entrambe le espressioni della lato sinistro e della lato destro restituiscono `true`. |
| `.or()` | Valuta come `true` se una delle espressioni restituisce `true`. |
| `.xor()` | Valuta come `true` se esattamente un'espressione restituisce `true`. |
| `.not()` | Nega il risultato della seguente espressione. |
| `.group()` | Raggruppa le condizioni e consente di specificare l'ordine di valutazione. |
Se vuoi trovare tutte le scarpe nella taglia 46, puoi utilizzare la seguente query:
```dart
final result = await isar.shoes.filter()
.sizeEqualTo(46)
.findAll();
```
Se vuoi usare più di una condizione, puoi combinare più filtri usando **and** logico `.and()`, **or** logico `.or()` e **xor** logico `. xor()`.
```dart
final result = await isar.shoes.filter()
.sizeEqualTo(46)
.and() // Optional. Filters are implicitly combined with logical and.
.isUnisexEqualTo(true)
.findAll();
```
Questa query equivale a: `size == 46 && isUnisex == true`.
Puoi anche raggruppare le condizioni usando `.group()`:
```dart
final result = await isar.shoes.filter()
.sizeBetween(43, 46)
.and()
.group((q) => q
.modelNameContains('Nike')
.or()
.isUnisexEqualTo(false)
)
.findAll()
```
Questa query equivale a `size >= 43 && size <= 46 && (modelName.contains('Nike') || isUnisex == false)`.
Per negare una condizione o un gruppo, usa la logica **not** `.not()`:
```dart
final result = await isar.shoes.filter()
.not().sizeEqualTo(46)
.and()
.not().isUnisexEqualTo(true)
.findAll();
```
Questa query equivale a `size != 46 && isUnisex != true`.
### Condizioni di stringa
Oltre alle condizioni di query precedenti, i valori String offrono alcune condizioni in più che puoi utilizzare. I caratteri jolly simili a Regex, ad esempio, consentono una maggiore flessibilità nella ricerca.
| Condizione | Descrizione |
| -------------------- | ----------------------------------------------------------------- |
| `.startsWith(value)` | Corrisponde ai valori di stringa che iniziano con il `valore` fornito. |
| `.contains(value)` | Corrisponde ai valori di stringa che contengono il `valore` fornito. |
| `.endsWith(value)` | Corrisponde ai valori di stringa che terminano con il `valore` fornito. |
| `.matches(wildcard)` | Corrisponde ai valori di stringa che corrispondono al modello `jolly` fornito. |
**Maiuscole/minuscole**
Tutte le operazioni sulle stringhe hanno un parametro `caseSensitive` opzionale che per impostazione predefinita è `true`.
**Wildcards:**
**Caratteri jolly:**
Una [espressione di stringa con caratteri jolly](https://en.wikipedia.org/wiki/Wildcard_character) è una stringa che utilizza caratteri normali con due caratteri jolly speciali:
- Il carattere jolly `*` corrisponde a zero o più caratteri
- Il carattere jolly `?` corrisponde a qualsiasi carattere.
Ad esempio, la stringa di caratteri jolly `"d?g"` corrisponde a `"dog"`, `"dig"` e `"dug"`, ma non a `"ding"`, `"dg"` o `" un cane"`.
### Modificatori di query
A volte è necessario creare una query in base ad alcune condizioni o per valori diversi. Isar ha uno strumento molto potente per la creazione di query condizionali:
| Modificatore | Descrizione |
| --------------------- | ---------------------------------------------------- |
| `.optional(cond, qb)` | Estende la query solo se la `condition` è `true`. Questo può essere utilizzato quasi ovunque in una query, ad esempio per ordinarlo o limitarlo in modo condizionale. |
| `.anyOf(list, qb)` | Estende la query per ogni valore in `values` e combina le condizioni utilizzando la logica **or**. |
| `.allOf(list, qb)` | Estende la query per ogni valore in `values` e combina le condizioni utilizzando **and** logici. |
In questo esempio, costruiamo un metodo in grado di trovare scarpe con un filtro opzionale:
```dart
Future> findShoes(Id? sizeFilter) {
return isar.shoes.filter()
.optional(
sizeFilter != null, // only apply filter if sizeFilter != null
(q) => q.sizeEqualTo(sizeFilter!),
).findAll();
}
```
Se vuoi trovare tutte le scarpe che hanno una di più misure di scarpe, puoi scrivere una query convenzionale o utilizzare il modificatore `anyOf()`:
```dart
final shoes1 = await isar.shoes.filter()
.sizeEqualTo(38)
.or()
.sizeEqualTo(40)
.or()
.sizeEqualTo(42)
.findAll();
final shoes2 = await isar.shoes.filter()
.anyOf(
[38, 40, 42],
(q, int size) => q.sizeEqualTo(size)
).findAll();
// shoes1 == shoes2
```
I modificatori di query sono particolarmente utili quando si desidera creare query dinamiche.
### Liste
Si possono interrogare anche le liste:
```dart
class Tweet {
Id? id;
String? text;
List hashtags = [];
}
```
È possibile eseguire query in base alla lunghezza della lista:
```dart
final tweetsWithoutHashtags = await isar.tweets.filter()
.hashtagsIsEmpty()
.findAll();
final tweetsWithManyHashtags = await isar.tweets.filter()
.hashtagsLengthGreaterThan(5)
.findAll();
```
Questi sono equivalenti al codice Dart `tweets.where((t) => t.hashtags.isEmpty);` e `tweets.where((t) => t.hashtags.length > 5);`. Puoi anche interrogare in base agli elementi dell'elenco:
```dart
final flutterTweets = await isar.tweets.filter()
.hashtagsElementEqualTo('flutter')
.findAll();
```
Questo equivale al codice Dart `tweets.where((t) => t.hashtags.contains('flutter'));`.
### Oggetti incorporati
Gli oggetti incorporati sono una delle funzionalità più utili di Isar. Possono essere interrogati in modo molto efficiente utilizzando le stesse condizioni disponibili per gli oggetti di livello superiore. Supponiamo di avere il seguente modello:
```dart
@collection
class Car {
Id? id;
Brand? brand;
}
@embedded
class Brand {
String? name;
String? country;
}
```
Vogliamo interrogare tutte le auto che hanno un marchio con il nome `"BMW"` e il paese `"Germania"`. Possiamo farlo usando la seguente query:
```dart
final germanCars = await isar.cars.filter()
.brand((q) => q
.nameEqualTo('BMW')
.and()
.countryEqualTo('Germany')
).findAll();
```
Cerca sempre di raggruppare le query nidificate. La query precedente è più efficiente della seguente. Anche se il risultato è lo stesso:
```dart
final germanCars = await isar.cars.filter()
.brand((q) => q.nameEqualTo('BMW'))
.and()
.brand((q) => q.countryEqualTo('Germany'))
.findAll();
```
### Collegamenti
Se il tuo modello contiene [link o backlink](links) puoi filtrare la tua query in base agli oggetti collegati o al numero di oggetti collegati.
:::warning
Tieni presente che le query di collegamento possono essere costose perché Isar ha bisogno di cercare oggetti collegati. Considera invece l'utilizzo di oggetti incorporati.
:::
```dart
@collection
class Teacher {
Id? id;
late String subject;
}
@collection
class Student {
Id? id;
late String name;
final teachers = IsarLinks();
}
```
Vogliamo trovare tutti gli studenti che hanno un insegnante di matematica o inglese:
```dart
final result = await isar.students.filter()
.teachers((q) {
return q.subjectEqualTo('Math')
.or()
.subjectEqualTo('English');
}).findAll();
```
I filtri di collegamento restituiscono `true` se almeno un oggetto collegato soddisfa le condizioni.
Cerchiamo tutti gli studenti che non hanno insegnanti:
```dart
final result = await isar.students.filter().teachersLengthEqualTo(0).findAll();
```
o in alternativa:
```dart
final result = await isar.students.filter().teachersIsEmpty().findAll();
```
## Clausole Where
Le clausole where sono uno strumento molto potente, ma può essere un po' difficile metterle in pratica.
A differenza dei filtri le clausole where utilizzano gli indici definiti nello schema per verificare le condizioni della query. Interrogare un indice è molto più veloce che filtrare ogni record individualmente.
➡️ Scopri di più: [Indici](indexes)
:::tip
Come regola di base, dovresti sempre cercare di ridurre il più possibile i record usando le clausole where e fare il filtraggio rimanente usando i filtri.
:::
Puoi combinare solo le clausole where usando **or** logici. In altre parole, puoi sommare più clausole where insieme, ma non puoi interrogare l'intersezione di più clausole where.
Aggiungiamo gli indici alla collezione di scarpe:
```dart
@collection
class Shoe with IsarObject {
Id? id;
@Index()
Id? size;
late String model;
@Index(composite: [CompositeIndex('size')])
late bool isUnisex;
}
```
Ci sono due indici. L'indice su `size` ci permette di usare clausole where come `.sizeEqualTo()`. L'indice composito su `isUnisex` consente dove clausole come `isUnisexSizeEqualTo()`. Ma anche `isUnisexEqualTo()` perché puoi sempre usare qualsiasi prefisso di un indice.
Ora possiamo riscrivere la query precedente che trova scarpe unisex della taglia 46 utilizzando l'indice composito. Questa query sarà molto più veloce della precedente:
```dart
final result = isar.shoes.where()
.isUnisexSizeEqualTo(true, 46)
.findAll();
```
Le clausole where hanno altri due superpoteri: ti danno l'ordinamento "gratuito" e un'operazione distinta super veloce.
### Combinare clausole where e filtri
Ricordi le query `shoes.filter()`? In realtà è solo una scorciatoia per `shoes.where().filter()`. Puoi (e dovresti) combinare dove clausole e filtri nella stessa query per utilizzare i vantaggi di entrambi:
```dart
final result = isar.shoes.where()
.isUnisexEqualTo(true)
.filter()
.modelContains('Nike')
.findAll();
```
La clausola where viene applicata per prima per ridurre il numero di oggetti da filtrare. Quindi il filtro viene applicato agli oggetti rimanenti.
## Ordinamento
È possibile definire come ordinare i risultati durante l'esecuzione della query utilizzando i metodi `.sortBy()`, `.sortByDesc()`, `.thenBy()` e `.thenByDesc()`.
Per trovare tutte le scarpe ordinate per nome del modello in ordine crescente e taglia in ordine decrescente senza utilizzare un indice:
```dart
final sortedShoes = isar.shoes.filter()
.sortByModel()
.thenBySizeDesc()
.findAll();
```
Ordinare molti risultati può essere costoso, soprattutto perché l'ordinamento avviene prima dell'offset e del limit. I metodi di ordinamento sopra non fanno mai uso di indici. Fortunatamente, possiamo di nuovo utilizzare l'ordinamento della clausola where e rendere la nostra query fulminea anche se dobbiamo ordinare un milione di oggetti.
### Ordinamento delle clausole where
Se utilizzi una clausola **singola** nella query, i risultati sono già ordinati in base all'indice. Questo è un grosso problema!
Supponiamo di avere scarpe nelle taglie `[43, 39, 48, 40, 42, 45]` e di voler trovare tutte le scarpe con una taglia maggiore di `42` e anche ordinarle per taglia:
```dart
final bigShoes = isar.shoes.where()
.sizeGreaterThan(42) // also sorts the results by size
.findAll(); // -> [43, 45, 48]
```
Come puoi vedere, il risultato è ordinato in base all'indice `size`. Se vuoi invertire l'ordinamento della clausola where, puoi impostare `sort` su `Sort.desc`:
```dart
final bigShoesDesc = await isar.shoes.where(sort: Sort.desc)
.sizeGreaterThan(42)
.findAll(); // -> [48, 45, 43]
```
A volte non si desidera utilizzare una clausola where ma comunque beneficiare dell'ordinamento implicito. Puoi usare la clausola `any` where:
```dart
final shoes = await isar.shoes.where()
.anySize()
.findAll(); // -> [39, 40, 42, 43, 45, 48]
```
Se utilizzi un indice composto, i risultati vengono ordinati in base a tutti i campi dell'indice.
:::tip
Se hai bisogno che i risultati siano ordinati, considera l'utilizzo di un indice a tale scopo. Soprattutto se lavori con `offset()` e `limit()`.
:::
A volte non è possibile o utile utilizzare un indice per l'ordinamento. In questi casi, dovresti utilizzare gli indici per ridurre il più possibile il numero di voci risultanti.
## Valori univoci
Per restituire solo voci con valori univoci, utilizzare il predicato distinto. Ad esempio, per scoprire quanti diversi modelli di scarpe hai nel tuo database Isar:
```dart
final shoes = await isar.shoes.filter()
.distinctByModel()
.findAll();
```
Puoi anche concatenare più condizioni distinte per trovare tutte le scarpe con combinazioni di taglia modello distinte:
```dart
final shoes = await isar.shoes.filter()
.distinctByModel()
.distinctBySize()
.findAll();
```
Viene restituito solo il primo risultato di ogni combinazione distinta. È possibile utilizzare le clausole where e le operazioni di ordinamento per controllarlo.
### Clausola where distinta
Se hai un indice non univoco, potresti voler ottenere tutti i suoi valori distinti. Potresti usare l'operazione `distinctBy` della sezione precedente, ma viene eseguita dopo l'ordinamento e i filtri, quindi c'è un po' di sovraccarico.
Se utilizzi solo una singola clausola where, puoi invece fare affidamento sull'indice per eseguire l'operazione distinta.
```dart
final shoes = await isar.shoes.where(distinct: true)
.anySize()
.findAll();
```
:::tip
In teoria, potresti anche usare più clausole where per l'ordinamento e la distinzione. L'unica restrizione è per quelle clausole where che non si sovrappongono e utilizzano lo stesso indice. Per un corretto ordinamento, devono anche essere applicati in ordine di ordinamento. Stai molto attento se fai affidamento su questo!
:::
## Offset e limit
Spesso è una buona idea limitare il numero di risultati di una query per le visualizzazioni lazy di liste. Puoi farlo impostando un `limit()`:
```dart
final firstTenShoes = await isar.shoes.where()
.limit(10)
.findAll();
```
Impostando un `offset()` puoi anche impaginare i risultati della tua query.
```dart
final firstTenShoes = await isar.shoes.where()
.offset(20)
.limit(10)
.findAll();
```
Poiché la creazione di un'istanza di oggetti Dart è spesso la parte più costosa dell'esecuzione di una query, è una buona idea caricare solo gli oggetti necessari.
## Ordine di esecuzione
Isar esegue le query sempre nello stesso ordine:
1. Attraversa l'indice primario o secondario per trovare gli oggetti (applica le clausole where)
2. Filtra gli oggetti
3. Ordina i risultati
4. Applicare un'operazione distinta
5. Risultato offset e limite
6. Restituisci i risultati
## Operazioni di query
Negli esempi precedenti, abbiamo usato `.findAll()` per recuperare tutti gli oggetti corrispondenti. Ci sono più operazioni disponibili, tuttavia:
| Operazione | Descrizione |
| ---------------- | ------------------------------------------------------------------------------------------------------------------- |
| `.findFirst()` | Recupera solo il primo oggetto corrispondente o `null` se nessuno corrisponde. |
| `.findAll()` | Recupera tutti gli oggetti corrispondenti. |
| `.count()` | Conta quanti oggetti corrispondono alla query. |
| `.deleteFirst()` | Elimina il primo oggetto corrispondente dalla raccolta. |
| `.deleteAll()` | Elimina tutti gli oggetti corrispondenti dalla raccolta. |
| `.build()` | Compila la query per riutilizzarla in seguito. Ciò consente di risparmiare il costo per creare una query se si desidera eseguirla più volte. |
## Query sulla proprietà
Se sei interessato solo ai valori di una singola proprietà, puoi utilizzare una query di proprietà. Basta creare una query normale e selezionare una proprietà:
```dart
List models = await isar.shoes.where()
.modelProperty()
.findAll();
List sizes = await isar.shoes.where()
.sizeProperty()
.findAll();
```
L'utilizzo di una sola proprietà consente di risparmiare tempo durante la deserializzazione. Le query sulle proprietà funzionano anche per gli oggetti e gli elenchi incorporati.
## Aggregazione
Isar supporta l'aggregazione dei valori di una query di proprietà. Sono disponibili le seguenti operazioni di aggregazione:
| Operazione | Descrizione |
| ------------ | -------------------------------------------------------------- |
| `.min()` | Trova il valore minimo o `null` se nessuno corrisponde. |
| `.max()` | Trova il valore massimo o `null` se nessuno corrisponde. |
| `.sum()` | Somma tutti i valori. |
| `.average()` | Calcola la media di tutti i valori o 'NaN' se nessuno corrisponde. |
L'utilizzo delle aggregazioni è molto più veloce rispetto alla ricerca di tutti gli oggetti corrispondenti e all'esecuzione manuale dell'aggregazione.
## Query dinamiche
:::danger
Questa sezione molto probabilmente non è rilevante per te. È sconsigliato utilizzare query dinamiche a meno che non sia assolutamente necessario (e raramente lo fai).
:::
Tutti gli esempi precedenti hanno utilizzato QueryBuilder e i metodi di estensione statica generati. Forse vuoi creare query dinamiche o un linguaggio di query personalizzato (come Isar Inspector). In tal caso, puoi usare il metodo `buildQuery()`:
| Parametro | Descrizione |
| --------------- | ------------------------------------------------------------------------------------------- |
| `whereClauses` | Le clausole where della query. |
| `whereDistinct` | Se le clausole where devono restituire valori distinti (utile solo per clausole where singole). |
| `whereSort` | L'ordine di scorrimento delle clausole where (utile solo per le clausole where singole). |
| `filter` | Il filtro da applicare ai risultati. |
| `sortBy` | Un elenco di proprietà da ordinare. |
| `distinctBy` | Un elenco di proprietà da distinguere. |
| `offset` | L'offset dei risultati. |
| `limit` | Il numero massimo di risultati da restituire. |
| `property` | Se non-null, vengono restituiti solo i valori di questa proprietà. |
Creiamo una query dinamica:
```dart
final shoes = await isar.shoes.buildQuery(
whereClauses: [
WhereClause(
indexName: 'size',
lower: [42],
includeLower: true,
upper: [46],
includeUpper: true,
)
],
filter: FilterGroup.and([
FilterCondition(
type: ConditionType.contains,
property: 'model',
value: 'nike',
caseSensitive: false,
),
FilterGroup.not(
FilterCondition(
type: ConditionType.contains,
property: 'model',
value: 'adidas',
caseSensitive: false,
),
),
]),
sortBy: [
SortProperty(
property: 'model',
sort: Sort.desc,
)
],
offset: 10,
limit: 10,
).findAll();
```
La seguente query è equivalente:
```dart
final shoes = await isar.shoes.where()
.sizeBetween(42, 46)
.filter()
.modelContains('nike', caseSensitive: false)
.not()
.modelContains('adidas', caseSensitive: false)
.sortByModelDesc()
.offset(10).limit(10)
.findAll();
```
================================================
FILE: docs/docs/it/recipes/data_migration.md
================================================
---
title: Migrazione dei dati
---
# Migrazione dei dati
Isar migra automaticamente gli schemi del database se aggiungi o rimuovi raccolte, campi o indici. A volte potresti voler migrare anche i tuoi dati. Isar non offre una soluzione integrata perché imporrebbe restrizioni alle migrazioni arbitrarie. È facile implementare la logica di migrazione adatta alle tue esigenze.
Vogliamo utilizzare una singola versione per l'intero database in questo esempio. Utilizziamo le preferenze condivise per archiviare la versione corrente e confrontarla con la versione a cui vogliamo migrare. Se le versioni non corrispondono, migriamo i dati e aggiorniamo la versione.
:::tip
Puoi anche assegnare a ciascuna raccolta la propria versione e migrarle individualmente.
:::
Immagina di avere una raccolta di utenti con un campo di compleanno. Nella versione 2 della nostra app, abbiamo bisogno di un campo aggiuntivo per l'anno di nascita per interrogare gli utenti in base all'età.
Versione 1:
```dart
@collection
class User {
Id? id;
late String name;
late DateTime birthday;
}
```
Versione 2:
```dart
@collection
class User {
Id? id;
late String name;
late DateTime birthday;
short get birthYear => birthday.year;
}
```
Il problema è che i modelli utente esistenti avranno un campo `birthYear` vuoto perché non esisteva nella versione 1. Abbiamo bisogno di migrare i dati per impostare il campo `birthYear`.
```dart
import 'package:isar/isar.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() async {
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[UserSchema],
directory: dir.path,
);
await performMigrationIfNeeded(isar);
runApp(MyApp(isar: isar));
}
Future performMigrationIfNeeded(Isar isar) async {
final prefs = await SharedPreferences.getInstance();
final currentVersion = prefs.getInt('version') ?? 2;
switch(currentVersion) {
case 1:
await migrateV1ToV2(isar);
break;
case 2:
// If the version is not set (new installation) or already 2, we do not need to migrate
return;
default:
throw Exception('Unknown version: $currentVersion');
}
// Update version
await prefs.setInt('version', 2);
}
Future migrateV1ToV2(Isar isar) async {
final userCount = await isar.users.count();
// We paginate through the users to avoid loading all users into memory at once
for (var i = 0; i < userCount; i += 50) {
final users = await isar.users.where().offset(i).limit(50).findAll();
await isar.writeTxn((isar) async {
// We don't need to update anything since the birthYear getter is used
await isar.users.putAll(users);
});
}
}
```
:::warning
Se devi migrare molti dati, prendi in considerazione l'utilizzo di un isolamento in background per evitare sollecitazioni sul thread dell'interfaccia utente.
:::
================================================
FILE: docs/docs/it/recipes/full_text_search.md
================================================
---
title: Ricerca full-text
---
# Ricerca full-text
La ricerca full-text è un modo efficace per cercare il testo nel database. Dovresti già avere familiarità con il funzionamento degli [indici](../indexes.md), ma andiamo oltre le basi.
Un indice funziona come una tabella di ricerca, consentendo al motore di query di trovare rapidamente i record con un determinato valore. Ad esempio, se hai un campo `title` nel tuo oggetto, puoi creare un indice su quel campo per rendere più veloce la ricerca di oggetti con un determinato titolo.
## Perché la ricerca full-text è utile?
Puoi cercare facilmente il testo usando i filtri. Esistono varie operazioni sulle stringhe, ad esempio `.startsWith()`, `.contains()` e `.matches()`. Il problema con i filtri è che il loro runtime è `O(n)` dove `n` è il numero di record nella raccolta. Le operazioni sulle stringhe come `.matches()` sono particolarmente costose.
:::tip
La ricerca full-text è molto più veloce dei filtri, ma gli indici presentano alcune limitazioni. In questa ricetta, esploreremo come aggirare queste limitazioni.
:::
## Esempio di base
L'idea è sempre la stessa: invece di indicizzare l'intero testo, indicizziamo le parole nel testo in modo da poterle cercare singolarmente.
Creiamo l'indice full-text più semplice:
```dart
class Message {
Id? id;
late String content;
@Index()
List get contentWords => content.split(' ');
}
```
Ora possiamo cercare messaggi con parole specifiche nel contenuto:
```dart
final posts = await isar.messages
.where()
.contentWordsAnyEqualTo('hello')
.findAll();
```
Questa query è super veloce, ma ci sono alcuni problemi:
1. Possiamo cercare solo parole intere
2. Non consideriamo la punteggiatura
3. Non supportiamo altri caratteri di spazio vuoto
## Dividere il testo nel modo giusto
Proviamo a migliorare l'esempio precedente. Potremmo provare a sviluppare un'espressione regolare complicata per correggere la divisione delle parole, ma probabilmente sarà lenta e sbagliata per i casi limite.
L'[Unicode Annex #29](https://unicode.org/reports/tr29/) definisce come dividere correttamente il testo in parole per quasi tutte le lingue. È piuttosto complicato, ma fortunatamente Isar fa il lavoro pesante per noi:
```dart
Isar.splitWords('hello world'); // -> ['hello', 'world']
Isar.splitWords('The quick (“brown”) fox can’t jump 32.3 feet, right?');
// -> ['The', 'quick', 'brown', 'fox', 'can’t', 'jump', '32.3', 'feet', 'right']
```
## Voglio più controllo
Facilissimo! Possiamo modificare il nostro indice anche per supportare la corrispondenza dei prefissi e la corrispondenza senza distinzione tra maiuscole e minuscole:
```dart
class Post {
Id? id;
late String title;
@Index(type: IndexType.value, caseSensitive: false)
List get titleWords => title.split(' ');
}
```
Per impostazione predefinita, Isar memorizzerà le parole come valori hash che sono veloci ed efficienti in termini di spazio. Ma gli hash non possono essere usati per la corrispondenza dei prefissi. Usando `IndexType.value`, possiamo cambiare l'indice per usare invece le parole direttamente. Ci fornisce la clausola where `.titleWordsAnyStartsWith()`:
```dart
final posts = await isar.posts
.where()
.titleWordsAnyStartsWith('hel')
.or()
.titleWordsAnyStartsWith('welco')
.or()
.titleWordsAnyStartsWith('howd')
.findAll();
```
## Ho anche bisogno di `.endsWith()`
Sicuramente! Useremo un trucco per ottenere la corrispondenza `.endsWith()`:
```dart
class Post {
Id? id;
late String title;
@Index(type: IndexType.value, caseSensitive: false)
List get revTitleWords {
return Isar.splitWords(title).map(
(word) => word.reversed).toList()
);
}
}
```
Non dimenticare di invertire il finale che vuoi cercare:
```dart
final posts = await isar.posts
.where()
.revTitleWordsAnyStartsWith('lcome'.reversed)
.findAll();
```
## Algoritmi di derivazione
Sfortunatamente, gli indici non supportano la corrispondenza `.contains()` (questo vale anche per altri database). Ma ci sono alcune alternative che vale la pena esplorare. La scelta dipende molto dal tuo utilizzo. Un esempio è l'indicizzazione delle radici delle parole anziché dell'intera parola.
Un algoritmo stemming è un processo di normalizzazione linguistica in cui le forme varianti di una parola sono ridotte a una forma comune:
```
connection
connections
connective ---> connect
connected
connecting
```
Gli algoritmi più diffusi sono [Algoritmo di stemming di Porter](https://tartarus.org/martin/PorterStemmer/) e [Algoritmi di stemming di Snowball](https://snowballstem.org/algorithms/).
Esistono anche forme più avanzate come [lemmatizzazione](https://en.wikipedia.org/wiki/Lemmatizzazione).
## Algoritmi fonetici
Un [algoritmo fonetico](https://en.wikipedia.org/wiki/Phonetic_algorithm) è un algoritmo per indicizzare le parole in base alla loro pronuncia. In altre parole, ti permette di trovare parole che suonano simili a quelle che stai cercando.
:::warning
La maggior parte degli algoritmi fonetici supporta solo una singola lingua.
:::
### Soundex
[Soundex](https://en.wikipedia.org/wiki/Soundex) è un algoritmo fonetico per indicizzare i nomi in base al suono, come si pronuncia in inglese. L'obiettivo è che gli omofoni siano codificati nella stessa rappresentazione in modo che possano essere abbinati nonostante piccole differenze nell'ortografia. È un algoritmo semplice e ci sono più versioni migliorate.
Usando questo algoritmo, sia `"Robert"` che `"Rupert"` restituiscono la stringa `"R163"` mentre `"Rubin"` restituisce `"R150"`. `"Ashcraft"` e `"Ashcroft"` producono entrambi `"A261"`.
### Double Metaphone
L'algoritmo di codifica fonetica [Double Metaphone](https://en.wikipedia.org/wiki/Metaphone) è la seconda generazione di questo algoritmo. Apporta diversi miglioramenti di progettazione fondamentali rispetto all'algoritmo Metaphone originale.
Double Metaphone spiega varie irregolarità in inglese di origine slava, germanica, celtica, greca, francese, italiana, spagnola, cinese e di altro tipo.
================================================
FILE: docs/docs/it/recipes/multi_isolate.md
================================================
---
title: Utilizzo multi-isolate
---
# Utilizzo multi-isolate
Invece dei thread, tutto il codice Dart viene eseguito all'interno degli isolate. Ogni isolate ha il proprio heap di memoria, assicurando che nessuno degli stati in un isolate sia accessibile da qualsiasi altro isolate.
È possibile accedere a Isar da più isolate contemporaneamente e anche gli osservatori lavorano tra gli isolate. In questa ricetta vedremo come utilizzare Isar in un ambiente multiisolate.
## Quando utilizzare più isolate
Le transazioni Isar vengono eseguite in parallelo anche se eseguite nello stesso isolate. In alcuni casi, è comunque vantaggioso accedere all'Isar da più isolate.
Il motivo è che Isar impiega parecchio tempo a codificare e decodificare i dati da e verso gli oggetti Dart. Puoi pensarlo come codifica e decodifica JSON (solo più efficiente). Queste operazioni vengono eseguite all'interno dell'isolate da cui si accede ai dati e bloccano naturalmente altro codice nell'isolate. In altre parole: Isar esegue parte del lavoro nel tuo isolate Dart.
Se hai solo bisogno di leggere o scrivere poche centinaia di oggetti contemporaneamente, farlo nell'isolate dell'interfaccia utente non è un problema. Ma per transazioni enormi o se il thread dell'interfaccia utente è già occupato, dovresti considerare l'utilizzo di un isolate separato.
## Esempio
La prima cosa che dobbiamo fare è aprire l'Isar nel nuovo isolate. Poiché l'istanza di Isar è già aperta nell'isolate principale, `Isar.open()` restituirà la stessa istanza.
:::warning
Assicurati di fornire gli stessi schemi dell'isolate principale. In caso contrario, riceverai un errore.
:::
`compute()` avvia un nuovo isolate in Flutter ed esegue la funzione data in esso.
```dart
void main() {
// Open Isar in the UI isolate
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[MessageSchema],
directory: dir.path,
name: 'myInstance',
);
// listen to changes in the database
isar.messages.watchLazy(() {
print('omg the messages changed!');
});
// start a new isolate and create 10000 messages
compute(createDummyMessages, 10000).then(() {
print('isolate finished');
});
// after some time:
// > omg the messages changed!
// > isolate finished
}
// function that will be executed in the new isolate
Future createDummyMessages(int count) async {
// we don't need the path here because the instance is already open
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[PostSchema],
directory: dir.path,
name: 'myInstance',
);
final messages = List.generate(count, (i) => Message()..content = 'Message $i');
// we use a synchronous transactions in isolates
isar.writeTxnSync(() {
isar.messages.insertAllSync(messages);
});
}
```
Ci sono alcune cose interessanti da notare nell'esempio sopra:
- `isar.messages.watchLazy()` viene chiamato nell'isolate dell'interfaccia utente e viene notificato delle modifiche da un altro isolate.
- Le istanze sono referenziate per nome. Il nome predefinito è `default`, ma in questo esempio lo impostiamo su `myInstance`.
- Abbiamo utilizzato una transazione sincrona per creare i messaggi. Bloccare il nostro nuovo isolate non è un problema e le transazioni sincrone sono un po' più veloci.
================================================
FILE: docs/docs/it/recipes/string_ids.md
================================================
---
title: ID stringa
---
# ID stringa
Questa è una delle richieste più frequenti che ricevo, quindi ecco un tutorial sull'uso di String ID.
Isar non supporta nativamente gli ID di stringa e c'è una buona ragione: gli ID interi sono molto più efficienti e veloci. Soprattutto per i collegamenti, l'overhead di un ID stringa è troppo significativo.
Comprendo che a volte devi archiviare dati esterni che utilizzano UUID o altri ID non interi. Consiglio di archiviare l'ID String come proprietà nell'oggetto e di utilizzare un'implementazione hash veloce per generare un int a 64 bit che può essere utilizzato come ID.
```dart
@collection
class User {
String? id;
Id get isarId => fastHash(id!);
String? name;
int? age;
}
```
Con questo approccio, ottieni il meglio da entrambi i mondi: ID interi efficienti per i collegamenti e la possibilità di utilizzare ID di stringa.
## Funzione hash veloce
Idealmente, la tua funzione hash dovrebbe avere un'alta qualità (non vuoi collisioni) ed essere veloce. Consiglio di utilizzare la seguente implementazione:
```dart
/// FNV-1a 64bit hash algorithm optimized for Dart Strings
int fastHash(String string) {
var hash = 0xcbf29ce484222325;
var i = 0;
while (i < string.length) {
final codeUnit = string.codeUnitAt(i++);
hash ^= codeUnit >> 8;
hash *= 0x100000001b3;
hash ^= codeUnit & 0xFF;
hash *= 0x100000001b3;
}
return hash;
}
```
Se scegli una funzione hash diversa, assicurati che restituisca un int a 64 bit ed evita di usare una funzione hash crittografica perché sono molto più lente.
:::warning
Evita di usare `string.hashCode` perché non è garantito che sia stabile su piattaforme e versioni diverse di Dart.
:::
================================================
FILE: docs/docs/it/schema.md
================================================
---
title: Schema
---
# Schema
Quando utilizzi Isar per archiviare i dati della tua app, hai a che fare con le raccolte. Una raccolta è come una tabella di database nel database Isar associato e può contenere solo un singolo tipo di oggetto Dart. Ogni oggetto della raccolta rappresenta una riga di dati nella raccolta corrispondente.
Una definizione di raccolta è chiamata "schema". Isar Generator farà il lavoro pesante per te e genererà la maggior parte del codice necessario per utilizzare la raccolta.
## Anatomia di una collezione
Definisci ogni collezione Isar annotando una classe con `@collection` o `@Collection()`. Una raccolta Isar include campi per ogni colonna nella tabella corrispondente nel database, incluso uno che comprende la chiave primaria.
Il codice seguente è un esempio di una raccolta semplice che definisce una tabella `User` con colonne per ID, nome e cognome:
```dart
@collection
class User {
Id? id;
String? firstName;
String? lastName;
}
```
:::tip
Per rendere permanente un campo, Isar deve avervi accesso. Puoi assicurarti che Isar abbia accesso a un campo rendendolo pubblico o fornendo metodi getter e setter.
:::
Ci sono alcuni parametri opzionali per personalizzare la collezione:
| Config | Description |
| ------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `inheritance` | Controlla se i campi delle classi padre e dei mixin verranno archiviati in Isar. Abilitato per impostazione predefinita. |
| `accessor` | Consente di rinominare la funzione di accesso predefinita della raccolta (ad esempio `isar.contacts` per la raccolta `Contact`). |
| `ignore` | Consente di ignorare determinate proprietà. Questi sono anche rispettati per le super classi. |
### Isar ID
Ogni classe di raccolta deve definire una proprietà id con il tipo 'Id' che identifica in modo univoco un oggetto. `Id` è solo un alias per `int` che permette a Isar Generator di riconoscere la proprietà id.
Isar indicizza automaticamente i campi id, il che ti consente di ottenere e modificare gli oggetti in base al loro id in modo efficiente.
Puoi impostare gli ID da solo o chiedere a Isar di assegnare un ID con incremento automatico. Se il campo `id` è `null` e non `finale`, Isar assegnerà un id di autoincremento. Se vuoi un ID di incremento automatico non annullabile, puoi usare `Isar.autoIncrement` invece di `null`.
:::tip
Gli ID di incremento automatico non vengono riutilizzati quando un oggetto viene eliminato. L'unico modo per reimpostare gli ID di incremento automatico è cancellare il database.
:::
### Rinominare raccolte e campi
Per impostazione predefinita, Isar utilizza il nome della classe come nome della raccolta. Allo stesso modo, Isar utilizza i nomi dei campi come nomi di colonne nel database. Se desideri che una raccolta o un campo abbia un nome diverso, aggiungi l'annotazione `@Name`. L'esempio seguente mostra i nomi personalizzati per la raccolta e i campi:
```dart
@collection
@Name("User")
class MyUserClass1 {
@Name("id")
Id myObjectId;
@Name("firstName")
String theFirstName;
@Name("lastName")
String familyNameOrWhatever;
}
```
Soprattutto se vuoi rinominare i campi o le classi Dart che sono già archiviati nel database, dovresti considerare di usare l'annotazione `@Name`. In caso contrario, il database eliminerà e ricreerà il campo o la raccolta.
### Ignorare i campi
Isar mantiene tutti i campi pubblici di una classe di raccolta. Annotando una proprietà o un getter con `@ignore`, puoi escluderlo dalla persistenza, come mostrato nel seguente frammento di codice:
```dart
@collection
class User {
Id? id;
String? firstName;
String? lastName;
@ignore
String? password;
}
```
Nei casi in cui una raccolta eredita i campi da una raccolta padre, di solito è più semplice utilizzare la proprietà `ignore` dell'annotazione `@Collection`:
```dart
@collection
class User {
Image? profilePicture;
}
@Collection(ignore: {'profilePicture'})
class Member extends User {
Id? id;
String? firstName;
String? lastName;
}
```
Se una collezione contiene un campo con un tipo non supportato da Isar, devi ignorare il campo.
:::warning
Tieni presente che non è buona norma memorizzare informazioni in oggetti Isar che non sono persistenti.
:::
## Tipi supportati
Isar supporta i seguenti tipi di dati:
- `bool`
- `byte`
- `short`
- `int`
- `float`
- `double`
- `DateTime`
- `String`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
Inoltre, sono supportati oggetti incorporati ed enumerazioni. Tratteremo quelli di seguito.
## byte, short, float
Per molti casi d'uso, non è necessario l'intero intervallo di un intero o doppio a 64 bit. Isar supporta tipi aggiuntivi che consentono di risparmiare spazio e memoria durante la memorizzazione di numeri più piccoli.
| Tipo | Dim. in bytes | Range |
| ---------- | ------------- | ------------------------------------------------------- |
| **byte** | 1 | 0 to 255 |
| **short** | 4 | -2,147,483,647 to 2,147,483,647 |
| **int** | 8 | -9,223,372,036,854,775,807 to 9,223,372,036,854,775,807 |
| **float** | 4 | -3.4e38 to 3.4e38 |
| **double** | 8 | -1.7e308 to 1.7e308 |
I tipi di numeri aggiuntivi sono solo alias per i tipi Dart nativi, quindi usare `short`, ad esempio, funziona come usare `int`.
Ecco una raccolta di esempio contenente tutti i tipi di cui sopra:
```dart
@collection
class TestCollection {
Id? id;
late byte byteValue;
short? shortValue;
int? intValue;
float? floatValue;
double? doubleValue;
}
```
Tutti i tipi di numeri possono essere utilizzati anche negli elenchi. Per memorizzare i byte, dovresti usare `List`.
## Tipi annullabili
Comprendere come funziona l'annullamento dei valori in Isar è essenziale: i tipi numerici **NON** hanno una rappresentazione `null` dedicata. Viene invece utilizzato un valore specifico:
| Type | VM |
| ---------- | ------------- |
| **short** | `-2147483648` |
| **int** | `int.MIN` |
| **float** | `double.NaN` |
| **double** | `double.NaN` |
`bool`, `String` e `List` hanno una rappresentazione `null` separata.
Questo comportamento consente miglioramenti delle prestazioni e ti consente di modificare liberamente la capacità di Null dei tuoi campi senza richiedere la migrazione o codice speciale per gestire i valori `null`.
:::warning
Il tipo `byte` non supporta valori nulli.
:::
## DateTime
Isar non memorizza le informazioni sul fuso orario delle tue date. Invece, converte `DateTime`s in UTC prima di archiviarli. Isar restituisce tutte le date nell'ora locale.
I `DateTime`s vengono archiviati con una precisione di microsecondi. Nei browser è supportata solo la precisione in millisecondi a causa delle limitazioni di JavaScript.
## Enum
Isar consente di archiviare e utilizzare le enumerazioni come altri tipi di Isar. Devi scegliere, tuttavia, come Isar deve rappresentare le enumerazioni sul disco. Isar supporta quattro diverse strategie:
| EnumType | Descrizione |
| ----------- | -------------------------------------------------------------------------------------------------------------- |
| `ordinal` | TL'indice dell'enum è memorizzato come `byte`. Questo è molto efficiente ma non consente enumerazioni nullable |
| `ordinal32` | L'indice dell'enumerazione viene archiviato come `short` (4-byte integer). |
| `name` | Il nome dell'enumerazione viene memorizzato come `String`. |
| `value` | Per recuperare il valore dell'enumerazioni viene utilizzata una proprietà personalizzata. |
:::warning
`ordinal` e `ordinal32` dipendono dall'ordine dei valori dell'enumerazione. Se modifichi l'ordine, i database esistenti restituiranno valori errati.
:::
Diamo un'occhiata a un esempio per ciascuna strategia.
```dart
@collection
class EnumCollection {
Id? id;
@enumerated // same as EnumType.ordinal
late TestEnum byteIndex; // cannot be nullable
@Enumerated(EnumType.ordinal)
late TestEnum byteIndex2; // cannot be nullable
@Enumerated(EnumType.ordinal32)
TestEnum? shortIndex;
@Enumerated(EnumType.name)
TestEnum? name;
@Enumerated(EnumType.value, 'myValue')
TestEnum? myValue;
}
enum TestEnum {
first(10),
second(100),
third(1000);
const TestEnum(this.myValue);
final short myValue;
}
```
Naturalmente, Enums può essere utilizzato anche nelle liste.
## Oggetti incorporati
Spesso è utile avere oggetti nidificati nel modello di raccolta. Non c'è limite a quanto in profondità puoi annidare gli oggetti. Tieni presente, tuttavia, che l'aggiornamento di un oggetto profondamente nidificato richiederà la scrittura dell'intero albero degli oggetti nel database.
```dart
@collection
class Email {
Id? id;
String? title;
Recepient? recipient;
}
@embedded
class Recepient {
String? name;
String? address;
}
```
Gli oggetti incorporati possono essere nulli ed estendere altri oggetti. L'unico requisito è che siano annotati con `@embedded` e abbiano un costruttore predefinito senza parametri richiesti.
================================================
FILE: docs/docs/it/transactions.md
================================================
---
title: Transazioni
---
# Transazioni
In Isar, le transazioni combinano più operazioni di database in un'unica unità di lavoro. La maggior parte delle interazioni con Isar utilizza implicitamente le transazioni. L'accesso in lettura e scrittura in Isar è conforme a [ACID](http://en.wikipedia.org/wiki/ACID). Le transazioni vengono automaticamente annullate se si verifica un errore.
## Transazioni esplicite
In una transazione esplicita, ottieni uno snapshot coerente del database. Cerca di ridurre al minimo la durata delle transazioni. È vietato effettuare chiamate di rete o altre operazioni di lunga durata in una transazione.
Le transazioni (in particolare le transazioni di scrittura) hanno un costo e dovresti sempre provare a raggruppare le operazioni successive in un'unica transazione.
Le transazioni possono essere sincrone o asincrone. Nelle transazioni sincrone è possibile utilizzare solo operazioni sincrone. Nelle transazioni asincrone, solo operazioni asincrone.
| | Read | Read & Write |
|--------------|--------------|--------------------|
| Synchronous | `.txnSync()` | `.writeTxnSync()` |
| Asynchronous | `.txn()` | `.writeTxn()` |
### Transazioni di lettura
Le transazioni di lettura esplicita sono facoltative, ma consentono di eseguire letture atomiche e fare affidamento su uno stato coerente del database all'interno della transazione. Internamente Isar utilizza sempre transazioni di lettura implicita per tutte le operazioni di lettura.
:::tip
Le transazioni di lettura asincrone vengono eseguite in parallelo ad altre transazioni di lettura e scrittura. Abbastanza bello, vero?
:::
### Transazioni di scrittura
A differenza delle operazioni di lettura, le operazioni di scrittura in Isar devono essere racchiuse in una transazione esplicita.
Quando una transazione di scrittura viene completata correttamente, viene automaticamente salvata e tutte le modifiche vengono scritte su disco. Se si verifica un errore, la transazione viene interrotta e tutte le modifiche vengono annullate. Le transazioni sono "tutto o niente": o tutte le scritture all'interno di una transazione hanno esito positivo o nessuna di esse ha effetto per garantire la coerenza dei dati.
:::warning
Quando un'operazione di database ha esito negativo, la transazione viene interrotta e non deve più essere utilizzata. Anche se catturi l'errore in Dart.
:::
```dart
@collection
class Contact {
Id? id;
String? name;
}
// GOOD
await isar.writeTxn(() async {
for (var contact in getContacts()) {
await isar.contacts.put(contact);
}
});
// BAD: move loop inside transaction
for (var contact in getContacts()) {
await isar.writeTxn(() async {
await isar.contacts.put(contact);
});
}
```
================================================
FILE: docs/docs/it/tutorials/quickstart.md
================================================
---
title: Avvio rapido
---
# Avvio rapido
Santi numi, sei qui! Iniziamo a usare il database Flutter più interessante in circolazione...
In questa guida introduttiva saremo a corto di parole e veloci nel codice.
## 1. Aggiungi dipendenze
Prima che inizii il divertimento, dobbiamo aggiungere alcuni pacchetti a `pubspec.yaml`. Possiamo usare il pub per facilitarci il lavoro pesante.
```bash
dart pub add isar:^0.0.0-placeholder isar_flutter_libs:^0.0.0-placeholder --hosted-url=https://pub.isar-community.dev
dart pub add dev:isar_generator:^0.0.0-placeholder --hosted-url=https://pub.isar-community.dev
```
## 2. Annota le classi
Annota le tue classi collection con `@collection` e scegli un campo `Id`.
```dart
part 'user.g.dart';
@collection
class User {
Id id = Isar.autoIncrement; // puoi anche usare id = null per incrementare automaticamente
String? name;
int? age;
}
```
Gli ID identificano in modo univoco gli oggetti in una collezione e ti consentono di ritrovarli in seguito.
## 3. Esegui il generatore di codice
Esegui il seguente comando per avviare `build_runner`:
```
dart run build_runner build
```
Se stai usando Flutter, usa quanto segue:
```
flutter pub run build_runner build
```
## 4. Apri l'istanza Isar
Apri una nuova istanza Isar e passa tutti i tuoi schemi di raccolte. Facoltativamente, puoi specificare un nome di istanza e una directory.
```dart
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[UserSchema],
directory: dir.path,
);
```
## 5. Scrivi e leggi
Una volta aperta l'istanza, puoi iniziare a utilizzare le raccolte.
Tutte le operazioni CRUD di base sono disponibili tramite "IsarCollection".
```dart
final newUser = User()..name = 'Jane Doe'..age = 36;
await isar.writeTxn(() async {
await isar.users.put(newUser); // insert & update
});
final existingUser = await isar.users.get(newUser.id); // get
await isar.writeTxn(() async {
await isar.users.delete(existingUser.id!); // delete
});
```
## Altre risorse
Sei uno studente visivo? Guarda questi video per iniziare con Isar:
================================================
FILE: docs/docs/it/watchers.md
================================================
---
title: Osservatori
---
# Osservatori
Isar permette di sottoscrivere le modifiche al database. Puoi "osservare" le modifiche in un oggetto specifico, un'intera raccolta o una query.
Gli osservatori consentono di reagire in modo efficiente alle modifiche nel database. Ad esempio, puoi ricostruire la tua interfaccia utente quando viene aggiunto un contatto, inviare una richiesta di rete quando un documento viene aggiornato, ecc.
Un osservatore riceve una notifica dopo che una transazione è stata eseguita correttamente e la destinazione cambia effettivamente.
## Osservare gli oggetti
Se vuoi essere avvisato quando un oggetto specifico viene creato, aggiornato o eliminato, dovresti osservare un oggetto:
```dart
Stream userChanged = isar.users.watchObject(5);
userChanged.listen((newUser) {
print('User changed: ${newUser?.name}');
});
final user = User(id: 5)..name = 'David';
await isar.users.put(user);
// prints: User changed: David
final user2 = User(id: 5)..name = 'Mark';
await isar.users.put(user);
// prints: User changed: Mark
await isar.users.delete(5);
// prints: User changed: null
```
Come puoi vedere nell'esempio sopra, l'oggetto non deve ancora esistere. L'osservatore riceverà una notifica quando verrà creato.
C'è un parametro aggiuntivo `fireImmediately`. Se lo imposti su `true`, Isar aggiungerà immediatamente il valore corrente dell'oggetto allo stream.
### Osservazione pigra
Forse non è necessario ricevere il nuovo valore ma solo essere avvisati della modifica. Ciò evita a Isar di dover recuperare l'oggetto:
```dart
Stream userChanged = isar.users.watchObjectLazy(5);
userChanged.listen(() {
print('User 5 changed');
});
final user = User(id: 5)..name = 'David';
await isar.users.put(user);
// prints: User 5 changed
```
## Osservare le raccolte
Invece di guardare un singolo oggetto, puoi guardare un'intera raccolta e ricevere una notifica quando un oggetto viene aggiunto, aggiornato o eliminato:
```dart
Stream userChanged = isar.users.watchLazy();
userChanged.listen(() {
print('A User changed');
});
final user = User()..name = 'David';
await isar.users.put(user);
// prints: A User changed
```
## Osservare le query
È anche possibile osservare intere query. Isar fa del suo meglio per avvisarti solo quando i risultati della query cambiano effettivamente. Non riceverai una notifica se i collegamenti causano la modifica della query. Utilizza un osservatore di raccolta se hai bisogno di essere informato sulle modifiche ai link.
```dart
Query usersWithA = isar.users.filter()
.nameStartsWith('A')
.build();
Stream> queryChanged = usersWithA.watch(fireImmediately: true);
queryChanged.listen((users) {
print('Users with A are: $users');
});
// prints: Users with A are: []
await isar.users.put(User()..name = 'Albert');
// prints: Users with A are: [User(name: Albert)]
await isar.users.put(User()..name = 'Monika');
// no print
await isar.users.put(User()..name = 'Antonia');
// prints: Users with A are: [User(name: Albert), User(name: Antonia)]
```
:::warning
Se utilizzi query offset & limit o distinte, Isar ti avviserà anche quando gli oggetti corrispondono al filtro ma al di fuori della query, i risultati cambiano.
:::
Proprio come `watchObject()`, puoi usare `watchLazy()` per ricevere una notifica quando i risultati della query cambiano ma non recupera i risultati.
:::danger
La ripetizione delle query per ogni modifica è molto inefficiente. Sarebbe meglio se invece utilizzassi un osservatore di raccolta pigro.
:::
================================================
FILE: docs/docs/ja/README.md
================================================
---
home: true
title: ホーム
heroImage: /isar.svg
actions:
- text: さっそく始めよう!
link: /ja/tutorials/quickstart.html
type: primary
features:
- title: 💙 Flutterのために
details: 最小限のSetup、簡単に使えて、追加の設定やボイラープレートは不要。数行のコードを追加後にすぐに使用可能。
- title: 🚀 高い拡張性
details: 数十万件のレコードを1つのNoSQLデータベースに格納し、効率的かつ非同期にクエリを実行。
- title: 🍭 豊富な機能
details: 複合 & 複数条件対応インデックスやクエリ修飾子、JSONのサポートなど、データ管理を支援する豊富な機能を搭載。
- title: 🔎 全文検索機能
details: 全文検索機能を保持。複数の条件を設定したIndexを作成し、簡単にレコードを検索する事が可能。
- title: 🧪 ACID セマンティクス
details: IsarはACIDに準拠しており、トランザクションを自動的に処理。エラーが発生しても変更をロールバック。
- title: 💃 静的型付け
details: Isarのクエリは静的型付けされ、コンパイル時にチェックされます。実行時エラーを心配する必要はありません。
- title: 📱 マルチプラットフォーム対応
details: iOS, Android, Desktop, そしてWEBにも対応!
- title: ⏱️ 非同期処理
details: 並列クエリ操作とMulti-Isolateをすぐに利用可能。
- title: 🦄 オープンソース
details: すべてがオープンソースで、永久に無料!
footer: Apache Licensed | Copyright © 2022 Simon Leier
---
================================================
FILE: docs/docs/ja/crud.md
================================================
---
title: CRUD操作
---
# CRUD操作
コレクションを定義したら、それを操作する方法を学びましょう。
## Isarを開く
何をするにしても、まずはIsarのインスタンスが必要です。各インスタンスには、データベースファイルを格納することができる書き込み権限のあるディレクトリが必要になります。ディレクトリを指定しない場合、Isarは現在のプラットフォームに適したデフォルトのディレクトリを見つけます。
Isarインスタンスで使用したいすべてのスキーマを指定します。複数のインスタンスを開いている場合でも、それぞれのインスタンスに同じスキーマを与える必要があります。
```dart
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[ContactSchema],
directory: dir.path,
);
```
加えて、デフォルト設定を使用するか、もしくは以下のいくつかのパラメータを指定することができます:
| Config | Description |
| -------| -------------|
| `name` | 複数のインスタンスを別々の名前で開きます。デフォルトでは、`"default"` が使用されます。 |
| `directory` | このインスタンスの保存場所です。相対パスまたは絶対パスを渡すことができます。デフォルトでは、iOS では `NSDocumentDirectory` が、Android では `getDataDirectory` が使用されます。Webにおいては必要ありません。 |
| `relaxedDurability` | 書き込み性能を向上させるために耐久性保証を緩和します。 アプリケーションのクラッシュではなく、システムクラッシュの場合、最後にコミットしたトランザクションが失われる可能性があります。破損する可能性はありません。 |
| `compactOnLaunch` | インスタンスを開く際にデータベースの圧縮を行うかどうかを確認するための条件です。 |
| `inspector` | デバッグビルドでインスペクターを有効にします。プロファイルとリリースビルドでは、このオプションは無視されます。 |
既にインスタンスが開かれている場合に `Isar.open()` を呼び出すと、指定したパラメータに関係なく既存のインスタンスを取得します。これはIsarをアイソレートで使用する場合に便利です。
:::tip
すべてのプラットフォームで有効なパスを取得するために、[path_provider](https://pub.dev/packages/path_provider)パッケージの使用を検討してください。
:::
データベースファイルの保存場所は `directory/name.isar` です。
## データベースからの読み込み
`IsarCollection` インスタンスを使用して、Isar で指定した型のオブジェクトを検索したり、照会したり、新規に作成したりすることができます。
これ以降のサンプルコードでは、コレクション `Recipe` が以下のように定義されていると仮定した上で述べて行きます。
```dart
@collection
class Recipe {
Id? id;
String? name;
DateTime? lastCooked;
bool? isFavorite;
}
```
### コレクションの取得
すべてのコレクションは、Isarインスタンスに格納されています。あなたはrecipesコレクションを次のように取得できます:
```dart
final recipes = isar.recipes;
```
簡単ですよね? コレクションアクセサを使いたくない場合は、`collection()` メソッドを使うこともできます:
```dart
final recipes = isar.collection();
```
### idを用いたオブジェクトの取得
まだコレクションにデータはありませんが、あるものと仮定して、 `123` という ID の架空のオブジェクトを取得してみましょう。
```dart
final recipe = await recipes.get(123);
```
`get()` はオブジェクトを含む `Future` を返しますが、オブジェクトが存在しない場合は `null` を返します。 Isar のすべての操作はデフォルトでは非同期ですが、ほとんどの操作には同期処理も対応しています:
```dart
final recipe = recipes.getSync(123);
```
:::warning
UIアイソレートでは、非同期バージョンのメソッドをデフォルトで使用する必要があります。ちなみに、Isarは非常に高速なので、多くの場合において同期バージョンを使用しても問題ありません。
:::
複数のオブジェクトを一度に取得したい場合は、 `getAll()` または `getAllSync()` を使用してください:
```dart
final recipe = await recipes.getAll([1, 2]);
```
### オブジェクトのクエリ
IDでオブジェクトを取得する代わりに、 `.where()` と `.filter()` を使って特定の条件に一致するオブジェクトのリストを取得することもできます:
```dart
final allRecipes = await recipes.where().findAll();
final favouires = await recipes.filter()
.isFavoriteEqualTo(true)
.findAll();
```
➡️ 詳しくはこちら: [クエリ](queries)
## データベースの書き換え
いよいよコレクションを書き換えるときがやってきました! オブジェクトを作成、更新、削除するには、それぞれの操作をWriteトランザクション内でラップして使用します:
```dart
await isar.writeTxn(() async {
final recipe = await recipes.get(123)
recipe.isFavorite = false;
await recipes.put(recipe); // 更新操作の実行
await recipes.delete(123); // 削除操作の実行
});
```
➡️ 詳しくはこちら: [トランザクション](transactions)
### オブジェクトの挿入
Isar でオブジェクトを永続化するには、コレクションにオブジェクトを挿入(put)します。
Isar の `put()` メソッドは、そのオブジェクトが既にコレクションに存在するかどうかに応じて、オブジェクトの挿入もしくは更新を行います。
この時、id フィールドが `null` または `Isar.autoIncrement` の場合、Isar はオートインクリメントの id を使用します。
```dart
final pancakes = Recipe()
..name = 'Pancakes'
..lastCooked = DateTime.now()
..isFavorite = true;
await isar.writeTxn(() async {
await recipes.put(pancakes);
})
```
Isarは `id` フィールドがfinalでは無い場合、オブジェクトに自動的にidを割り当てます。
複数のオブジェクトを一度に挿入することも簡単です。
```dart
await isar.writeTxn(() async {
await recipes.putAll([pancakes, pizza]);
})
```
### オブジェクトの更新
作成と更新の両方は `collection.put(object)` で行います。id が `null` (または存在しない) 場合はオブジェクトは挿入され、そうでない時は更新されます。
つまり、pancakesをunfavoriteにしたい場合は、以下のようになります:
```dart
await isar.writeTxn(() async {
pancakes.isFavorite = false;
await recipes.put(recipe);
});
```
### オブジェクトの削除
オブジェクトを削除したい場合は、`collection.delete(id)`を使用してください. delete メソッドは、指定された id を持つオブジェクトを見つけて、それを削除したかどうかを返します。例えば、id が `123` のオブジェクトを削除したい場合、以下のようになります。
```dart
await isar.writeTxn(() async {
final success = await recipes.delete(123);
print('Recipe deleted: $success');
});
```
getやputと同様に、削除されたオブジェクトの数を返す一括削除命令も存在します:
```dart
await isar.writeTxn(() async {
final count = await recipes.deleteAll([1, 2, 3]);
print('We deleted $count recipes');
});
```
削除したいオブジェクトのidが分からない場合は、クエリを使用することができます:
```dart
await isar.writeTxn(() async {
final count = await recipes.filter()
.isFavoriteEqualTo(false)
.deleteAll();
print('We deleted $count recipes');
});
```
================================================
FILE: docs/docs/ja/faq.md
================================================
---
title: よくある質問
---
# よくある質問
IsarとFlutterのデータベースについてよくある質問を無作為に集めました。
### 何でデータベースが必要なの?
> 私はバックエンドDBにデータを保存しているけど, 何でIsarが必要なの?
地下鉄や飛行機に乗っているとき、あるいはWiFiがなく携帯の電波が非常に悪いおばあちゃんの家に行ったときなど、今日においてもデータ通信ができないことはよくあることです。接続が悪いとアプリ自体が使えないなんてことがあってはなりません。
### Isar vs Hive
答えは簡単です: Isar は [Hiveの代替としてスタート](https://github.com/hivedb/hive/issues/246) し、現在はHiveよりもIsarを使うことを推奨する状態になっています。
### WHERE節って!?
> 何でwhere節にどのインデックスを使うのか選択しなきゃいけないの?
これには複数の理由があります。ほとんどのデータベースは、与えられたクエリに対して最適なインデックスを選択するためにヒューリスティックを使用しています。データベースは追加の利用状況データを収集する必要があり、それがオーバーヘッドに繋がります。 加えて、それでも間違ったインデックスを選択する可能性があります。 更にはクエリの作成が遅くなるという点も懸念されます。
開発者であるあなた以上に、あなたのデータを知っている人はいません。ですから、あなた自身が最適なインデックスを選択し、例えば、クエリにインデックスを使うかソートにインデックスを使うかなどを決定することができるのです。
### インデックスやwhere節を必ず使わないといけないの?
いえ、必ずしもそんなことはありません。多くの場合、filterしか使用しなかったとしてもIsarは高速で処理されます。
### Isarって速いの?
Isarは、モバイル向けデータベースの中では最速クラスなので、ほとんどのケースでは十分な速度が出るはずです。もし、パフォーマンスで問題が発生した場合は、何か間違った操作をしている可能性があります。
### Isarはアプリのサイズを増加させますか?
そうですね、少しは。Isarは、アプリのダウンロードサイズを約1〜1.5MB増加させます。Isar Webは、数KBの追加にとどまります。
### ドキュメントの内容が間違ってるよ / タイポ見つけた
すみません、失礼致しました。 [issueを開く](https://github.com/isar-community/isar/issues/new/choose)か、もしくは、プルリクエストをして修正して頂けると助かります💪
================================================
FILE: docs/docs/ja/indexes.md
================================================
---
title: インデックス
---
# インデックス
インデックスは、Isarの最も強力な機能です。多くの組み込み型データベースは、"通常の"インデックスを提供していますが、Isarは複合インデックスやマルチエントリーインデックスも提供しています。
クエリのパフォーマンスを最適化するためには、インデックスがどのように機能するかを理解することが重要です。 Isarでは、どのインデックスを、どのように使用するか事を選ぶ事が出来ます。
それではまず最初に、インデックスとは何かということを簡単に紹介します。
## インデックスとは?
コレクションにインデックスがない場合、行の順番はクエリによって最適化されていない可能性が高く、クエリはオブジェクトを直線的に検索しなければならなくなります。 言い換えれば、クエリはすべてのオブジェクトを検索して、条件にマッチするものを見つけなければならないのです。ご想像のとおり、これには時間がかかります。オブジェクトをひとつひとつ見ていくのは、あまり効率的ではありません。
例えば、この `Product` コレクションは完全に順不同です。
```dart
@collection
class Product {
Id? id;
late String name;
late int price;
}
```
**データ:**
| id | name | price |
| --- | --------- | ----- |
| 1 | Book | 15 |
| 2 | Table | 55 |
| 3 | Chair | 25 |
| 4 | Pencil | 3 |
| 5 | Lightbulb | 12 |
| 6 | Carpet | 60 |
| 7 | Pillow | 30 |
| 8 | Computer | 650 |
| 9 | Soap | 2 |
30ユーロ以上の商品をすべて探そうとするクエリは、9行すべてを検索しなければなりません。9行では問題ないかもしれませんが、10万行になると問題になるでしょう。
```dart
final expensiveProducts = await isar.products.filter()
.priceGreaterThan(30)
.findAll();
```
このクエリの性能を向上させるために、`price` プロパティにインデックスを付けます。インデックスとは、ソートされた検索テーブルのようなものです。:
```dart
@collection
class Product {
Id? id;
late String name;
@Index()
late int price;
}
```
**生成されたインデックス:**
| price | id |
| -------------------- | ------------------ |
| 2 | 9 |
| 3 | 4 |
| 12 | 5 |
| 15 | 1 |
| 25 | 3 |
| 30 | 7 |
| **55** | **2** |
| **60** | **6** |
| **650** | **8** |
これで、クエリの実行がかなり速くなりました。エクゼキュータは、最後の3つのインデックス行に直接ジャンプして、対応するオブジェクトをそのIDで見つけることができます。
### ソート
もうひとつ素晴らしいのは、インデックスを使うと超高速でソートができることです。ソートを指示するクエリは、ソートをする前にデータベースが全ての結果をメモリにロードする必要があるため、コストがかかります。offsetやlimitを指定しても、それはソート後に適用されます。
例えば、最も安い商品を4つ見つけたいとします。次のようなクエリを使うことができます:
```dart
final cheapest = await isar.products.filter()
.sortByPrice()
.limit(4)
.findAll();
```
この例では、データベースはすべての(!)オブジェクトを読み込み、それらを価格順にソートして、最も安い価格の 4 つの製品を返さなければなりません。
想像がつくと思いますが、これは先ほどのインデックスを使えばもっと効率的に行えます。データベースはインデックスの最初の4行を受け取り、対応するオブジェクトを返します。なぜなら、これらはすでに適切な順番になっているからです。
インデックスをソートに使うには、次のようなクエリを書きます。
```dart
final cheapestFast = await isar.products.where()
.anyPrice()
.limit(4)
.findAll();
```
`.anyX()` というwhere節は、Isarにソートのためだけにインデックスを使用するように指示します。また、`priceGreaterThan()`のようなwhere節を使用して、ソートされた結果を得ることもできます。
## ユニークインデックス
ユニークインデックス(一意なIndex)は、インデックスが重複した値を含まないことを保証します。これは、1つまたは複数のプロパティで構成されることがあります。ユニークインデックスが1つのプロパティを持つ場合、このプロパティの値は一意となります。ユニークインデックスが複数のプロパティを持つ場合、これらのプロパティの値の組み合わせは一意になります。
```dart
@collection
class User {
Id? id;
@Index(unique: true)
late String username;
late int age;
}
```
ユニークインデックスに重複を引き起こすデータを挿入または更新しようとすると、エラーになります:
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1); // -> ok
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
// 同じユーザー名でユーザーを挿入しようとする。
await isar.users.put(user2); // -> エラー: 一意制約に反しています。
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
```
## インデックスの置き換え
一意性制約に違反した場合にエラーを投げることが好ましくない場合もあります。エラーを投げる代わりに、既存のオブジェクトを新しいオブジェクトに置き換えたい場合があるかもしれまん。その場合は、インデックスの `replace` プロパティを `true` に設定することで実現できます。
```dart
@collection
class User {
Id? id;
@Index(unique: true, replace: true)
late String username;
}
```
これで、既存のユーザー名でユーザーを挿入しようとすると、Isarは既存のユーザーを新しいユーザーで置き換えます。
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1);
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
print(await isar.user.where().findAll());
// > [{id: 2, username: 'user1' age: 30}]
```
Replaceインデックスは `putBy()` メソッドも生成し、オブジェクトを置き換えるのではなく、更新することができます。既存の ID は再利用され、リンクはそのまま反映されます。
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
// ユーザーが存在しないので、put()と同じです。
await isar.users.putByUsername(user1);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1' age: 30}]
```
見ての通り、最初に挿入されたユーザーのidが再利用されています。
## 大文字小文字を区別しないインデックス
`String` と `List` プロパティに対するすべてのインデックスは、デフォルトで大文字と小文字を区別して表示されます。大文字小文字を区別しないインデックスを作成したい場合は、 `caseSensitive` オプションを使用することができます。
```dart
@collection
class Person {
Id? id;
@Index(caseSensitive: false)
late String name;
@Index(caseSensitive: false)
late List tags;
}
```
## インデックスの種類
インデックスにはさまざまな種類があります。ほとんどの場合、 `IndexType.value` インデックスを使用することになるでしょうが、Hashインデックスを使用するとより効率的です。
### Valueインデックス
Valueインデックスは既定の型であり、StringやListを保持しないすべてのプロパティで許可される唯一のものです。インデックスを構築するために、プロパティの値が使用されます。Listの場合は、Listの要素が使用されます。これは、3つのインデックスタイプの中で最も柔軟性がありますが、ストレージも消費します。
:::tip
基本データ型や、Strings(※where節において `startsWith()` を使いたい場合)、そしてLists(※個別の要素を検索したい場合)においてはIndexType.valueを使用しましょう。
:::
### Hashインデックス
文字列やListをハッシュ化することで、インデックスに必要なストレージを大幅に削減することができます。Hashインデックスの欠点は、接頭辞の走査 (where節における `startsWith` ) に使用できないことです。
:::tip
文字列やListに対して、 `startsWith` や `elementEqualTo` という where 節が必要ない場合は、 `IndexType.hash` を使用しましょう。
:::
### HashElementsインデックス
文字列Listは、全体を (`IndexType.hash` を用いて) ハッシュ化することができますし、Listの要素を個別に (`IndexType.hashElements` を用いて) ハッシュ化して、効率的に要素をハッシュ化したマルチエントリーインデックスを作成することができます。
:::tip
`List` で `elementEqualTo` の where 節が必要な場合は、 `IndexType.hashElements` を使用します。
:::
## 複合インデックス
複合インデックスとは、複数のプロパティに対するインデックスのことです。Isarでは、最大3つのプロパティのコンポジットインデックスを作成することができます。
複合インデックスは、複数列インデックスとも呼ばれます。
まず、例から始めるのが一番でしょう。person コレクションを作成し、age プロパティと name プロパティに複合インデックスを定義します。
```dart
@collection
class Person {
Id? id;
late String name;
@Index(composite: [CompositeIndex('name')])
late int age;
late String hometown;
}
```
**データ:**
| id | name | age | hometown |
| --- | ------ | --- | --------- |
| 1 | Daniel | 20 | Berlin |
| 2 | Anne | 20 | Paris |
| 3 | Carl | 24 | San Diego |
| 4 | Simon | 24 | Munich |
| 5 | David | 20 | New York |
| 6 | Carl | 24 | London |
| 7 | Audrey | 30 | Prague |
| 8 | Anne | 24 | Paris |
**生成されたインデックス:**
| age | name | id |
| --- | ------ | --- |
| 20 | Anne | 2 |
| 20 | Daniel | 1 |
| 20 | David | 5 |
| 24 | Anne | 8 |
| 24 | Carl | 3 |
| 24 | Carl | 6 |
| 24 | Simon | 4 |
| 30 | Audrey | 7 |
生成された複合インデックスは、年齢と名前でソートされたすべての人物を含んでいます。
複合インデックスは、複数のプロパティでソートされた効率的なクエリを作成したい場合に最適です。また、複数のプロパティを持つ高度な where 節も作成できます。
```dart
final result = await isar.where()
.ageNameEqualTo(24, 'Carl')
.hometownProperty()
.findAll() // -> ['San Diego', 'London']
```
複合インデックスの末尾のプロパティは、 `startsWith()` や `lessThan()` といった条件もサポートします。
```dart
final result = await isar.where()
.ageEqualToNameStartsWith(20, 'Da')
.findAll() // -> [Daniel, David]
```
## マルチエントリーインデックス
IndexType.valueを使ってListのインデックスを作成すると、Isarは自動的にマルチエントリーのインデックスを作成し、List内の各項目がオブジェクトに対してインデックスされます。これはすべての型のListに対して機能します。
マルチエントリーインデックスの実用的な用途としては、タグのListのインデックス化や全文インデックスの作成などが挙げられます。
```dart
@collection
class Product {
Id? id;
late String description;
@Index(type: IndexType.value, caseSensitive: false)
List get descriptionWords => Isar.splitWords(description);
}
```
`Isar.splitWords()` は [Unicode Annex #29](https://unicode.org/reports/tr29/) の仕様に従って文字列を単語に分割するので、ほとんどすべての言語に対して正しく動作します。
**データ:**
| id | description | descriptionWords |
| --- | ---------------------------- | ---------------------------- |
| 1 | comfortable blue t-shirt | [comfortable, blue, t-shirt] |
| 2 | comfortable, red pullover!!! | [comfortable, red, pullover] |
| 3 | plain red t-shirt | [plain, red, t-shirt] |
| 4 | red necktie (super red) | [red, necktie, super, red] |
重複する単語を含むエントリは、インデックスに一度だけ表示されます。
**生成されたインデックス:**
| descriptionWords | id |
| ---------------- | --------- |
| comfortable | [1, 2] |
| blue | 1 |
| necktie | 4 |
| plain | 3 |
| pullover | 2 |
| red | [2, 3, 4] |
| super | 4 |
| t-shirt | [1, 3] |
これで、このインデックスは、個々の単語の接頭辞(または等号)をwhere節に使用できるようになりました。
:::tip
単語を直接保存する代わりに、[Soundex](https://en.wikipedia.org/wiki/Soundex) のような [音声アルゴリズム](https://en.wikipedia.org/wiki/Phonetic_algorithm) を使用する事も候補に入れてみてください。
:::
================================================
FILE: docs/docs/ja/limitations.md
================================================
# 制限事項
ご存知のように、Isarはモバイル端末や VM上で動作するデスクトップ、そしてWeb上で動作します。この2つのプラットフォームは非常に異なっており、それぞれ異なる制限事項があります。
## VMの制限事項
- 文字列の最初の1024バイトのみがwhere節の接頭辞として使用可能です。
- オブジェクトのサイズは 16MB までとなります。
## Webの制限事項
Isar WebはIndexedDBに依存しているため、より多くの制限事項がありますが、 Isarを使用している間はほとんど気にはならないでしょう。
- 同期メソッドはサポートされていません。
- 現時点において、 `Isar.splitWords()`と`.matches()`フィルターは未実装です。
- スキーマの変更はVMほど厳密にはチェックされないので、規則に従うように注意してください。
- すべての数値型は double (唯一の js 数値型) として保存されるので、 `@Size32` は影響しません。
- インデックスの表現が異なるため、Hashインデックスの容量は減りません。(機能/動作は同じです)
- `col.delete()` と `col.deleteAll()` は正しく動作しますが、戻り値が正しくありません。
- `col.clear()` はオートインクリメント値をリセットしません。
- 値として `NaN` はサポートされていません。
================================================
FILE: docs/docs/ja/links.md
================================================
---
title: リンク
---
# リンク
リンクは、例えばコメントの作成者(User)のようなオブジェクト間の関係を表現することができ、 `1:1`、`1:n`、`n:n`の関係を IsarLinkで表現することができます。リンクの使用は、埋め込みオブジェクトの使用よりも人間工学的に劣るので、可能な限り埋め込みオブジェクトを使用するようにしましょう。
リンクはリレーションを含む別のテーブルだと考えてください。これはSQLのリレーションと似ていますが、異なった機能セットとAPIを持っています。
## IsarLink
`IsarLink` は関連するオブジェクトを含まないか、1つだけ含むことができ、一対一の関係を表現するために使用することができます。`IsarLink` はリンク先のオブジェクトを保持する `value` というプロパティをひとつだけ持っています。
リンクは遅延(lazy)する為、 `IsarLink` に対して、明示的に `value` を読み込みまたは保存するように指示する必要があります。これは、 `linkProperty.load()` と `linkProperty.save()` を呼び出すことで実現できます。
:::tip
Linkの元(Source)コレクションと対象(Target)コレクションの id プロパティは非final値にすべきです。
:::
Web 以外の対象ついては、リンクは初めて使用する際に自動的に読み込まれます。まずは、IsarLinkをコレクションに追加してみましょう。
```dart
@collection
class Teacher {
Id? id;
late String subject;
}
@collection
class Student {
Id? id;
late String name;
final teacher = IsarLink();
}
```
教師と生徒を介するリンクを定義しました。この例では、全ての生徒が1人だけの教師を持つことができます。
まず、教師を作成し、生徒に割り当てます。教師を `.put()` して、手動でリンクを保存する必要があります。
```dart
final mathTeacher = Teacher()..subject = 'Math';
final linda = Student()
..name = 'Linda'
..teacher.value = mathTeacher;
await isar.writeTxn(() async {
await isar.students.put(linda);
await isar.teachers.put(mathTeacher);
await linda.teacher.save();
});
```
これでリンクが使えるようになりました:
```dart
final linda = await isar.students.where().nameEqualTo('Linda').findFirst();
final teacher = linda.teacher.value; // > Teacher(subject: 'Math')
```
同じことを同期コードで試してみましょう。`.putSync()`が自動的にすべてのリンクを保存するので、手動でリンクを保存する必要はありません。さらには、教師も自動作成してくれます。
```dart
final englishTeacher = Teacher()..subject = 'English';
final david = Student()
..name = 'David'
..teacher.value = englishTeacher;
isar.writeTxnSync(() {
isar.students.putSync(david);
});
```
## IsarLinks
前の例の生徒が複数の教師を持つことができれば、理にかなっていますよね。幸いなことに、Isarには `IsarLinks` があり、複数の関連オブジェクトを含むことができ、対多関係を表現することができます。
`IsarLinks` は `Set` を継承しており、Setに対して許可されている全てのメソッドを実装しています。
`IsarLinks` は `IsarLink` と同じように動作し、遅延(lazy)します。リンクされたオブジェクトを全て読み込むには、 `linkProperty.load()` を呼び出します。変更を持続させるには、 `linkProperty.save()` を呼び出します。
内部的には、`IsarLink` と `IsarLinks` は同じように表現されています。先ほどの `IsarLink` を `IsarLinks` にアップグレードすれば、(データを失うことなく)一人の生徒に複数の教師を割り当てることができます。
```dart
@collection
class Student {
Id? id;
late String name;
final teachers = IsarLinks();
}
```
リンクの名前(`teacher`)を変更していないので、Isarがそれを記憶しています。
その為、データを失わずに機能します。
```dart
final biologyTeacher = Teacher()..subject = 'Biology';
final linda = isar.students.where()
.filter()
.nameEqualTo('Linda')
.findFirst();
print(linda.teachers); // {Teacher('Math')}
linda.teachers.add(biologyTeacher);
await isar.writeTxn(() async {
await linda.teachers.save();
});
print(linda.teachers); // {Teacher('Math'), Teacher('Biology')}
```
## Backlinks
逆方向の関係を表現したい場合はどうすればいいのでしょうか?安心してください。これからバックリンクを紹介します。
バックリンクとは、逆方向のリンクのことです。各リンクは、常に暗黙のバックリンクを持っています。`IsarLink` や `IsarLinks` に `@Backlink()` というアノテーションをつけることで、アプリでバックリンクを利用できるようになります。
バックリンクは追加のメモリやリソースを必要としません。データを失うことなく、自由に追加、削除、名前の変更を行うことができます。
特定の教師がどのような生徒を持っているかを知りたいので、バックリンクを定義します。
```dart
@collection
class Teacher {
Id id;
late String subject;
@Backlink(to: 'teacher')
final student = IsarLinks();
}
```
また、バックリンクが指し示すリンクを明示する必要があります。2つのオブジェクトの間に複数の異なるリンクを設定することが可能です。
## リンクの初期化
`IsarLink` と `IsarLinks` にはゼロ引数のコンストラクタがあり、オブジェクトの生成時にリンクのプロパティを代入するために使用されます。リンクのプロパティを `final` にするのは良い習慣です。
オブジェクトを初めて `put()` したとき、リンクは元(Source)コレクションと対象(Target)コレクションで初期化され、 `load()` や `save()` といったメソッドを呼び出すことができるようになります。リンクは作成後すぐに変更の追跡を開始するので、リンクが初期化される前でもリレーションを追加したり削除したりすることができます。
:::danger
リンクを他のオブジェクトに移動することは不正(illegal)です。
:::
================================================
FILE: docs/docs/ja/queries.md
================================================
---
title: クエリ
---
# クエリ
クエリとは、ある条件に合致するレコードを探し出す方法です。例えば:
- 星付きの連絡先をすべて検索
- 連絡先の名前を個別に検索する
- 姓が定義されていないすべての連絡先を削除する
クエリはDart内ではなくデータベース上で実行されるため、非常に速く実行することができます。インデックスを巧みに使えば、クエリの性能をさらに向上させることができます。
以降では、クエリの記述方法と、クエリを可能な限り高速化する方法について学びます。
レコードを絞り込むには、2種類の方法があります。フィルタとWHERE節です。まず、フィルターがどのように機能するかを見てみましょう。
## フィルタ
フィルタは使いやすく、わかりやすいです。プロパティの種類に応じて、さまざまなフィルタリング処理が用意されており、そのほとんどが一目でわかるような名前になっています。
フィルタは、フィルタリングされるコレクション内のすべてのオブジェクトに対して評価式を適用することで動作します。式の結果が `true` であった場合、Isar はそのオブジェクトを結果に含めます。フィルタは結果の順序に影響を与えません。
これから紹介する例では、次のようなモデルを使用します:
```dart
@collection
class Shoe {
Id? id;
int? size;
late String model;
late bool isUnisex;
}
```
### クエリの条件
フィールドの種類に応じて、利用可能な条件が異なります。
| Condition | Description |
| ----------| ------------|
| `.equalTo(value)` | 指定した `value` と等しい値に一致する。 |
| `.between(lower, upper)` | `lower` と `upper` の間にある値に一致する。 |
| `.greaterThan(bound)` | `bound` よりも大きい値に一致する。 |
| `.lessThan(bound)` | `bound` よりも小さい値に一致する。デフォルトでは `null` の値も含まれる。なぜなら `null` は他のどの値よりも小さいとみなされるからである。 |
| `.isNull()` | `null` に一致する。|
| `.isNotNull()` | `null` ではない値に一致する。|
| `.length()` | List、String、linkの長さのクエリは、Listやlinkの要素数に基づいてオブジェクトをフィルタリングする。 |
ここでは、データベースにsizeが39、40、46のshoeとサイズが設定されていない(`null`)1つのshoeの合計4つが含まれていると仮定します。ソートを行わない限り、値は id でソートされて返されます。
```dart
isar.shoes.filter()
.sizeLessThan(40)
.findAll() // -> [39, null]
isar.shoes.filter()
.sizeLessThan(40, include: true)
.findAll() // -> [39, null, 40]
isar.shoes.filter()
.sizeBetween(39, 46, includeLower: false)
.findAll() // -> [40, 46]
```
### 論理演算子
以下の論理演算子を使って述語を合成することもできます:
| Operator | Description |
| ---------- | ----------- |
| `.and()` | 左側と右側の式の両方が `true` と評価された場合、`true` と評価される。|
| `.or()` | どちらかの式が `true` と評価された場合、`true` と評価される。|
| `.xor()` | ちょうど1つの式が `true` と評価される場合に、 `true` と評価される。 |
| `.not()` | 次の式の結果を否定する。 |
| `.group()` | 条件をグループ化し、評価順序を指定できるようにする。|
sizeが46のshoesをすべて見つけたい場合は、次のようなクエリを使用します。
```dart
final result = await isar.shoes.filter()
.sizeEqualTo(46)
.findAll();
```
複数の条件を使用したい場合は、 **論理積** `.and()` や, **論理和** `.or()` 、 **排他的論理和** `.xor()`を組み合わせることが出来ます。
```dart
final result = await isar.shoes.filter()
.sizeEqualTo(46)
.and() // オプション。 フィルターは暗黙的に論理積で結合される.
.isUnisexEqualTo(true)
.findAll();
```
このクエリは次の式と同等です: `size == 46 && isUnisex == true`.
また、`.group()` を使って条件をグループ化することもできます:
```dart
final result = await isar.shoes.filter()
.sizeBetween(43, 46)
.and()
.group((q) => q
.modelNameContains('Nike')
.or()
.isUnisexEqualTo(false)
)
.findAll()
```
このクエリは次の式と同等です: `size >= 43 && size <= 46 && (modelName.contains('Nike') || isUnisex == false)`.
条件やグループを否定するには、**論理否定** `.not()` を使用します:
```dart
final result = await isar.shoes.filter()
.not().sizeEqualTo(46)
.and()
.not().isUnisexEqualTo(true)
.findAll();
```
このクエリは次の式と同等です: `size != 46 && isUnisex != true`.
### 文字列の条件
上記のクエリ条件に加えて、文字列値にはさらにいくつかの条件を使用することができます。たとえば、正規表現に似たワイルドカードを使用すると、より柔軟な検索が可能になります。
| Condition | Description |
| -------------------- | ----------------------------------------------------------------- |
| `.startsWith(value)` | 指定した `value` で始まる文字列値に一致する。 |
| `.contains(value)` | 指定した `value` を含む文字列値に一致する。 |
| `.endsWith(value)` | 指定した `value` で終わる文字列値に一致する。 |
| `.matches(wildcard)` | 指定した `wildcard` パターンに適合する文字列値に一致する。 |
**大文字小文字を区別する**
すべての文字列操作には、オプションで `caseSensitive` パラメータがあり、デフォルトは `true` です。
**ワイルドカード:**
[ワイルドカード文字列表現](https://en.wikipedia.org/wiki/Wildcard_character) は、通常の文字に2つの特殊なワイルドカード文字を使用した文字列です。:
- ワイルドカードの `*` は、0個以上の任意の文字に一致します。
- ワイルドカードの `?` は、任意の文字に一致します。
たとえば, ワイルドカード文字列 `"d?g"` は `"dog"`, `"dig"`, および `"dug"` にマッチするが、 `"ding"`, `"dg"`, および `"a dog"` にマッチしません。
### クエリ修飾子
時には、ある条件や異なる値に基づいてクエリを作成することが必要な場合があります。Isarは、条件付きクエリを作成するための非常に強力な機能を持っています。:
| Modifier | Description |
| --------------------- | ---------------------------------------------------- |
| `.optional(cond, qb)` | 条件が `true` の場合のみ、クエリを拡張する。 これは、クエリ内のほぼすべての場所で使用することが出来ます。条件付きでソートしたり絞り込む為に用いるなどが使用例です。 |
| `.anyOf(list, qb)` | `values` の各値に対してクエリを拡張し、 **論理和** を用いて条件を組み合わせる。 |
| `.allOf(list, qb)` | `values` の各値に対してクエリを拡張し、 **論理積** を用いて条件を組み合わせる。 |
このサンプルでは、optionalを使用してShoesを見つけることができるメソッドを構築しています:
```dart
Future> findShoes(Id? sizeFilter) {
return isar.shoes.filter()
.optional(
sizeFilter != null, // sizeFilter != null の場合のみ、フィルタを適用する。
(q) => q.sizeEqualTo(sizeFilter!),
).findAll();
}
```
複数の靴のサイズのいずれかを持つ靴をすべて見つけたい場合は、従来のクエリを書くか、 `anyOf()` 修飾子を使うことができます:
```dart
final shoes1 = await isar.shoes.filter()
.sizeEqualTo(38)
.or()
.sizeEqualTo(40)
.or()
.sizeEqualTo(42)
.findAll();
final shoes2 = await isar.shoes.filter()
.anyOf(
[38, 40, 42],
(q, int size) => q.sizeEqualTo(size)
).findAll();
// shoes1 == shoes2
```
クエリ修飾子は、動的なクエリを構築したい場合に特に有効です。
### リスト
Listにおいてもクエリが可能です:
```dart
class Tweet {
Id? id;
String? text;
List hashtags = [];
}
```
Listの長さ(length)に基づいてクエリを実行できます:
```dart
final tweetsWithoutHashtags = await isar.tweets.filter()
.hashtagsIsEmpty()
.findAll();
final tweetsWithManyHashtags = await isar.tweets.filter()
.hashtagsLengthGreaterThan(5)
.findAll();
```
これらは、Dartのコード `tweets.where((t) => t.hashtags.isEmpty);` や `tweets.where((t) => t.hashtags.length > 5);` に相当します。また、リストの要素をもとに問い合わせることもできます:
```dart
final flutterTweets = await isar.tweets.filter()
.hashtagsElementEqualTo('flutter')
.findAll();
```
これはDartのコード `tweets.where((t) => t.hashtags.contains('flutter'));` に相当します。
### 埋め込みオブジェクト
組み込みオブジェクトは、Isarの最も便利な機能の一つです。トップレベルオブジェクトと同じ条件で非常に効率的に問い合わせることができます。例えば、次のようなモデルがあるとします:
```dart
@collection
class Car {
Id? id;
Brand? brand;
}
@embedded
class Brand {
String? name;
String? country;
}
```
ブランド名が `"BMW"` で、国名が `"Germany"` である車をすべて問い合わせたいとします。これは以下のクエリで実現できます:
```dart
final germanCars = await isar.cars.filter()
.brand((q) => q
.nameEqualTo('BMW')
.and()
.countryEqualTo('Germany')
).findAll();
```
ネストされたクエリは常にグループ化するようにしましょう。上記のクエリは以下のクエリと結果は同じですが、上記のクエリの方がより効率的に動作します:
```dart
final germanCars = await isar.cars.filter()
.brand((q) => q.nameEqualTo('BMW'))
.and()
.brand((q) => q.countryEqualTo('Germany'))
.findAll();
```
### リンク
モデルに[リンクもしくはバックリンク](links)が含まれている場合、リンクされたオブジェクトまたはリンクされたオブジェクトの数に基づいてクエリをフィルタリングすることができます。
:::warning
リンククエリは、Isarがリンクされたオブジェクトを検索する必要があるため、コストがかかることに留意してください。また、代わりに埋め込みオブジェクトを使用することを検討してみてください。
:::
```dart
@collection
class Teacher {
Id? id;
late String subject;
}
@collection
class Student {
Id? id;
late String name;
final teachers = IsarLinks();
}
```
数学または英語の先生を持つ全ての生徒を見つけたいとします:
```dart
final result = await isar.students.filter()
.teachers((q) {
return q.subjectEqualTo('Math')
.or()
.subjectEqualTo('English');
}).findAll();
```
リンクフィルターは、少なくとも1つのリンクオブジェクトが条件にマッチすれば、`true`と評価されます。
教師を持たない全ての生徒を検索してみましょう。:
```dart
final result = await isar.students.filter().teachersLengthEqualTo(0).findAll();
```
もしくは:
```dart
final result = await isar.students.filter().teachersIsEmpty().findAll();
```
## Where節
Where節は非常に強力な機能ですが、正しく使用するのは少し難しいかもしれません。
フィルターとは対照的に、where節はスキーマで定義したインデックスを使用してクエリ条件を確認しています。各レコードを個別にフィルタリングするより、インデックスを用いる方がはるかに高速です。
➡️ 詳しくはこちら: [インデックス](indexes)
:::tip
基本的なルールとして、Where節を使用してレコードをできる限り減らし、残りのフィルタリングはフィルタを使用して行うようにすることをお勧めします。
:::
where節を組み合わせるには、**論理和**しか使えません。言い換えると、複数のwhere節を合計することはできますが、複数のwhere節の交差部分を照会することはできません。
それではShoeコレクションにインデックスを追加してみましょう:
```dart
@collection
class Shoe with IsarObject {
Id? id;
@Index()
Id? size;
late String model;
@Index(composite: [CompositeIndex('size')])
late bool isUnisex;
}
```
ここではインデックスが2つあります。`size` のインデックスは、 `.sizeEqualTo()` のような where 節を使用可能にしています。`isUnisex` の複合インデックス(CompositeIndex)は、 `isUnisexSizeEqualTo()` のような where 節を使用できるようにしています。そしてまた、インデックスの接頭辞は常に任意のものを使用できる為、 `isUnisexEqualTo()` のような事も可能です。
これでサイズ46のユニセックスの靴を検索する以前見たクエリを、複合インデックスを使用して書き換えることができます。このクエリは前記で述べたクエリよりも高速に動作します:
```dart
final result = isar.shoes.where()
.isUnisexSizeEqualTo(true, 46)
.findAll();
```
Where節には、さらに2つの強力な機能があります。
Where節は"Free"なソートと、超高速なDISTINCT命令を保持しています。
### where節とフィルタの組み合わせ
`shoes.filter()` というクエリを覚えていますか?
実はこれは `shoes.where().filter()` の短縮形なのです。where節とfilterを同じクエリで組み合わせて、両方の利点を利用することができます(そして、そうすべきです):
```dart
final result = isar.shoes.where()
.isUnisexEqualTo(true)
.filter()
.modelContains('Nike')
.findAll();
```
まず、where 節が適用され、フィルタリングされるオブジェクトの数が減ります。その後、残りのオブジェクトにフィルタが適用されます。
## ソート
クエリ実行結果のソート方法は、`.sortBy()`, `.sortByDesc()`, `.thenBy()`, `.thenByDesc()` メソッドを用いて定めることが可能です。
インデックスを使わずに、すべての靴をModel名の昇順とSizeの降順でソートして検索する方法です:
```dart
final sortedShoes = isar.shoes.filter()
.sortByModel()
.thenBySizeDesc()
.findAll();
```
特に、ソートは offset と limit の前に行われるため、たくさんの結果をソートするのはコストがかかります。上記のソートメソッドでは、インデックスを使用することはありません。幸いなことに、Where節によるソートを使えば、100万個のオブジェクトをソートする場合でもクエリを高速に実行することができます。
### Where節のソート
クエリで **単一(single)** の where 節を使用した場合、結果はすでにインデックスでソートされています。これは非常に重要です。
例えば、サイズ `[43, 39, 48, 40, 42, 45]` の靴があり、サイズが `42` より大きい靴をすべて検索し、サイズ順に並べたいとしましょう。
```dart
final bigShoes = isar.shoes.where()
.sizeGreaterThan(42) // 加えて、結果がSizeでソートされる
.findAll(); // -> [43, 45, 48]
```
見ての通り、結果は `size` インデックスでソートされています。where 節のソート順を逆にしたい場合は、 `sort` に `Sort.desc` をセットします:
```dart
final bigShoesDesc = await isar.shoes.where(sort: Sort.desc)
.sizeGreaterThan(42)
.findAll(); // -> [48, 45, 43]
```
時には where 節を使いたくないけれども、暗黙のうちにソートが行われるという恩恵を受けたいこともあるでしょう。そのような場合には、 `any` という where 節を使用します:
```dart
final shoes = await isar.shoes.where()
.anySize()
.findAll(); // -> [39, 40, 42, 43, 45, 48]
```
もし、あなたが複合インデックスを使用した場合、結果はそのインデックス内のすべてのフィールドでソートされます。
:::tip
結果をソートする必要がある場合は、インデックスを使用することを検討してください。`offset()` や `limit()` を使っている場合は特にそうです。
:::
時には、ソートのためにインデックスを使用することが出来なかったり、有用ではない場合もあるかもしれません。そのような場合は、インデックスを使用して結果の項目数をできるだけ減らすのが良いでしょう。
## ユニーク値
一意な値を持つ項目のみを返すには、distinct述語を使用します。たとえば、Isar データベースに何種類の異なる靴のModelがあるかを調べるには、 以下のようにします:
```dart
final shoes = await isar.shoes.filter()
.distinctByModel()
.findAll();
```
また、複数のdistinctの条件を繋げて、異なるModelとSizeの組み合わせである全ての靴を検索することができます。
```dart
final shoes = await isar.shoes.filter()
.distinctByModel()
.distinctBySize()
.findAll();
```
異なる組み合わせの最初の結果のみが返されます。これをコントロールするために、where句とソート操作を使用することも可能です。
### WHERE節のdistinct
一意でないインデックスがある場合、それの全ての異なる値を取得したい時があると思います。前のセクションで紹介した `distinctBy` オペレーションを使うこともできますが、ソートやフィルタの後に実行されるため、若干のオーバーヘッドが発生します。
WHERE節を1つだけ使用するのであれば、代わりにインデックスに依拠してdistinct処理を実行することができます。
```dart
final shoes = await isar.shoes.where(distinct: true)
.anySize()
.findAll();
```
:::tip
理論・仕組み的には、ソートとdistinctのために複数のwhere節を使うこともできます。複数のwhere節を使う唯一の制限は、これらのwhere節が重複しておらず、同じインデックスを使用していることです。正しいソートを行うには、ソート順で適用する必要があります。十分に注意をしてください。
:::
## OffsetとLimit
遅延(lazy)リストビューのために、クエリ結果の数を制限することは良い方法だと思います。これを行うには、 `limit()` を設定します。
```dart
final firstTenShoes = await isar.shoes.where()
.limit(10)
.findAll();
```
`offset()` を設定することで、クエリの結果をページネイト(取得開始位置の指定)することもできます。
```dart
final firstTenShoes = await isar.shoes.where()
.offset(20)
.limit(10)
.findAll();
```
Dartオブジェクトのインスタンス化は、クエリ実行の中で最もコストのかかる部分であることが多いので、必要なオブジェクトだけを読み込むのが良いでしょう。
## 実行順序
Isarは常に同じ順序でクエリーを実行します:
1. プライマリまたはセカンダリインデックスを走査してオブジェクトを見つける(where節の適用)
2. オブジェクトのフィルタリング
3. 結果のソート
4. distinct操作の適用
5. 結果のoffset と limit
6. 結果の返却
## クエリの操作
これまでの例では、`.findAll()` を使ってマッチするオブジェクトをすべて取得しました。しかし、利用できる操作は他にも沢山あります。
| Operation | Description |
| ---------------- | ------------------------------------------------------------------------------------------------------------------- |
| `.findFirst()` | 最初にマッチしたオブジェクトのみを取得し、マッチしない場合は `null` を取得する。 |
| `.findAll()` | マッチしたオブジェクトを全て取得する。 |
| `.count()` | クエリにマッチするオブジェクトの数を数える。 |
| `.deleteFirst()` | コレクションから、最初にマッチしたオブジェクトを削除する。 |
| `.deleteAll()` | コレクションから、一致するすべてのオブジェクトを削除する。 |
| `.build()` | クエリをコンパイルして、後で再利用することが出来る。これにより、クエリを複数回実行したい場合に、そのクエリを構築するためのコストを節約する事が出来る。 |
## プロパティクエリ
単一プロパティの値にしか関心が無く必要の無い場合、プロパティクエリを使用することができます。通常のクエリを構築し、プロパティを選択するだけです:
```dart
List models = await isar.shoes.where()
.modelProperty()
.findAll();
List sizes = await isar.shoes.where()
.sizeProperty()
.findAll();
```
単一プロパティのみを使用することで、逆シリアル化の時間を節約できます。プロパティクエリは、埋め込みオブジェクトやリストに対しても機能します。
## アグリゲーション(集約)
Isarはプロパティクエリの値を集約する機能を持っています。以下の集約操作が可能です:
| Operation | Description |
| ------------ | -------------------------------------------------------------- |
| `.min()` | 最小値を探す。該当するものがなければ `null` となる。 |
| `.max()` | 最大値を探す。該当するものがなければ `null` となる。 |
| `.sum()` | 全ての値を合計する。 |
| `.average()` | すべての値の平均を計算し、一致するものがない場合は `NaN` を計算する。 |
一致するオブジェクトをすべて見つけて手動で集約するよりも、アグリゲーションを使用する方が、はるかに高速になります。
## 動的なクエリ
:::danger
このセクションは、おそらくほとんどの方には関係ないでしょう。ダイナミッククエリの使用は、どうしても必要な場合(ほぼ無いです)を除き、お勧めしません。
:::
今まで述べて来たすべての例は、QueryBuilderと生成された静的な拡張メソッドを使用しています。もしかしたら、動的なクエリや(Isar Inspectorのような)カスタムクエリ言語を作りたいかもしれません。その場合は、`buildQuery()` メソッドを使うことができます:
| Parameter | Description |
| --------------- | ------------------------------------------------------------------------------------------- |
| `whereClauses` | クエリのwhere節 |
| `whereDistinct` | where 節が個別の値を返すかどうか(単一の where 節の場合のみ有効) |
| `whereSort` | where節のトラバース(巡回)順序(単一のwhere節にのみ有効) |
| `filter` | 結果に適用するフィル |
| `sortBy` | ソートするプロパティの一覧 |
| `distinctBy` | 区別するプロパティの一覧 |
| `offset` | 結果のoffset |
| `limit` | 返送する結果の最大数 |
| `property` | nullで無い場合、このプロパティの値のみが返される。 |
それでは動的なクエリを作成してみましょう:
```dart
final shoes = await isar.shoes.buildQuery(
whereClauses: [
WhereClause(
indexName: 'size',
lower: [42],
includeLower: true,
upper: [46],
includeUpper: true,
)
],
filter: FilterGroup.and([
FilterCondition(
type: ConditionType.contains,
property: 'model',
value: 'nike',
caseSensitive: false,
),
FilterGroup.not(
FilterCondition(
type: ConditionType.contains,
property: 'model',
value: 'adidas',
caseSensitive: false,
),
),
]),
sortBy: [
SortProperty(
property: 'model',
sort: Sort.desc,
)
],
offset: 10,
limit: 10,
).findAll();
```
これらは以下のクエリに相当します。:
```dart
final shoes = await isar.shoes.where()
.sizeBetween(42, 46)
.filter()
.modelContains('nike', caseSensitive: false)
.not()
.modelContains('adidas', caseSensitive: false)
.sortByModelDesc()
.offset(10).limit(10)
.findAll();
```
================================================
FILE: docs/docs/ja/recipes/data_migration.md
================================================
---
title: データの移行
---
# データの移行
コレクション、フィールド、インデックスを追加または削除すると、Isarは自動的にデータベーススキーマを移行(マイグレート)します。時には、データも一緒に移行したい場合もあるでしょう。 Isarは組み込み解決法を提供していません。これはデタラメな移行制限が課される可能性があるためです。ただ、ニーズに合った移行ロジックを簡単に実装することができます。
この例では、データベース全体で1つのバージョンを使用したいと思います。共有環境設定を使って現在のバージョンを保存し、移行したいバージョンと比較します。バージョンが一致しない場合、データを移行し、バージョンを更新します。
:::tip
各コレクションに独自のバージョンを与え、個別に移行することも可能です。
:::
誕生日フィールドを持つユーザーコレクションがあると仮定します。このアプリのバージョン2では、年齢に基づいてユーザーを照会するために、誕生年のフィールドを追加する必要があります。
Version 1:
```dart
@collection
class User {
Id? id;
late String name;
late DateTime birthday;
}
```
Version 2:
```dart
@collection
class User {
Id? id;
late String name;
late DateTime birthday;
short get birthYear => birthday.year;
}
```
問題は、バージョン1では `birthYear` フィールドが存在しないため、既存のUserモデルを作成しても空の `birthYear`が設定されることです。 `birthYear` フィールドを設定するために、データを移行する必要があります。
```dart
import 'package:isar/isar.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() async {
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[UserSchema],
directory: dir.path,
);
await performMigrationIfNeeded(isar);
runApp(MyApp(isar: isar));
}
Future performMigrationIfNeeded(Isar isar) async {
final prefs = await SharedPreferences.getInstance();
final currentVersion = prefs.getInt('version') ?? 2;
switch(currentVersion) {
case 1:
await migrateV1ToV2(isar);
break;
case 2:
// バージョンが設定されていない場合(新規インストール)、または既にver.2の場合は移行する必要はない
return;
default:
throw Exception('Unknown version: $currentVersion');
}
// バージョンを更新する
await prefs.setInt('version', 2);
}
Future migrateV1ToV2(Isar isar) async {
final userCount = await isar.users.count();
// すべてのユーザーを一度にメモリにロードするのを避けるため、ユーザーをページ分割する
for (var i = 0; i < userCount; i += 50) {
final users = await isar.users.where().offset(i).limit(50).findAll();
await isar.writeTxn((isar) async {
// birthYear ゲッターを使用しているため、何も更新する必要はありません
await isar.users.putAll(users);
});
}
}
```
:::warning
多くのデータを移行する必要がある場合、UIスレッドに負担がかからないようにバックグラウンドアイソレートを使用することを検討してください。
:::
================================================
FILE: docs/docs/ja/recipes/full_text_search.md
================================================
---
title: 全文検索
---
# 全文検索
全文検索は、データベース内のテキストを検索する強力な方法です。[インデックス](../indexes.md)がどのように機能するかについては既にご存じだとは思いますが、基本的なことを説明します。
インデックスはルックアップテーブルのように機能し、特定の値を持つレコードをクエリエンジンがすばやく検索できるようにします。たとえば、オブジェクトに `title` フィールドがある場合、そのフィールドにインデックスを作成することで、指定したタイトルを持つオブジェクトをより速く見つけることができます。
## なぜ全文検索が便利なのか
IsarDB ではフィルタを使って簡単にテキストを検索することができます。例えば、 `.startsWith()`, `.contains()`, `.matches()` のような様々な文字列操作があります。フィルタの問題は、その実行時間が `O(n)` (ここで `n` はコレクション内のレコードの数) であることです。特に、 `.matches()` のような文字列演算は時間がかかります。
:::tip
全文検索はフィルタよりはるかに高速ですが、インデックスにはいくつかの制限があります。このレシピでは、これらの制限を回避する方法を探ります。
:::
## 基本例
考え方としては常に同じです:テキスト全体をインデックス化するのではなく、テキスト中の単語をインデックス化し、個別に検索できるようにします。
それではさっそく、基本的な全文インデックスを作成してみましょう:
```dart
class Message {
Id? id;
late String content;
@Index()
List get contentWords => content.split(' ');
}
```
これで、content 内の特定の単語を検索できるようになりました:
```dart
final posts = await isar.messages
.where()
.contentWordsAnyEqualTo('hello')
.findAll();
```
このクエリは高速に動作しますが、いくつかの問題があります:
1. 単語全体しか検索できない
2. 句読点は考慮しない
3. 他の空白文字の検索に対応していない
## テキストを正しく分割する
先ほどの例を改善してみましょう。単語分割を修正するために複雑な正規表現を開発しようとすることもできますが、おそらく時間がかかり、エッジケースで間違ってしまう可能性もあります。
[Unicode Annex #29](https://unicode.org/reports/tr29/)では、ほぼ全ての言語について、テキストを単語に正しく分割する方法を定義しています。これは非常に複雑ですが、幸いなことに、Isar は重い仕事を代わりにやってくれます。
```dart
Isar.splitWords('hello world'); // -> ['hello', 'world']
Isar.splitWords('The quick (“brown”) fox can’t jump 32.3 feet, right?');
// -> ['The', 'quick', 'brown', 'fox', 'can’t', 'jump', '32.3', 'feet', 'right']
```
## 他の機能の追加
他の機能も簡単に実装できますよ!プレフィックスマッチングや大文字小文字を区別しないマッチングをサポートするようにインデックスを変更することもできます。
```dart
class Post {
Id? id;
late String title;
@Index(type: IndexType.value, caseSensitive: false)
List get titleWords => title.split(' ');
}
```
デフォルトでは、Isar は単語をハッシュ値として保存します。これは高速で容量効率のよい方法です。 しかし、ハッシュ値はプレフィックスマッチングに使用することはできません。インデックスを変更して、`IndexType.value` を使用すると、単語を直接利用することができます。これによって `.titleWordsAnyStartsWith()` という where 節を提供します。
```dart
final posts = await isar.posts
.where()
.titleWordsAnyStartsWith('hel')
.or()
.titleWordsAnyStartsWith('welco')
.or()
.titleWordsAnyStartsWith('howd')
.findAll();
```
## `.endsWith()` の実装
`.endsWith()` の実装も、勿論可能です!ここでは、`.endsWith()`のマッチングを実現するためのちょっとしたテクニックをお見せします。
```dart
class Post {
Id? id;
late String title;
@Index(type: IndexType.value, caseSensitive: false)
List get revTitleWords {
return Isar.splitWords(title).map(
(word) => word.reversed).toList()
);
}
}
```
検索したい語尾を反転(reversed)させることを忘れないようにしてください。
```dart
final posts = await isar.posts
.where()
.revTitleWordsAnyStartsWith('lcome'.reversed)
.findAll();
```
## ステミングアルゴリズム
残念ながら、インデックスは `.contains()` マッチングをサポートしていません (これは他のデータベースでも同様です)。しかし、いくつかの代替手段があり、検討する価値はあります。その選択肢は、用途に大きく依存します。その一例として、単語全体ではなく、単語の語幹をインデックス化する方法があります。
ステミングアルゴリズムは、言語の正規化プロセスで、単語のさまざまな形式を共通の形式に変換します:
```
connection
connections
connective ---> connect
connected
connecting
```
一般的なアルゴリズムは、[Porter stemming algorithm](https://tartarus.org/martin/PorterStemmer/) と [Snowball stemming algorithms](https://snowballstem.org/algorithms/) です。
また、[lemmatization](https://en.wikipedia.org/wiki/Lemmatisation) のような、より高度な形式もあります。
## 音声学的アルゴリズム
[音声アルゴリズム](https://en.wikipedia.org/wiki/Phonetic_algorithm)とは、発音によって単語を割り出すためのアルゴリズムです。つまり、探している単語と似た音の単語を見つけることができるのです。
:::warning
音声アルゴリズムの多くは、単一言語しかサポートしていません。
:::
### Soundex
[Soundex](https://en.wikipedia.org/wiki/Soundex)は、英語の発音で人名を索引付けするための音声アルゴリズムです。同音異義語が同じ表現にエンコードされ、スペルが多少違ってもマッチングできるようにすることが目的で作られています。これは簡単なアルゴリズムであり、複数の改良版が存在する。
このアルゴリズムを使うと、`"Robert"` と `"Rupert"` はともに `"R163"` という文字列を返し、 `"Rubin"` は `"R150"` を返します。`Ashcraft"` と `"Ashcroft"` は共に `"A261"` を返します。
### Double Metaphone
[Double Metaphone](https://en.wikipedia.org/wiki/Metaphone) 音素符号化アルゴリズムは、このアルゴリズムの第二世代です。このアルゴリズムは、オリジナルの Metaphone アルゴリズムと比較して、いくつかの基本的な設計上の改良がなされています。
Double Metaphone は、スラブ語、ゲルマン語、ケルト語、ギリシャ語、フランス語、イタリア語、スペイン語、中国語、およびその他の起源の英語におけるさまざまな不規則性を考慮しています。
================================================
FILE: docs/docs/ja/recipes/multi_isolate.md
================================================
---
title: Multi-Isolateの使用法
---
# Multi-Isolateの使用法
スレッドの代わりに、すべてのDartのコードはアイソレートの内部で実行されます。それぞれのアイソレートは独自のメモリヒープを持ち、アイソレート内のどのステートも他のアイソレートからアクセスできないことを保証しています。
Isarは同時に複数のアイソレートからアクセスすることができ、ウォッチャーもアイソレートをまたいで動作します。このレシピでは、複数のアイソレート環境でIsarを使用する方法を確認します。
## いつMulti-Isolateを使用すべきか
Isarのトランザクションは、同じアイソレートで実行されても並列に実行されます。そうだとしても、場合によっては、複数のアイソレートからIsarにアクセスすることが 有益なこともあります。
その理由は、IsarはDartオブジェクトとの間でデータのエンコードとデコードにかなりの時間を費やしているからです。これはJSONのエンコードとデコードのようなものだと考えることができます。(ただ、より効率的です)これらの操作は、データがアクセスされるアイソレートの内部で実行され、当然アイソレート内の他のコードをブロックします。言い換えれば IsarはあなたのDartアイソレートで作業の一部を実行します。
一度に数百のオブジェクトを読み書きする必要があるだけなら、UIアイソレートで行うことは問題ではありません。しかし、巨大なトランザクションや、UIスレッドがすでにBusy状態である場合は、別のアイソレートを使用することを検討する必要があります。
## 具体例
まず最初に行うべきことは、新しいアイソレートでIsarをオープンすることです。Isarのインスタンスは既にメインとなるアイソレートで開かれているので、 `Isar.open()` は同じインスタンスを返します。
:::warning
メインアイソレートと同じスキーマを提供することを忘れないでください。そうでない場合は、エラーになります。
:::
`compute()` は Flutter で新しいアイソレートを開始し、その中で与えられた関数を実行します。
```dart
void main() {
// UIアイソレートでIsarを開く
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[MessageSchema],
directory: dir.path,
name: 'myInstance',
);
// データベースの変更を監視する
isar.messages.watchLazy(() {
print('omg the messages changed!');
});
// 新しいアイソレートを開始し、10000メッセージを作成します。
compute(createDummyMessages, 10000).then(() {
print('isolate finished');
});
// しばらくすると:
// > omg the messages changed!
// > isolate finished
}
// 新しいアイソレート内で実行される関数
Future createDummyMessages(int count) async {
// インスタンスはすでに開かれているので、ここではPathは必要ありません。
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[PostSchema],
directory: dir.path,
name: 'myInstance',
);
final messages = List.generate(count, (i) => Message()..content = 'Message $i');
// アイソレート内で同期トランザクションを使用する。
isar.writeTxnSync(() {
isar.messages.insertAllSync(messages);
});
}
```
上記の例の中で、いくつか興味深い点があります:
- `isar.messages.watchLazy()` は UI アイソレートで呼び出されていますが、他のアイソレートからの変更についても通知されている。
- インスタンスは名前(name)で参照されます。デフォルトの名前は `default` ですが、この例では `myInstance` に設定しました。
- メッセージを作成するために同期トランザクションを使用しました。新しいアイソレートをブロックすることは問題ありませんし、同期トランザクションは少し速くなります。
================================================
FILE: docs/docs/ja/recipes/string_ids.md
================================================
---
title: 文字列のID
---
# 文字列のID
このチュートリアルは、私が最も頻繁に受け取るリクエストの一つです。
IsarはString IDを標準サポートしていませんが、それには理由があります。特にリンクの場合、String IDのオーバーヘッドが大きすぎるのです。
時には、UUIDやその他の整数型ではないidを利用する外部データを保存しなければならない場合があることは理解しています。 そこで、String idをオブジェクトのプロパティとして保存し、fastHashを実装して、Idとして使用できる64ビットintを生成することをお勧めします。
```dart
@collection
class User {
String? id;
Id get isarId => fastHash(id!);
String? name;
int? age;
}
```
この方法を使用すれば、リンク用の効率的な整数IDと、文字列IDを使用する機能という、両方の長所を得ることができます。
## Fast hash 関数
ハッシュ関数は高品質で高速なものが理想的です(かつコリジョンは避けたい)。
そこで、以下のような実装をお勧めします。
```dart
/// Dart Stringsの為に最適化されたFNV-1a 64bitハッシュアルゴリズム
int fastHash(String string) {
var hash = 0xcbf29ce484222325;
var i = 0;
while (i < string.length) {
final codeUnit = string.codeUnitAt(i++);
hash ^= codeUnit >> 8;
hash *= 0x100000001b3;
hash ^= codeUnit & 0xFF;
hash *= 0x100000001b3;
}
return hash;
}
```
もし別のハッシュ関数を選択したい場合は、64ビットの整数値を返すことを確認する事に加えて、暗号化ハッシュ関数を使用することは避けてください(処理速度が大幅に低下します)。
:::warning
`String.hashCode` は、異なるプラットフォームやバージョンの Dart で安定して動作することが保証されていないため、使用しないようにしましょう。
:::
================================================
FILE: docs/docs/ja/schema.md
================================================
---
title: スキーマとは
---
# スキーマとは
Isar を使用してアプリのデータを保存する場合、コレクションを扱うことになります。コレクションとは、関連付けられた IsarDB 内のテーブルのようなもので、単一型の Dart オブジェクトのみを格納することができます。それぞれのコレクションオブジェクトは、対応するコレクションの行を表します。
コレクション定義は "スキーマ"と呼ばれます。Isar Generator は貴方のために手間のかかる面倒な作業を行い、コレクションを使用するのに必要なコードの大部分を生成してくれます。
## コレクションの構造
Isar コレクションを定義するには、Class を `@collection` または `@Collection()` でアノテートします。 Isar コレクションは対応するテーブル内の各列となるフィールドを含みます。ここには、主キーを構成するフィールドも含めてください。
次のコードは、ID、名前、苗字の列を持つ `User` テーブルを定義するシンプルなコレクションの例です。
```dart
@collection
class User {
Id? id;
String? firstName;
String? lastName;
}
```
:::tip
フィールドを永続化するためには、Isar がそのフィールドにアクセスできる必要があります。フィールドを public にしたり、Getter や Setter のメソッドを用意したりすることで、Isar がフィールドにアクセスできるようになります。
:::
コレクションをカスタマイズするために、いくつかの任意のパラメータがあります:
| Config | Description |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `inheritance` | 親クラスや mixins のフィールドを Isar に保存するかどうかを管理します。デフォルトでは有効です。 |
| `accessor` | デフォルトのコレクションアクセサの名前を変更できるようにします。 (たとえば、`Contact` コレクションには `isar.contacts` を指定など). |
| `ignore` | 特定のプロパティを無視(除外)することができます。これらは、スーパークラスに対しても同様に適用されます。 |
### Isar の Id
各コレクションクラスは、オブジェクトを一意に識別する `Id` 型の id プロパティを定義する必要があります。`Id` は `int` の別名(エイリアス)で、IsarGenerator が id プロパティを識別できるようにするためのものです。
Isar は自動的に id フィールドにインデックスを作成するので、id に基づいて効率的にオブジェクトを取得したり変更したりすることができます。
id は自分で設定することもできますし、Isar にオートインクリメントの id を割り当ててもらうこともできます。もし`id` フィールドが `null` かつ `final` でない場合、Isar はオートインクリメントの id を割り当てます。NULL でないオートインクリメントの id が欲しい場合は、 `null` の代わりに `Isar.autoIncrement` を使用することができます。
:::tip
オブジェクトが削除された場合、オートインクリメント ID は再利用されません。オートインクリメント ID をリセットする唯一の方法は、データベースを削除(Clear)することです。
:::
### コレクションとフィールドの名前変更
デフォルトでは、Isar はクラス名をコレクション名として使用します。同様に、Isar はフィールド名をデータベースの列名として使用します。コレクションやフィールドに別の名前を付けたい場合は、 `@Name` アノテーションを追加します。次の例は、コレクションとフィールドの名前をカスタマイズする例です:
```dart
@collection
@Name("User")
class MyUserClass1 {
@Name("id")
Id myObjectId;
@Name("firstName")
String theFirstName;
@Name("lastName")
String familyNameOrWhatever;
}
```
特に、既にデータベースに保存されている Dart のフィールドやクラスの名前を変更したい場合は、 `@Name` アノテーションの使用を検討する必要があります。そうしないと、データベースがそのフィールドやコレクションを削除したり、再作成したりすることになりかねません。
### フィールドを無視する
Isar は、コレクションクラスのすべての public フィールドを永続化します。プロパティや Getter に `@ignore` というアノテーションを付けると、次のコードスニペットのように永続化から除外することができます:
```dart
@collection
class User {
Id? id;
String? firstName;
String? lastName;
@ignore
String? password;
}
```
コレクションが親コレクションからフィールドを継承しているような場合は、通常、 `@Collection` アノテーションの `ignore` プロパティを使用する方が簡単です:
```dart
@collection
class User {
Image? profilePicture;
}
@Collection(ignore: {'profilePicture'})
class Member extends User {
Id? id;
String? firstName;
String? lastName;
}
```
もし、コレクションに Isar がサポートしていない型のフィールドが含まれている場合、そのフィールドは無視しなければなりません。
:::warning
永続化されていない Isar オブジェクトに情報を保存することは、良い習慣ではないことに留意してください。
:::
## 対応している型
Isar は以下のデータ型に対応しています:
- `bool`
- `byte`
- `short`
- `int`
- `float`
- `double`
- `DateTime`
- `String`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
加えて、埋め込み型オブジェクトと列挙型(Enum)もサポートされています。それらについては後述します。
## byte, short, float
多くの場合、64 ビット整数型や double の全範囲は必要ありませんよね。Isar は、より小さな数値を保存する際の為に、容量とメモリを節約することができる追加の型をサポートしています。
| Type | Size in bytes | Range |
| ---------- | ------------- | ------------------------------------------------------- |
| **byte** | 1 | 0 to 255 |
| **short** | 4 | -2,147,483,647 to 2,147,483,647 |
| **int** | 8 | -9,223,372,036,854,775,807 to 9,223,372,036,854,775,807 |
| **float** | 4 | -3.4e38 to 3.4e38 |
| **double** | 8 | -1.7e308 to 1.7e308 |
追加の数値型は Dart のネイティブ型の別名(エイリアス)に過ぎません。例えば `short` を使用すると、 `int` を使用するのと同じように動作します。
以下に、上記のすべての型を含むコレクションの例を示します:
```dart
@collection
class TestCollection {
Id? id;
late byte byteValue;
short? shortValue;
int? intValue;
float? floatValue;
double? doubleValue;
}
```
すべての数値型は List でも使用することができます。バイトを格納する場合は、`List` を使用してください。
## Null 許容型
Isar で nullability(訳注:DB 関連用語では、列などの項目が NULL 値を受け入れる能力)がどのように機能するかを理解するのは非常に重要です:
数値型は、専用の `null` 表現を持ちません。その代わりに、特定の値が使用されます:
| Type | VM |
| ---------- | ------------- |
| **short** | `-2147483648` |
| **int** | `int.MIN` |
| **float** | `double.NaN` |
| **double** | `double.NaN` |
`bool`, `String`, `List` は、それぞれ別の `null` 表現を持ちます。
この動作によってパフォーマンスが向上し、 `null` 値を処理するためのマイグレーションや特別なコードを必要とせずに、フィールドの nullability を自由に変更することができるようになります。
:::warning
`byte` 型は null 値をサポートしていません。
:::
## DateTime
Isar は、日付のタイムゾーン情報を保存しません。その代わり、`DateTime`を UTC に変換してから保存します。Isar はすべての日付をローカルタイムで返します。
`DateTime`はマイクロ秒の精度で保存されます。ただしブラウザ上においては、JavaScript の制限により、ミリ秒の精度しかサポートされていません。
## 列挙型(Enum)
Isar では他の型と同様に、列挙型を保存し使用することができます。しかし、Isar がディスク上でどのように enum を表すかを選択する必要があります。Isar は 4 つの異なる方法をサポートしています。:
| EnumType | Description |
| ----------- | --------------------------------------------------------------------------------------------------------------------- |
| `ordinal` | 列挙型のインデックスは `byte` として格納されます。これは非常に効率的ですが、null 値を許容する enum は使用できません。 |
| `ordinal32` | 列挙型のインデックスは `short` (4 バイトの整数) として格納されます。 |
| `name` | 列挙名称は `String` として格納されます。 |
| `value` | 列挙値の取得には、カスタムプロパティを使用します。 |
:::warning
`ordinal` と `ordinal32` は、列挙された値の順番に依存します。この順序を変更すると、既存のデータベースは不正な値を返す可能性があります。
:::
それでは、それぞれの方法の例を確認してみましょう。
```dart
@collection
class EnumCollection {
Id? id;
@enumerated // EnumType.ordinalと同様
late TestEnum byteIndex; // null 許容には出来ない
@Enumerated(EnumType.ordinal)
late TestEnum byteIndex2; // null 許容には出来ない
@Enumerated(EnumType.ordinal32)
TestEnum? shortIndex;
@Enumerated(EnumType.name)
TestEnum? name;
@Enumerated(EnumType.value, 'myValue')
TestEnum? myValue;
}
enum TestEnum {
first(10),
second(100),
third(1000);
const TestEnum(this.myValue);
final short myValue;
}
```
もちろん、Enum は List 内でも使用可能です。
## 組み込みオブジェクト
コレクションモデルでオブジェクトをネストさせると便利なことがよくあります。オブジェクトをネストさせる深さは無制限です。しかし、深くネストされたオブジェクトを更新するには、オブジェクトツリー全体をデータベースに書き込む必要があることを覚えておいてください。
```dart
@collection
class Email {
Id? id;
String? title;
Recepient? recipient;
}
@embedded
class Recepient {
String? name;
String? address;
}
```
埋め込みオブジェクトは null を許容する事も出来ますし、他のオブジェクトを拡張(extend)することも出来ます。唯一の要件は `@embedded` のアノテーションを付け、required パラメータの無いデフォルトのコンストラクタを持つことです。
================================================
FILE: docs/docs/ja/transactions.md
================================================
---
title: トランザクション
---
# トランザクション
Isarにおいて、トランザクションは複数のデータベース操作を1つの作業単位にまとめます。Isarの大半の処理は、暗黙のうちにトランザクションを利用しています。IsarのRead & write操作は、[ACID](http://en.wikipedia.org/wiki/ACID)に準拠しており、トランザクションは、エラーが発生すると自動的にロールバックされます。
## 明示的なトランザクション
明示的なトランザクションでは、データベースの整合性のあるスナップショットを取得したり、トランザクションの継続時間を最小限にするようにします。また、トランザクションの中でネットワークの呼び出しやその他の長時間実行される操作を行うことは禁じられています。
トランザクション(特に書き込みトランザクション)にはコストがかかるので、連続する操作は常に1つのトランザクションにまとめるようにしましょう。
トランザクションには、同期と非同期があります。同期トランザクションでは、同期操作のみを使用することができて、非同期トランザクションでは、非同期操作のみを使用することができます。
| | Read | Read & Write |
|--------------|--------------|--------------------|
| 同期 | `.txnSync()` | `.writeTxnSync()` |
| 非同期 | `.txn()` | `.writeTxn()` |
### 読み取りトランザクション
明示的な読み取りトランザクションは任意ですが、Atomic(原子性の)読み取りを可能にし、トランザクション内のデータベースの一貫した状態に依存することができます。内部的には、Isarはすべての読み込み操作に対して常に暗黙的な読み込みトランザクションを使用します。
:::tip
非同期読み取りトランザクションは、他の読み取りおよび書き込みトランザクションと並行して実行されます。かなりイケてますよね?
:::
### 書き込みトランザクション
読み込み操作とは異なり、Isarでの書き込み操作は明示的なトランザクションに包まれる必要があります。
書き込みトランザクションが正常に終了すると、自動的にコミットされ、すべての変更がディスクに書き込まれます。もしエラーが発生した場合、トランザクションは中断され、すべての変更がロールバックされます。トランザクションは "all or nothing"です。トランザクション内のすべての書き込みが成功するか、データの一貫性を保証するために何も実行されないかのどちらかである。
:::warning
データベース操作に失敗した場合、トランザクションは破棄され、それ以降使用してはいけません。たとえDartでエラーを捕捉したとしてもです。
:::
```dart
@collection
class Contact {
Id? id;
String? name;
}
// GOOD
await isar.writeTxn(() async {
for (var contact in getContacts()) {
await isar.contacts.put(contact);
}
});
// BAD: トランザクションの中にforループを移動させましょう。
for (var contact in getContacts()) {
await isar.writeTxn(() async {
await isar.contacts.put(contact);
});
}
```
================================================
FILE: docs/docs/ja/tutorials/quickstart.md
================================================
---
title: クイックスタート
---
# クイックスタート
お待たせしました。さあ、最高にクールなFlutterのデータベースを使い始めましょう!
この記事では、簡潔にコードを書いていきます。
## 1. 依存関係を追加する
Isarを使用する前に、いくつかのパッケージを `pubspec.yaml` に追加する必要があります。pubを使用する事で、面倒な作業を簡単に済ませることが出来ます。
```bash
dart pub add isar:^0.0.0-placeholder isar_flutter_libs:^0.0.0-placeholder --hosted-url=https://pub.isar-community.dev
dart pub add dev:isar_generator:^0.0.0-placeholder --hosted-url=https://pub.isar-community.dev
```
## 2. クラスの注釈(アノテーション)
あなたの使用するコレクションクラスに `@collection` でアノテーションを付け、`Id` フィールドを設定します。
```dart
part 'user.g.dart';
@collection
class User {
Id id = Isar.autoIncrement; // id = nullでも自動インクリメントされます。
String? name;
int? age;
}
```
idはコレクション内のオブジェクトを一意に識別して、後で再び見つけられるようにします。
## 3. コード生成ツールの実行
以下のコマンドを実行して、`build_runner`を起動します。:
```
dart run build_runner build
```
Flutterを使用している場合は、代わりに次のコマンドを使用してください:
```
flutter pub run build_runner build
```
## 4. Isarインスタンスを開く
新規のIsarインスタンスを開き、コレクションのスキーマを渡します。必要に応じて、インスタンス名とディレクトリを指定することができます。
```dart
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[UserSchema],
directory: dir.path,
);
```
## 5. 書き込みと読み込み
Isarインスタンスを開いたら, コレクションを利用することができます.
基本的なCRUD操作は、全て `IsarCollection` を介して行う事が出来ます。
```dart
final newUser = User()..name = 'Jane Doe'..age = 36;
await isar.writeTxn(() async {
await isar.users.put(newUser); // 挿入と更新
});
final existingUser = await isar.users.get(newUser.id); // 取得
await isar.writeTxn(() async {
await isar.users.delete(existingUser.id!); // 削除
});
```
## その他の資料
視覚的に学ぶ方が好みであれば、Isarを始めるためにこれらの動画をぜひご覧ください:
================================================
FILE: docs/docs/ja/watchers.md
================================================
---
title: ウォッチャー
---
# ウォッチャー
Isar では、データベースの変更を監視することができます。特定のオブジェクトやコレクション全体、あるいはクエリの変更を "監視" することができます。
ウォッチャーを使うと、データベースの変更に迅速に対応することができます。例えば、連絡先が追加されたときに UI を再構築したり、ドキュメントが更新されたときにネットワークリクエストを送ったりすることができます。
ウォッチャーは、トランザクションが正常にコミットされ、ターゲットが実際に変更された後に通知されます。
## オブジェクトの監視
特定のオブジェクトが作成、更新、削除されたときに通知を受けたい場合、そのオブジェクトを監視する必要があります:
```dart
Stream userChanged = isar.users.watchObject(5);
userChanged.listen((newUser) {
print('User changed: ${newUser?.name}');
});
final user = User(id: 5)..name = 'David';
await isar.users.put(user);
// prints: User changed: David
final user2 = User(id: 5)..name = 'Mark';
await isar.users.put(user);
// prints: User changed: Mark
await isar.users.delete(5);
// prints: User changed: null
```
上記の例からわかるように、オブジェクトはまだ存在しなくてもかまいません。オブジェクトが作成されると、ウォッチャーに通知されます。
追加のパラメータとして `fireImmediately` があります。これを `true` に設定すると、Isar はオブジェクトの現在の値を即座に Stream に追加します。
### レイジーウォッチング
新しい値を受け取る必要はなく、変更された事についてのみ通知して欲しい場合があるかもしれません。
その場合、Isarはオブジェクトを取得する手間を省くことができます。
```dart
Stream userChanged = isar.users.watchObjectLazy(5);
userChanged.listen(() {
print('User 5 changed');
});
final user = User(id: 5)..name = 'David';
await isar.users.put(user);
// prints: User 5 changed
```
## コレクションの監視
単一のオブジェクトを監視する代わりに、コレクション全体を監視し、いずれかのオブジェクトが追加、更新、または削除されたときに通知を受けることができます:
```dart
Stream userChanged = isar.users.watchLazy();
userChanged.listen(() {
print('A User changed');
});
final user = User()..name = 'David';
await isar.users.put(user);
// prints: A User changed
```
## クエリの監視
クエリ全体を監視することも可能です。Isarは、クエリの結果が実際に変更されたときのみ通知するよう最善を尽くします。ただ、リンクが原因でクエリが変更された場合は通知されません。リンクの変更について通知を受ける必要がある場合は、コレクションウォッチャーを使用してください。
```dart
Query usersWithA = isar.users.filter()
.nameStartsWith('A')
.build();
Stream> queryChanged = usersWithA.watch(fireImmediately: true);
queryChanged.listen((users) {
print('Users with A are: $users');
});
// prints: Users with A are: []
await isar.users.put(User()..name = 'Albert');
// prints: Users with A are: [User(name: Albert)]
await isar.users.put(User()..name = 'Monika');
// no print
await isar.users.put(User()..name = 'Antonia');
// prints: Users with A are: [User(name: Albert), User(name: Antonia)]
```
:::warning
offset & limit や distinct クエリを使用する場合, オブジェクトがフィルタにマッチしたが、クエリの外でoffset & limitなどから結果が変化した場合にも、Isar は通知します。
:::
`watchObject()` と同様に、`watchLazy()` を使うと、クエリの結果が変わっても、結果を取得せずに通知を受けることができます。
:::danger
変更があるたびにクエリを再実行するのは非効率です。その代わりにLazyコレクションウォッチャーを使うとよいでしょう。
:::
================================================
FILE: docs/docs/ko/README.md
================================================
---
home: true
title: 홈
heroImage: /isar.svg
actions:
- text: 시작하기!
link: /tutorials/quickstart.html
type: primary
features:
- title: 💙 플러터를 위해 만들었어요
details: 설정은 최소로, 사용하기 쉽고, 구성도 없고, 보일러 플레이트도 없습니다. 코드 몇 줄만 추가하면 바로 시작할 수 있습니다.
- title: 🚀 뛰어난 확장성
details: 수십만 개의 레코드를 단일 NoSQL 데이터베이스에 저장하고 효율적이고 비동기적으로 쿼리할 수 있습니다.
- title: 🍭 풍부한 기능
details: Isar에는 데이터를 관리하기 위한 다양한 기능이 있습니다. 복합 & 다중 항목 인덱스, 쿼리 수정자, JSON 지원 등이 있습니다.
- title: 🔎 전체 텍스트 검색
details: Isar는 전체 텍스트 검색 기능이 내장되어 있습니다. 다중 항목 색인을 작성하고 레코드를 쉽게 검색할 수 있습니다.
- title: 🧪 ACID 시멘틱
details: Isar는 ACID를 준수하며 트랜잭션을 자동으로 처리합니다. 오류가 발생하면 변경 내용을 롤백합니다.
- title: 💃 정적 타입
details: Isar의 쿼리는 정적 타입이고, 컴파일 시간에 검사됩니다. 런타임 오류에 대해 걱정할 필요가 없습니다.
- title: 📱 다중 플랫폼
details: iOS, Android, Desktop 및 완전한 웹 지원!
- title: ⏱ 비동기
details: 병렬 쿼리 작업 및 다중 Isolate 지원을 즉시 사용할 수 있습니다.
- title: 🦄 오픈 소스입니다.
details: 모든 것이 오픈 소스이며 영원히 무료입니다.
footer: Apache Licensed | Copyright © 2022 Simon Leier
---
================================================
FILE: docs/docs/ko/crud.md
================================================
---
title: CRUD 조작
---
# CRUD 조작
컬렉션이 정의되었다면, 이제 조작하는 방법을 배워봅시다!
## Isar 열기
무엇을 하든 우선 Isar 인스턴스가 필요합니다. 각 인스턴스에는 데이터베이스 파일을 저장할 수 있도록 쓰기 권한이 있는 디렉토리가 필요합니다. 디렉토리를 지정하지 않는 경우 Isar는 현재 플랫폼에 적합한 기본 디렉토리를 찾습니다.
Isar 인스턴스에서 사용하고 싶은 모든 스키마를 지정합니다. 여러 인스턴스를 열고 있는 경우에도 각각의 인스턴스에 동일한 스키마를 부여해야 합니다.
```dart
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[RecipeSchema],
directory: dir.path,
);
```
기본 구성을 사용하거나 다음 매개 변수 중 일부를 제공할 수 있습니다:
| Config | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name` | 여러 인스턴스를 다른 이름으로 엽니다. 기본적으로 `"default"`가 사용됩니다. |
| `directory` | 이 인스턴스의 저장 장소 입니다. 상대 경로 또는 절대 경로를 전달할 수 있습니다. 기본값으로, iOS 에서는 `NSDocumentDirectory`, Android 에서는 `getDataDirectory` 가 사용됩니다. 웹에서는 필요하지 않습니다. |
| `maxSizeMib` | 데이터베이스 파일의 최대 크기(MiB)입니다. Isar는 무한하지 않은 가상 메모리를 사용하므로 여기 값을 명심하세요. 여러 인스턴스를 열면 사용 가능한 가상 메모리가 공유되므로 각 인스턴스의 `maxSizeMib` 가 더 작아집니다. 기본값은 2048 입니다. |
| `relaxedDurability` | 내구성 보장을 완화하여 쓰기 성능을 향상 시킵니다. 시스템 충돌(앱 충돌이 아닌) 의 경우 마지막으로 커밋된 트랜잭션이 손실될 수 있습니다. 완전히 파손(Corruption)될 가능성은 없습니다. |
| `compactOnLaunch` | 인스턴스를 열 때 데이터베이스를 압축해야 하는지 여부를 확인하는 조건입니다. |
| `inspector` | 디버그 빌드에 대해서 Inspector 를 사용하도록 설정합니다. 프로파일이나 릴리즈 빌드에서는 이 옵션이 무시됩니다. |
인스턴스가 이미 열려 있는 경우 `Isar.open()` 을 호출하면 지정된 매개 변수에 관계없이 기존 인스턴스가 생성됩니다. isolate 안에서 Isar 를 사용할 때 유용합니다.
:::tip
모든 플랫폼에서 유효한 저장 경로를 얻기 위해서 [path_provider](https://pub.dev/packages/path_provider) 패키지를 사용하는 것을 고려해보세요.
:::
데이터베이스 파일의 저장 위치는 `directory/name.isar` 입니다.
## 데이터베이스에서 읽기
Isar 에서 지정된 타입의 새로운 객체를 찾고 쿼리하고 생성할 때 `IsarCollection` 인스턴스를 사용합니다.
밑에 나올 예시들에서, 우리는 다음과 같이 정의된 `Recipe` 컬렉션이 있다고 가정합니다.
```dart
@collection
class Recipe {
Id? id;
String? name;
DateTime? lastCooked;
bool? isFavorite;
}
```
### 컬렉션을 가져오기
모든 컬렉션들은 Isar 인스턴스 안에 있습니다. 레시피 컬렉션은 다음 방법으로 가져옵니다:
```dart
final recipes = isar.recipes;
```
너무 쉽죠! 컬렉션 접근자를 사용하기 싫다면, `collection()` 메서드를 사용해도 됩니다.
```dart
final recipes = isar.collection();
```
### 객체 얻기 (id를 이용)
아직 컬렉션에 데이터가 들어있지 않지만, 아이디 `123` 의 가상의 객체가 있다고 가정하고 가져오겠습니다.
```dart
final recipe = await isar.recipes.get(123);
```
`get()` 은 객체를 `Future` 로 반환하고, 해당 객체가 존재하지 않는 경우에는 `null` 을 반환합니다. 모든 Isar 작업들은 기본적으로 비동기적으로 작동합니다. 대부분의 경우는 동기적인 방법도 가지고 있습니다.
```dart
final recipe = isar.recipes.getSync(123);
```
:::warning
UI isolate 에서는 비동기 버전을 기본적으로 사용해야 합니다. 하지만 Isar 는 매우 빠르기 때문에, 동기식으로 사용하는 것도 종종 허용됩니다.
:::
한 번에 여러 객체를 가져오려면 `getAll()` 또는 `getAllSync()` 를 사용하세요:
```dart
final recipe = await isar.recipes.getAll([1, 2]);
```
### 객체 쿼리
id를 이용해서 객체를 가져오는 대신, `.where()` 과 `.filter()` 를 사용해서 특정 조건에 맞는 객체 목록을 쿼리할 수 있습니다:
```dart
final allRecipes = await isar.recipes.where().findAll();
final favouires = await isar.recipes.filter()
.isFavoriteEqualTo(true)
.findAll();
```
➡️ 더 알아보기: [Queries](queries)
## 데이터베이스 수정하기
드디어 컬렉션을 수정할 때가 됐습니다! 객체를 생성, 갱신, 삭제하려면 쓰기 트랜잭션 안에서 각각의 작업들을 수행하세요.
```dart
await isar.writeTxn(() async {
final recipe = await isar.recipes.get(123)
recipe.isFavorite = false;
await isar.recipes.put(recipe); // 갱신 작업을 수행합니다.
await isar.recipes.delete(123); // 또는 삭제 작업
});
```
➡️ 더 알아보기: [Transactions](transactions)
### 객체 삽입
Isar 에 객체를 보존하기 위해서, 컬렉션에 집어넣어야 합니다. 컬렉션에 객체를 삽입할 때는 Isar의 `put()` 메소드를 이용합니다. 만약 이미 들어있는 객체라면 갱신을 합니다.
id 필드가 `null` 이나 `Isar.autoIncrement` 라면, Isar 는 자동 증분 아이디를 사용합니다.
```dart
final pancakes = Recipe()
..name = 'Pancakes'
..lastCooked = DateTime.now()
..isFavorite = true;
await isar.writeTxn(() async {
await isar.recipes.put(pancakes);
})
```
`id` 필드가 final 이 아닌 경우에 Isar 가 id를 객체에 자동으로 할당합니다.
여러 객체를 한 번에 삽입하는 것도 쉽습니다:
```dart
await isar.writeTxn(() async {
await isar.recipes.putAll([pancakes, pizza]);
})
```
### 객체 갱신
`collection.put(object)` 를 이용해서 만들고 갱신하는 동작을 모두 할 수 있습니다. id가 `null`(또는 존재하지 않는 경우) 이라면, 객체는 추가됩니다. 그 이외의 경우에는 갱신됩니다.
만약 팬케익에 즐겨찾기를 해제하는 경우, 이렇게 할 수 있습니다.
```dart
await isar.writeTxn(() async {
pancakes.isFavorite = false;
await isar.recipes.put(pancakes);
});
```
### 객체 삭제
Isar 에 있는 것을 없애고 싶나요? `collection.delete(id)` 를 사용하세요. delete 메소드는 주어진 id 를 가진 객체를 찾아서 삭제합니다. id `123` 을 가지는 객체를 삭제하는 예시 입니다:
```dart
await isar.writeTxn(() async {
final success = await isar.recipes.delete(123);
print('Recipe deleted: $success');
});
```
get 과 put 과 마찬가지로 delete 에도 여러개를 한꺼번에 삭제하는 방법이 있습니다. 삭제된 객체의 수를 반환합니다.
```dart
await isar.writeTxn(() async {
final count = await isar.recipes.deleteAll([1, 2, 3]);
print('We deleted $count recipes');
});
```
만약 삭제할 객체의 id 를 모른다면 query 를 사용할 수 있습니다.
```dart
await isar.writeTxn(() async {
final count = await isar.recipes.filter()
.isFavoriteEqualTo(false)
.deleteAll();
print('We deleted $count recipes');
});
```
================================================
FILE: docs/docs/ko/faq.md
================================================
---
title: 자주 묻는 질문들
---
# 자주 묻는 질문들
Isar 와 Flutter 데이터베이스에 대해서 자주 물어보는 질문들을 랜덤으로 뽑아봤습니다.
### 데이터베이스가 왜 필요하죠?
> 저는 백엔드 데이터베이스에 데이터를 보관해요. 왜 Isar 가 필요하죠?
심지어 요즘에도, 지하철이나 비행기 안에 있거나 와이파이가 없는 할머니 집을 갈 때는 데이터 연결이 없는 경우가 흔하게 있습니다. 나쁜 연결로 인해서 앱이 먹통이 되는 일이 없어야 합니다!
### Isar vs Hive
답은 간단합니다: Isar 는 [Hive의 대체재로 시작](https://github.com/hivedb/hive/issues/246) 했었고 지금은 저는 항상 Hive보다 Isar 를 사용하는 것을 추천합니다.
### Where 절?!
> 왜 **_내_** 가 어떤 인덱스를 사용할 지 선택해야 합니까?
여러 이유가 있습니다. 대부분의 데이터베이스는 휴리스틱을 사용해서 주어진 쿼리에 가장 적합한 인덱스를 선택합니다. 데이터베이스가 추가 사용량 데이터(-> 오버헤드) 를 수집해야 하지만 여전히 잘못된 인덱스를 선택할 수 있습니다. 또한 쿼리를 작성하는 속도가 느려지게 됩니다.
개발자인 여러분보다 당신의 데이터를 잘 아는 사람은 아무도 없습니다. 따라서 최적의 인덱스를 선택하고 쿼리나 정렬에 사용할 인덱스를 결정할 수 있습니다.
### 인덱스 / where 절을 사용해야 합니까?
아뇨! 필터에만 의존해도 Isar 는 충분히 빠릅니다.
### Isar 가 충분히 빠른가요?
Isar는 모바일용 데이터베이스 중 가장 빠르기 때문에 대부분의 사용 사례에서 충분히 빠릅니다. 성능 문제가 발생하면 뭔가를 잘못했을 가능성이 높습니다.
### Isar 가 제 앱의 크기를 늘리나요?
조금은 그렇죠. Isar는 다운로드 크기를 1 - 1.5 MB 정도 늘릴 겁니다. Isar Web 은 몇 KB 만 추가합니다.
### 문서가 잘못됐네요 / 오타가 있어요.
이런, 죄송해요. [이슈 열기](https://github.com/isar-community/isar/issues/new/choose) 또는 PR 을 통해서 고쳐주세요. 💪.
================================================
FILE: docs/docs/ko/indexes.md
================================================
---
title: 인덱스
---
# 인덱스
인덱스는 Isar 의 가장 강력한 기능입니다. 대부분의 내장 데이터베이스는 "일반적인" 인덱스만을 제공하지만(인덱스가 있다면요), Isar 에는 복합 및 다중 항목 인덱스도 있습니다. 쿼리 성능을 최적화하려면 인덱스 작동 방식을 이해하는 것이 필수적입니다. Isar 를 사용하면 사용할 인덱스와 인덱스 사용 방법을 선택할 수 있습니다. 인덱스가 무엇인지에 대한 간단한 소개로 시작하겠습니다.
## 인덱스가 뭔가요?
컬렉션이 인덱싱되지 않은 경우, 쿼리 입장에서는 행의 순서가 전혀 최적화 되지 않은 것으로 식별되지 않을 수 있습니다. 그래서 쿼리는 객체를 선형으로 검색해야만 합니다. 즉, 쿼리는 조건과 일치하는 객체를 찾기 위해서 모든 객체를 검색해야 합니다. 예상한대로, 그건 시간이 오래 걸립니다. 모든 객체를 하나하나 훑어보는 것은 그다지 효율적이지 않습니다.
예를 들어 이 `Product` 컬렉션에는 전혀 순서가 없습니다.
```dart
@collection
class Product {
Id? id;
late String name;
late int price;
}
```
#### 데이터:
| id | name | price |
| --- | --------- | ----- |
| 1 | Book | 15 |
| 2 | Table | 55 |
| 3 | Chair | 25 |
| 4 | Pencil | 3 |
| 5 | Lightbulb | 12 |
| 6 | Carpet | 60 |
| 7 | Pillow | 30 |
| 8 | Computer | 650 |
| 9 | Soap | 2 |
가격이 30 유로 이상인 모든 제품을 찾는 쿼리는 9개 행을 모두 검색해야 합니다. 9개 행은 문제가 없지만, 10만 행이 되면 문제가 될 수 있습니다.
```dart
final expensiveProducts = await isar.products.filter()
.priceGreaterThan(30)
.findAll();
```
이 쿼리 성능을 개선하기 위해서 우리는 `price` 속성을 인덱스해야 합니다. 인덱스는 정렬된 룩업 테이블과 같습니다.
```dart
@collection
class Product {
Id? id;
late String name;
@Index()
late int price;
}
```
#### 생선된 인덱스:
| price | id |
| -------------------- | ------------------ |
| 2 | 9 |
| 3 | 4 |
| 12 | 5 |
| 15 | 1 |
| 25 | 3 |
| 30 | 7 |
| **55** | **2** |
| **60** | **6** |
| **650** | **8** |
이제 쿼리는 훨씬 빠르게 실행할 수 있습니다. 실행자(executor) 는 마지막 3 개의 인덱스 행으로 바로 이동해서 ID 로 해당 객체를 찾을 수 있습니다.
### 정렬
또 다른 멋진 점은 인덱스가 매우 빠른 정렬을 할 수 있다는 것입니다. 정렬된 쿼리는 정렬하기 전에 데이터베이스가 모든 결과를 메모리에 로드해야 하므로 비용이 많이 듭니다. 오프셋이나 제한을 지정하더라도 정렬 이후에 적용됩니다.
가장 싼 4개의 제품을 찾고 싶다고 가정해 보겠습니다. 다음 쿼리를 사용할 수 있습니다.
```dart
final cheapest = await isar.products.filter()
.sortByPrice()
.limit(4)
.findAll();
```
이 예에서 데이터베이스는 모든 (!) 객체를 로드하고 가격별로 정렬한 다음 가장 낮은 가격으로 4개의 제품을 반환해야 합니다.
예상대로, 전의 인덱스를 사용하면 훨씬 효율적으로 작업을 수행할 수 있습니다. 데이터베이스는 인덱스의 처음 4개 행을 사용하고 해당 객체가 이미 올바른 순서에 있으므로 해당 객체를 반환합니다.
정렬에 인덱스를 사용하려면 다음과 같이 쿼리를 작성합니다.
```dart
final cheapestFast = await isar.products.where()
.anyPrice()
.limit(4)
.findAll();
```
`.anyX()` 여기서 절은 Isar 에 정렬에만 인덱스를 사용하도록 지시합니다. `.priceGreaterThan()` 과 같은 where 절을 사용해서 정렬된 결과를 얻을 수도 있습니다.
## 고유 인덱스(Unique indexes)
고유 인덱스는 인덱스에 중복된 값이 포함되지 않게 합니다. 고유 인덱스는 하나 이상의 속성으로 이루어 집니다. 고유한 인덱스에 속성이 하나 있으면 이 속성의 값이 고유하게 됩니다(중복이 허용되지 않게 됩니다). 고유 인덱스에 둘 이상의 속성이 있는 경우 이러한 속성의 값 조합은 고유합니다.
```dart
@collection
class User {
Id? id;
@Index(unique: true)
late String username;
late int age;
}
```
중복을 유발하는 데이터 삽입이나 업데이트를 시도하면 오류가 발생합니다:
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1); // -> 괜찮습니다.
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
// 같은 유저 이름으로 유저 삽입을 시도
await isar.users.put(user2); // -> 에러: 고유 제약조건 위반
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
```
## 인덱스 대체 (replace indexes)
고유 제약조건을 위반할 경우에 에러가 발생하는 것이 좋지 않을 수도 있습니다. 대신에 기존 객체를 새로운 객체로 대체할 수 있습니다. 이는 인덱스의 `replace` 속성을 `true` 로 설정해서 수행할 수 있습니다.
```dart
@collection
class User {
Id? id;
@Index(unique: true, replace: true)
late String username;
}
```
이제 기존 사용자 이름을 가진 사용자를 삽입하려고 하면 Isar 가 기존 사용자를 새 사용자로 대체합니다.
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1);
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
print(await isar.user.where().findAll());
// > [{id: 2, username: 'user1' age: 30}]
```
인덱스 대체는 객체를 바꾸는 대신 업데이트할 수 있는 `putBy()` 메서드를 생성합니다. 기존 ID 는 재사용되고 링크는 여전히 채워집니다.
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
// user does not exist so this is the same as put()
await isar.users.putByUsername(user1);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1' age: 30}]
```
As you can see, the id of the first inserted user is reused.
## Case-insensitive indexes
All indexes on `String` and `List` properties are case-sensitive by default. If you want to create a case-insensitive index, you can use the `caseSensitive` option:
```dart
@collection
class Person {
Id? id;
@Index(caseSensitive: false)
late String name;
@Index(caseSensitive: false)
late List tags;
}
```
## 인덱스 유형
There are different types of indexes. Most of the time, you'll want to use an `IndexType.value` index, but hash indexes are more efficient.
### Value index
Value indexes are the default type and the only one allowed for all properties that don't hold Strings or Lists. Property values are used to build the index. In the case of lists, the elements of the list are used. It is the most flexible but also space-consuming of the three index types.
:::tip
Use `IndexType.value` for primitives, Strings where you need `startsWith()` where clauses, and Lists if you want to search for individual elements.
:::
### Hash index
Strings and Lists can be hashed to reduce the storage required by the index significantly. The disadvantage of hash indexes is that they can't be used for prefix scans (`startsWith` where clauses).
:::tip
Use `IndexType.hash` for Strings and Lists if you don't need `startsWith`, and `elementEqualTo` where clauses.
:::
### HashElements index
String lists can be hashed as a whole (using `IndexType.hash`), or the elements of the list can be hashed separately (using `IndexType.hashElements`), effectively creating a multi-entry index with hashed elements.
:::tip
Use `IndexType.hashElements` for `List` where you need `elementEqualTo` where clauses.
:::
## Composite indexes
A composite index is an index on multiple properties. Isar allows you to create composite indexes of up to three properties.
Composite indexes are also known as multiple-column indexes.
It's probably best to start with an example. We create a person collection and define a composite index on the age and name properties:
```dart
@collection
class Person {
Id? id;
late String name;
@Index(composite: [CompositeIndex('name')])
late int age;
late String hometown;
}
```
#### Data:
| id | name | age | hometown |
| --- | ------ | --- | --------- |
| 1 | Daniel | 20 | Berlin |
| 2 | Anne | 20 | Paris |
| 3 | Carl | 24 | San Diego |
| 4 | Simon | 24 | Munich |
| 5 | David | 20 | New York |
| 6 | Carl | 24 | London |
| 7 | Audrey | 30 | Prague |
| 8 | Anne | 24 | Paris |
#### Generated index
| age | name | id |
| --- | ------ | --- |
| 20 | Anne | 2 |
| 20 | Daniel | 1 |
| 20 | David | 5 |
| 24 | Anne | 8 |
| 24 | Carl | 3 |
| 24 | Carl | 6 |
| 24 | Simon | 4 |
| 30 | Audrey | 7 |
The generated composite index contains all persons sorted by their age their name.
Composite indexes are great if you want to create efficient queries sorted by multiple properties. They also enable advanced where clauses with multiple properties:
```dart
final result = await isar.where()
.ageNameEqualTo(24, 'Carl')
.hometownProperty()
.findAll() // -> ['San Diego', 'London']
```
The last property of a composite index also supports conditions like `startsWith()` or `lessThan()`:
```dart
final result = await isar.where()
.ageEqualToNameStartsWith(20, 'Da')
.findAll() // -> [Daniel, David]
```
## Multi-entry indexes
If you index a list using `IndexType.value`, Isar will automatically create a multi-entry index, and each item in the list is indexed toward the object. It works for all types of lists.
Practical applications for multi-entry indexes include indexing a list of tags or creating a full-text index.
```dart
@collection
class Product {
Id? id;
late String description;
@Index(type: IndexType.value, caseSensitive: false)
List get descriptionWords => Isar.splitWords(description);
}
```
`Isar.splitWords()` splits a string into words according to the [Unicode Annex #29](https://unicode.org/reports/tr29/) specification, so it works for almost all languages correctly.
#### Data:
| id | description | descriptionWords |
| --- | ---------------------------- | ---------------------------- |
| 1 | comfortable blue t-shirt | [comfortable, blue, t-shirt] |
| 2 | comfortable, red pullover!!! | [comfortable, red, pullover] |
| 3 | plain red t-shirt | [plain, red, t-shirt] |
| 4 | red necktie (super red) | [red, necktie, super, red] |
Entries with duplicate words only appear once in the index.
#### Generated index
| descriptionWords | id |
| ---------------- | --------- |
| comfortable | [1, 2] |
| blue | 1 |
| necktie | 4 |
| plain | 3 |
| pullover | 2 |
| red | [2, 3, 4] |
| super | 4 |
| t-shirt | [1, 3] |
This index can now be used for prefix (or equality) where clauses of the individual words of the description.
:::tip
Instead of storing the words directly, also consider using the result of a [phonetic algorithm](https://en.wikipedia.org/wiki/Phonetic_algorithm) like [Soundex](https://en.wikipedia.org/wiki/Soundex).
:::
================================================
FILE: docs/docs/ko/limitations.md
================================================
# 제한 사항
아시다시피 Isar 는 VM 에서 실행되는 모바일 장치 및 데스크톱과 웹에서 작동합니다. 두 플랫폼은 매우 다르고 다른 한계점을 가지고 있습니다.
As you know, Isar works on mobile devices and desktops running on the VM as well as Web. Both platforms are very different and have different limitations.
## VM 에서의 제한사항
- where 절에는 문자열의 처음 1024바이트만 사용할 수 있습니다.
- 객체의 크기는 16MB 를 넘을 수 없습니다.
## 웹 에서의 제한사항
Isar Web 은 IndexedDB 에 의존하고 있습니다. 그래서 더 많은 제약이 있지만, Isar 를 사용하는 동안 거의 눈치채기 어렵습니다.
- 동기식 메서드들은 지원되지 않습니다.
- 현재 `Isar.splitWords()` 및 `.matches()` 필터가 구현되지 않았습니다.
- 스키마 변경 사항이 VM에서만큼 엄격하게 확인되지 않기 때문에 규칙을 준수하도록 주의하십시오.
- 모든 숫자 유형이 두 배(js의 number 타입) 으로 저장되므로 `@Size32` 가 효과가 없습니다.
- 해시 인덱스가 더 적은 공간을 사용하지 않도록 인덱스가 다르게 표기됩니다.(여전히 동일하게 작동합니다.)
- `col.delete()` 및 `col.deleteAll()` 이 올바르게 작동하지만 반환 값은 올바르지 않습니다.
- `col.clear()` 자동 증분 값을 초기화 하지 않습니다.
- `NaN` 값으로 지원되지 않습니다.
================================================
FILE: docs/docs/ko/links.md
================================================
---
title: 링크
---
# 링크
링크를 사용해서 댓글 작성자(사용자) 같은 객체 간의 관계를 나타낼 수 있습니다. Isar 링크를 사용해서 `1:1`, `1:n`, 과 `n:n` 관계를 모델링할 수 있습니다. 링크를 사용하는 것은 내장된 객체를 사용하는 것보다 인체 공학적이지 않으므로(less ergonomic), 가능하다면 임베드된 객체를 사용해야 합니다.
링크를 관계를 포함하는 별도의 테이블로 간주합니다. SQL 관계와 비슷하지만 사용가능한 기능과 API 가 다릅니다.
## IsarLink
`IsarLink` 는 관련 객체를 포함하지 않거나 하나만 포함할 수 있으며 일대일 관계를 표현하는데 사용할 수 있습니다. `IsarLink` 에는 연결된 객체를 가지는 `value` 라는 단일 속성이 있습니다.
링크는 게으르므로 `IsarLink` 에 `value` 를 명시적으로 로드하거나 저장하도록 지시해야 합니다. `linkProperty.load()` 및 `linkProperty.save()` 를 호출하여 이 작업을 수행할 수 있습니다.
:::tip
링크의 원본 및 대상 컬렉션의 ID 타입은 final 이 아니어야 합니다.
:::
웹이 아닌 타겟에서는, 링크를 처음 사용할 때 링크가 자동 로드 됩니다. 먼저 IsarLink 를 컬렉션에 추가합니다:
```dart
@collection
class Teacher {
Id? id;
late String subject;
}
@collection
class Student {
Id? id;
late String name;
final teacher = IsarLink();
}
```
우리는 선생님과 학생들 사이의 링크를 정의했습니다. 이 예에서 모든 학생은 정확히 한 명의 선생님만 가질 수 있습니다.
먼, 우리는 선생님을 만들어 학생에게 할당합니다. 우리는 선생님을 `.put()` 하고 링크를 수동으로 저장해야 합니다.
```dart
final mathTeacher = Teacher()..subject = 'Math';
final linda = Student()
..name = 'Linda'
..teacher.value = mathTeacher;
await isar.writeTxn(() async {
await isar.students.put(linda);
await isar.teachers.put(mathTeacher);
await linda.teachers.save();
});
```
우리는 이제 링크를 이용할 수 있습니다:
```dart
final linda = await isar.students.where().nameEqualTo('Linda').findFirst();
final teacher = linda.teacher.value; // > Teacher(subject: 'Math')
```
동기 코드로 똑같이 해보겠습니다. `.putSync()` 는 모든 링크를 자동으로 저장하므로 수동으로 링크를 저장할 필요가 없습니다. 심지어 우리를 위해서 선생님을 만듭니다.
```dart
final englishTeacher = Teacher()..subject = 'English';
final david = Student()
..name = 'David'
..teacher.value = englishTeacher;
isar.writeTxnSync(() {
isar.students.putSync(david);
});
```
## IsarLinks
이전 예시의 학생이 여러 명의 선생님을 가질 수 있다면 더 그럴듯할 것입니다. 다행히 Isar는 `IsarLinks` 를 가지고 있습니다. 여러 개의 관련 객체를 포함할 수 있으며 -N 관계(1:N, N:N)를 표현할 수 있습니다.
`IsarLinks` 는 `Set` 을 확장하고, set에서 사용하는 모든 메서드들을 사용할 수 있습니다.
`IsarLinks` 는 `IsarLink` 와 비슷한 행동을 하고 lazy 합니다. 연결된 모든 객체를 로드하려면 `linkProperty.load()`를 호출하세요. 변경 내용을 유지하려면, `linkProperty.save()` 를 호출하세요.
내부적으로 `IsarLink` 와 `IsarLinks` 는 동일한 방식으로 표현됩니다. 우리는 이전 예제의 `IsarLink` 를 `IsarLinks` 로 업그레이드 해서 한 학생에 여러 선생님들을 할당할 수 있습니다.
```dart
@collection
class Student {
Id? id;
late String name;
final teacher = IsarLinks();
}
```
우리가 링크(`teacher`)의 이름을 바꾸지 않았기 때문에 Isar 는 이전의 것들을 기억하고 있습니다.
```dart
final biologyTeacher = Teacher()..subject = 'Biology';
final linda = isar.students.where()
.filter()
.nameEqualTo('Linda')
.findFirst();
print(linda.teachers); // {Teacher('Math')}
linda.teachers.add(biologyTeacher);
await isar.writeTxn(() async {
await linda.teachers.save();
});
print(linda.teachers); // {Teacher('Math'), Teacher('Biology')}
```
## 백링크 (Backlinks)
이렇게 물을 수 있습니다. "만약 역관계를 표현하려면 어떻게 해야 하나요?". 걱정마세요; 백링크가 있습니다.
백링크는 역방향 링크입니다. 각 링크들은 항상 암시적인 백링크를 가지고 있습니다. `IsarLink`, `IsarLinks` 에 `@BackLink()` 어노테이션을 써서 만들 수 있습니다.
백링크는 추가적인 메모리나 자원을 필요로 하지 않습니다; 항상 데이터 손실 없이 자유롭게 추가하고 삭제하고 이름을 바꿀 수 있습니다.
우리는 특정한 선생님이 어떤 학생을 가지고 있는지를 알고 싶습니다, 그래서 백링크를 정의합니다:
```dart
@collection
class Teacher {
Id id;
late String subject;
@Backlink(to: 'teacher')
final student = IsarLinks();
}
```
우리는 백링크가 어느 링크를 가르키는지를 지정해야 합니다. 두 개의 객체 간에도 여러 링크가 있을 수 있습니다.
## 링크들을 초기화하기
`IsarLink` 와 `IsarLinks` 는 매개변수가 없는 생성자를 가지고, 객체가 만들어 질 때 링크 속성을 할당해야 합니다. 링크 속성을 `final` 로 만드는 것이 좋은 습관입니다.
객체를 처음으로 `put()` 할 때, 링크는 소스 컬렉션과 대상 컬렉션으로 초기화 됩니다. 그 이후, `load()` 와 `save()` 같은 메서드를 호출할 수 있습니다. 링크가 생성된 후 바로 변경 사항을 추적하기 시작하므로 링크가 초기화 되기 전에도 관계를 추가하거나 제거할 수 있습니다.
:::danger
링크를 또 다른 개체로 옮기는 것은 금지되어 있습니다.
:::
================================================
FILE: docs/docs/ko/queries.md
================================================
---
title: 쿼리
---
# 쿼리
쿼리는 특정 조건들에 맞는 레코드들을 찾는 방법입니다. 예:
- 별표로 표시된 모든 연락처를 찾습니다.
- 연락처에서 고유한 이름들을 찾습니다.
- 성이 정의되지 않은 모든 연락처를 삭제합니다.
쿼리는 다트가 아닌 데이터베이스에서 실행되기 때문에 매우 빠릅니다. 인덱스를 똑똑하게 사용하면 쿼리 성능을 더욱 더 향상시킬 수 있습니다. 아래에서는 쿼리를 작성하는 방법과 쿼리를 가능한 한 빨리 작성하는 방법에 대해 알아봅니다.
레코드들을 필터링하는 방법에는 2가지가 있습니다. 필터를 이용하는 방법과 where 절을 이용하는 방법입니다. 먼저 필터 사용법에 대해 알아보겠습니다.
## 필터
필터는 사용하기 쉽고 이해하기 쉽습니다. 속성들의 타입에 따라 다양한 필터 작업이 가능합니다. 필터 작업들은 대부분 알기 쉬운 이름들을 사용합니다.
필터는 필터링할 컬렉션의 모든 객체에 대한 식을 계산해서 작동합니다. 표현식이 `true` 로 결정되면 Isar 는 결과에 객체를 포함합니다. 필터는 결과 순서에 영향을 주지 않습니다.
아래에 나오는 예제들에서는 다음 모델을 사용합니다.
```dart
@collection
class Shoe {
Id? id;
int? size;
late String model;
late bool isUnisex;
}
```
### 쿼리 조건들
필드의 타입에 따라서, 다른 조건들을 사용할 수 있습니다.
| 조건 | 설명 |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------- |
| `.equalTo(value)` | 특정 `value` 와 일치하는 값들. |
| `.between(lower, upper)` | `lower` 와 `upper` 사이에 있는 값들. |
| `.greaterThan(bound)` | `bound` 보다 큰 값들. |
| `.lessThan(bound)` | `bound` 보다 작은 값들. 기본적으로 `null` 값이 사용된다. `null` 은 모든 값들 중에 제일 작은 값으로 간주 되기 때문이다. |
| `.isNull()` | `null` 인 값들. |
| `.isNotNull()` | `null` 이 아닌 값들. |
| `.length()` | List, String, 링크에 있는 요소의 개수를 기반으로 한 길이 쿼리 필터 |
데이터베이스에 크기가 39, 40, 46, `null` 인 신발 4켤레가 있다고 가정해보자.
정렬을 따로 하지 않으면, ID별로 정렬된 값이 반환됩니다.
```dart
isar.shoes.filter()
.sizeLessThan(40)
.findAll() // -> [39, null]
isar.shoes.filter()
.sizeLessThan(40, include: true)
.findAll() // -> [39, null, 40]
isar.shoes.filter()
.sizeBetween(39, 46, includeLower: false)
.findAll() // -> [40, 46]
```
### 논리 연산들
논리 연산자를 이용해서 구문을 합성할 수 있습니다.
| Operator | Description |
| ---------- | ------------------------------------------------------ |
| `.and()` | 양 쪽의 식이 모두 `true` 인 경우 `true` 로 평가됩니다. |
| `.or()` | 한 쪽의 식이라도 `true` 인 경우 `true` 로 평가됩니다. |
| `.xor()` | 정확히 한 쪽의 식이 `true` 라면 `true` 로 평가됩니다. |
| `.not()` | 다음 식이 부정되는 결과를 가져옵니다. |
| `.group()` | 조건을 그룹화하고 평가 순서를 지정할 수 있습니다. |
만약 크기가 46인 모든 신발들을 원한다면, 다음 쿼리를 사용할 수 있습니다:
```dart
final result = await isar.shoes.filter()
.sizeEqualTo(46)
.findAll();
```
하나 이상의 조건이 필요하다면, 논리적 **and** `.and()`, 논리적 **or** `.or()`, 논리적 **xor** `.xor()` 을 이용해서 여러 필터들을 조합하세요.
```dart
final result = await isar.shoes.filter()
.sizeEqualTo(46)
.and() // 선택적으로, 필터들을 논리 and 연산으로 조합합니다.
.isUnisexEqualTo(true)
.findAll();
```
이 쿼리는 다음과 같습니다: `size == 46 && isUnisex == true`.
`group()` 으로 그룹 조건을 사용할 수도 있습니다:
```dart
final result = await isar.shoes.filter()
.sizeBetween(43, 46)
.and()
.group((q) => q
.modelNameContains('Nike')
.or()
.isUnisexEqualTo(false)
)
.findAll()
```
이 쿼리는 `size >= 43 && size <= 46 && (modelName.contains('Nike') || isUnisex == false)` 와 같습니다.
하나의 조건이나 그룹을 부정하려면, 논리적 **부정** 인 `.not()` 을 사용합니다:
```dart
final result = await isar.shoes.filter()
.not().sizeEqualTo(46)
.and()
.not().isUnisexEqualTo(true)
.findAll();
```
이 쿼리는 `size != 46 && isUnisex != true` 와 같습니다.
### 문자열 조건들
위에 있는 쿼리 조건들 말고도, String 값에서는 좀 더 많은 조건들이 제공됩니다. 정규식과 유사한 와일드카드를 사용하면 검색의 유연성을 높일 수 있습니다.
| 조건 | 설명 |
| -------------------- | ---------------------------------------------- |
| `.startsWith(value)` | 주어진 `value` 로 시작하는 문자열 값들. |
| `.contains(value)` | 주어진 `value` 를 포함하는 문자열 값들. |
| `.endsWith(value)` | 주어진 `value` 로 끝나는 문자열 값들. |
| `.matches(wildcard)` | 주어진 `wildcard` 패턴과 일치하는 문자열 값들. |
**대소문자 구분**
모든 문자열 연산은 추가적인 `caseSensitive` 매개변수를 가지고 있습니다.
기본값은 `true`.
**와일드 카드:**
[와일드카드 문자열 표현식](https://ko.wikipedia.org/wiki/%EC%99%80%EC%9D%BC%EB%93%9C%EC%B9%B4%EB%93%9C_%EB%AC%B8%EC%9E%90) 다음 2개의 특수한 와일드카드 문자를 포함한 문자열 입니다.
- 와일드카드 `*` 는 0개 이상의 어떠한 문자열과 대응됩니다.
- 와일드카드 `?` 은 어떠한 문자 하나와 대응됩니다.
예를 들어, 와일드카드 문자열 `"d?g"` 는 `"dog"`, `"dig"`, `"dug"` 와 일치하지만, `"ding"`, `"dg"`, `"a dog"` 와는 일치하지 않습니다.
### 쿼리 수정자 (query modifiers)
경우에 따라서 일부 조건이나 다른 값들을 기준으로 쿼리를 작성해야 할 수도 있습니다. Isar 에는 조건부 쿼리를 작성하기 위한 매우 강력한 도구가 있습니다.
| 수정자 | 설명 |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `.optional(cond, qb)` | `condition` 이 `true` 인 경우에만 쿼리를 확장합니다. 조건부로 정렬하거나 제한하기 위해서 쿼리의 모든 곳에서 사용할 수 있습니다. |
| `.anyOf(list, qb)` | `value` 의 각 값에 대한 쿼리를 확장하고 논리적 **or** 을 사용해서 조건을 결합합니다. |
| `.allOf(list, qb)` | `value` 의 각 값에 대한 쿼리를 확장하고 논리적 **and** 를 사용해서 조건을 결합합니다. |
| |
이 예시에서, 선택적 필터를 사용해서 신발을 찾는 메서드를 만듭니다.
```dart
Future> findShoes(Id? sizeFilter) {
return isar.shoes.filter()
.optional(
sizeFilter != null, // sizeFilter != null 이 아닐 때만 적용됩니다.
(q) => q.sizeEqualTo(sizeFilter!),
).findAll();
}
```
여러 신발 크기 중 하나를 가진 모든 신발을 찾으려면, 일반적인 쿼리를 작성하거나 `anyOf()` 수정자를 사용할 수 있습니다:
```dart
final shoes1 = await isar.shoes.filter()
.sizeEqualTo(38)
.or()
.sizeEqualTo(40)
.or()
.sizeEqualTo(42)
.findAll();
final shoes2 = await isar.shoes.filter()
.anyOf(
[38, 40, 42],
(q, int size) => q.sizeEqualTo(size)
).findAll();
// shoes1 == shoes2
```
쿼리 수정자는 동적 쿼리를 작성할 때 특히 유용합니다.
### 리스트
심지어 리스트를 쿼리할 수도 있습니다:
```dart
class Tweet {
Id? id;
String? text;
List hashtags = [];
}
```
리스트의 길이에 대해서 쿼리할 수 있습니다.
```dart
final tweetsWithoutHashtags = await isar.tweets.filter()
.hashtagsIsEmpty()
.findAll();
final tweetsWithManyHashtags = await isar.tweets.filter()
.hashtagsLengthGreaterThan(5)
.findAll();
```
다트 코드로 `tweets.where((t) => t.hashtags.isEmpty);` 와 `tweets.where((t) => t.hashtags.length > 5);` 같습니다. 리스트 요소에 대해서 쿼리할 수 있습니다.
```dart
final flutterTweets = await isar.tweets.filter()
.hashtagsElementEqualTo('flutter')
.findAll();
```
다트 코드로 `tweets.where((t) => t.hashtags.contains('flutter'));` 와 같습니다.
### 임베드된 객체들
임베드된 객체는 Isar 의 가장 유용한 기능 중 하나 입니다. 최상위 객체와 동일한 조건을 사용하여 매우 효율적으로 쿼리할 수 있습니다. 다음과 같은 모델이 있다고 가정합시다:
```dart
@collection
class Car {
Id? id;
Brand? brand;
}
@embedded
class Brand {
String? name;
String? country;
}
```
`BMW` 라는 브랜드와 `"Germany"` 라는 나라를 갖는 모든 차들을 쿼리하고 싶습니다. 다음 쿼리를 사용해서 이 작업을 수행할 수 있습니다.
```dart
final germanCars = await isar.cars.filter()
.brand((q) => q
.nameEqualTo('BMW')
.and()
.countryEqualTo('Germany')
).findAll();
```
항상 중첩된 쿼리들을 그룹화하세요. 위의 쿼리가 다음 쿼리보다 효율적입니다. 결과는 같겠지만요:
```dart
final germanCars = await isar.cars.filter()
.brand((q) => q.nameEqualTo('BMW'))
.and()
.brand((q) => q.countryEqualTo('Germany'))
.findAll();
```
### 링크(Link)
모델이 [링크와 백링크](links) 를 포함하고 있다면 연결된 객체 또는 연결된 객체 수를 기준으로 쿼리를 필터링 할 수 있습니다.
:::warning
Isar 는 링크된 객체를 조회해야 하므로 링크 쿼리의 비용은 비쌀 수 있습니다. 대신 임베드된 객체를 사용하는 것을 고려해 보십시오.
:::
```dart
@collection
class Teacher {
Id? id;
late String subject;
}
@collection
class Student {
Id? id;
late String name;
final teachers = IsarLinks();
}
```
수학이나 영어 선생님이 있는 모든 학생을 찾습니다:
```dart
final result = await isar.students.filter()
.teachers((q) {
return q.subjectEqualTo('Math')
.or()
.subjectEqualTo('English');
}).findAll();
```
링크 필터는 하나 이상의 연결된 객체가 조건과 일치하면 `true` 로 평가합니다.
선생님이 없는 모든 학생을 찾아봅시다:
```dart
final result = await isar.students.filter().teachersLengthEqualTo(0).findAll();
```
또는 이렇게 할 수 있습니다:
```dart
final result = await isar.students.filter().teachersIsEmpty().findAll();
```
## Where 절
Where 절은 매우 강력한 도구이지만, 제대로 이해하는 것은 약간 어렵습니다.
filter 와 달리 where 절은 쿼리 조건을 검사하기 위해서 스키마에서 정의된 index 들을 사용합니다. 인덱스를 쿼리하는 것이 레코드 각각을 필터링하는 것보다 훨씬 빠릅니다.
➡️ 더 알아보기: [인덱스](indexes)
:::팁
기본적으로 where 절을 사용해서 레코드를 최대한 줄이고 나머지에 대해 필터링을 수행해야 합니다.
:::
논리적 **or** 을 사용하여 where 절만 결합할 수 있습니다. 즉, 여러 where 절들의 합집합을 구할 수는 있지만, 여러 where 절들의 교집합을 쿼리할 수 는 없습니다.
신발 컬렉션에 인덱스를 추가합니다:
```dart
@collection
class Shoe with IsarObject {
Id? id;
@Index()
Id? size;
late String model;
@Index(composite: [CompositeIndex('size')])
late bool isUnisex;
}
```
두 개의 인덱스가 있습니다. `size` 의 인덱스를 사용하면 `.sizeEqualTo()` 와 같은 절을 사용할 수 있습니다. `isUnisex` 의 합성 인덱스는 `isUnisexSizeEqualTo()` 와 같은 where 절을 가능하게 합니다. 하지만 인덱스의 접두사를 항상 사용할 수 있기 때문에 `isUnisexEqualTo()` 도 허용됩니다.
우리는 복합 인덱스를 사용해서 46사이즈의 남녀공용 신발을 찾는 이전의 쿼리를 다시 작성할 수 있습니다. 이 쿼리는 이전 쿼리보다 훨씬 빨라집니다:
```dart
final result = isar.shoes.where()
.isUnisexSizeEqualTo(true, 46)
.findAll();
```
where 절은 2개의 초능력을 더 가지고 있습니다: "무료" 정렬과 초고속 구별(distinct) 작업을 제공합니다.
### where 절과 filter 결합하기
`shoes.filter()` 쿼리가 기억나죠? 그건 사실 `shoes.where().filter()` 의 줄임 표현입니다. 양 쪽의 장점들을 사용하기 위해서 하나의 쿼리 안에서 where 절과 filter 를 결합할 수 있습니다.
```dart
final result = isar.shoes.where()
.isUnisexEqualTo(true)
.filter()
.modelContains('Nike')
.findAll();
```
필터링할 개체 수를 줄이기 위해서 where 절이 먼저 적용됩니다. 남은 객체들에 필터가 적용됩니다.
The where clause is applied first to reduce the number of objects to be filtered. Then the filter is applied to the remaining objects.
## 정렬
`.sortBy()`, `.sortByDesc()`, `.thenBy()` 및 `.thenByDesc()` 메서드를 사용해서 쿼리를 실행할 때 결과를 정렬하는 방법을 정의합니다.
인덱스를 사용하지 않고 모델 이름 기준으로 오름차순, 크기 기준으로 내림차순 정렬된 모든 신발을 찾으려면 이렇게 합니다.
```dart
final sortedShoes = isar.shoes.filter()
.sortByModel()
.thenBySizeDesc()
.findAll();
```
특히 정렬은 오프셋과 제한 이전에 실행되기 때문에, 많은 결과를 정렬하는 것은 비용이 많이 듭니다. 위의 정렬 방법은 인덱스를 사용하지 않습니다. 다행히, 우리는 where 절 정렬을 다시 사용할 수 있고 백만 개의 객체를 정렬하는 경우에도 번개처럼 빠르게 수행할 수 있습니다.
### where 절 정렬
쿼리에 **단일** where 절을 사용하는 경우 결과가 이미 인덱스 기준으로 정렬되어 있습니다. 정말 큰일입니다!
신발의 크기가 `[43, 39, 48, 40, 42, 45]` 이고 `42` 보다 큰 모든 신발을 찾고 크기별로 정렬한다고 가정해 보겠습니다.
```dart
final bigShoes = isar.shoes.where()
.sizeGreaterThan(42) // 크기 기준으로 정렬까지 됩니다.
.findAll(); // -> [43, 45, 48]
```
결과는 기본적으로 `size` 인덱스 기준으로 정렬됩니다. where 절의 정렬 순서를 반대로 하려면 `sort` 를 `Sort.desc` 로 설정하면 됩니다:
```dart
final bigShoesDesc = await isar.shoes.where(sort: Sort.desc)
.sizeGreaterThan(42)
.findAll(); // -> [48, 45, 43]
```
가끔 where 절을 이용하지 않지만 암시적인 정렬을 원하는 경우가 있습니다. `any` where 절을 사용하면 됩니다.
```dart
final shoes = await isar.shoes.where()
.anySize()
.findAll(); // -> [39, 40, 42, 43, 45, 48]
```
복합 인덱스를 사용하는 경우, 인덱스의 모든 필드 별로 결과가 정렬됩니다.
:::tip
결과를 정렬해야 하는 경우 인덱스를 사용하는 게 좋습니다. 특히 `offset()` 과 `limit()` 를 사용하여 작업하는 경우에는 더욱 그렇습니다.
:::
인덱스를 사용해서 정렬할 수 없거나 유용하지 않은 경우가 있습니다. 이러한 경우 인덱스를 사용하여 결과 항목 수를 최대한 줄여야 합니다.
## 고유한 값들 (Unique values)
고유한 값들로만 이루어진 항목들을 반환하려면 distinct 술어를 사용하세요. 예를 들어, Isar 데이터베이스에 있는 신발 모델의 수를 확인하려면 다음과 같이 하세요.
```dart
final shoes = await isar.shoes.filter()
.distinctByModel()
.findAll();
```
여러 개의 개별 조건들을 체인으로 연결해서 모델 크기 조합이 다른 모든 신발을 찾을 수 있습니다.
```dart
final shoes = await isar.shoes.filter()
.distinctByModel()
.distinctBySize()
.findAll();
```
각 고유한 조합의 첫 번째 결과만 반환됩니다. where 절 및 정렬 작업을 사용하여 이를 제어할 수 있습니다.
### Where 절 구분 (Where clause distinct)
고유하지 않은 인덱스가 있는 경우, 구분된 값들을 모두 가져올 수 있습니다. 이전 섹션의 `distinctBy` 연산을 사용할 수 있지만, 정렬 및 필터 이후에 실행되므로 오버헤드가 있습니다. 단일 where 절만 사용하는 경우 인덱스를 사용하여 구분 작업을 수행할 수 있습니다.
```dart
final shoes = await isar.shoes.where(distinct: true)
.anySize()
.findAll();
```
:::tip
이론적으로는 정렬 및 구분을 위해서 여러 개의 where 절을 사용하 수 있습니다. 유일한 제약은 where 절이 중복되지 않고 동일한 인덱스를 사용하는 것입니다. 올바른 정렬을 위해서는 정렬 순서로 적용해야 합니다. 이것에 의존하는 것은 매우 조심하세요!
In theory, you could even use multiple where clauses for sorting and distinct. The only restriction is that those where clauses are not overlapping and use the same index. For correct sorting, they also need to be applied in sort order. Be very careful if you rely on this!
:::
## 오프셋과 제한(Offset & Limit)
lazy 리스트 뷰를 위해서 쿼리 결과를 제한하는 것이 좋습니다. 다음과 같이 `limit()` 를 설정해서 할 수 있습니다.
```dart
final firstTenShoes = await isar.shoes.where()
.limit(10)
.findAll();
```
`offset()` 을 이용해서 쿼리를 페이징할 수 있습니다.
By setting an `offset()` you can also paginate the results of your query.
```dart
final firstTenShoes = await isar.shoes.where()
.offset(20)
.limit(10)
.findAll();
```
Dart 객체를 인스턴스화하는 것은 보통 쿼리 실행에서 비용이 가장 많이 드는 부분이기 때문에, 필요한 객체만 불러오는 것이 좋습니다.
## 실행 순서
Isar 는 항상 다음 순서로 쿼리들을 실행합니다.
1. 주 또는 보조 인덱스를 순회하면서 객체를 찾습니다. (where 절 적용)
2. Filter
3. 정렬
4. 구분 연산
5. 오프셋 & 제한
6. 결과 반환
## 쿼리 연산들
이전 예제들에서 일치하는 모든 객체들을 검색하기 위해서 `.findAll()` 을 사용했습니다. 그러나 더 많은 연산을 사용할 수 있습니다.
| 연산 | 설명 |
| ---------------- | -------------------------------------------------------------------------------------------------------------------- |
| `.findFirst()` | 일치하는 첫 객체 또는 일치하는 것이 없는 경우 `null` 을 반환합니다. |
| `.findAll()` | 일치하는 모든 객체들을 검색합니다. |
| `.count()` | 쿼리와 일치하는 객체의 수를 셉니다. |
| `.deleteFirst()` | 컬렉션에서 일치하는 첫 객체를 제거합니다. |
| `.deleteAll()` | 컬렉션에서 일치하는 모든 객체를 제거합니다. |
| `.build()` | 쿼리를 나중에 사용하기 위해 컴파일 합니다. 이렇게 하면 쿼리를 여러 번 실행하는 경우 쿼리를 만드는 비용이 절약됩니다. |
## 속성 쿼리 (Property queries)
단일 속성 값에만 관심이 있는 경우 속성 쿼리를 사용하세요. 일반 쿼리를 만들고 속성을 선택하세요:
```dart
List models = await isar.shoes.where()
.modelProperty()
.findAll();
List sizes = await isar.shoes.where()
.sizeProperty()
.findAll();
```
단일 속성만 이용하면 역직렬화에 걸리는 시간을 절약할 수 있습니다. 속성 쿼리는 임베드된 객체와 리스트에도 사용할 수 있습니다.
## 집계 (Aggregation)
Isar 에서는 속성 쿼리의 값을 집계할 수 있습니다. 다음 집계 연산이 가능합니다.
| 연산 | 설명 |
| ------------ | --------------------------------------------------------------------------- |
| `.min()` | 최소값 또는 일치하는 것이 없는 경우 `null` 을 반환합니다. |
| `.max()` | 최대값 또는 일치하는 것이 없는 경우 `null` 을 반환합니다. |
| `.sum()` | 모든 값들을 더합니다. |
| `.average()` | 모든 값들의 평균을 계산합니다. 일치하는 값이 없는 경우 `NaN` 을 반환합니다. |
집계를 사용하는 것이 일치하는 모든 객체를 찾은 다음 집계를 수동으로 하는 것보다 훨씬 빠릅니다.
## 동적 쿼리
:::danger
이 섹션은 대부분 사용자와는 관련이 없습니다. 반드시 필요한 경우(거의 그럴 일은 없습니다.)가 아니면 동적 쿼리를 사용하지 않는 것이 좋습니다.
:::
위의 모든 예시에서 QueryBuilder와 생성된 정적 확장 메서드들을 사용했습니다. 동적 쿼리 또는 사용자 지정 쿼리 언어 (Isar Inspector 같은) 를 만들 수 있습니다. 이 경우 `buildQuery()` 메서드를 사용할 수 있습니다.
| 매개변수 | 설명 |
| --------------- | ------------------------------------------------------------------------------ |
| `whereClauses` | 이 쿼리의 where 절들 입니다. |
| `whereDistinct` | where 절이 구분된 값을 반환해야 하는 지 여부입니다. (단일 where 절에만 유효함) |
| `whereSort` | where 절의 순회 순서 입니다. (단일 where 절에만 유효함) |
| `filter` | 결과에 적용할 필터입니다. |
| `sortBy` | 정렬의 기준으로 사용할 속성의 리스트입니다. |
| `distinctBy` | 구분할 속성 리스트 입니다. |
| `offset` | 결과의 오프셋 입니다. |
| `limit` | 반환할 결과의 최대 개수입니다. |
| `property` | null이 아닌 경우 이 속성의 값만 반환됩니다. |
동적 쿼리를 만들어 봅시다:
```dart
final shoes = await isar.shoes.buildQuery(
whereClauses: [
WhereClause(
indexName: 'size',
lower: [42],
includeLower: true,
upper: [46],
includeUpper: true,
)
],
filter: FilterGroup.and([
FilterCondition(
type: ConditionType.contains,
property: 'model',
value: 'nike',
caseSensitive: false,
),
FilterGroup.not(
FilterCondition(
type: ConditionType.contains,
property: 'model',
value: 'adidas',
caseSensitive: false,
),
),
]),
sortBy: [
SortProperty(
property: 'model',
sort: Sort.desc,
)
],
offset: 10,
limit: 10,
).findAll();
```
다음 쿼리와 동일합니다:
```dart
final shoes = await isar.shoes.where()
.sizeBetween(42, 46)
.filter()
.modelContains('nike', caseSensitive: false)
.not()
.modelContains('adidas', caseSensitive: false)
.sortByModelDesc()
.offset(10).limit(10)
.findAll();
```
================================================
FILE: docs/docs/ko/recipes/data_migration.md
================================================
---
title: 데이터 마이그레이션 (Data migration)
---
# 데이터 마이그레이션
Isar 는 컬렉션, 속성, 인덱스를 추가하거나 삭제하면 데이터베이스 스키마를 자동으로 마이그레이션합니다. 가끔은 데이터도 마이그레이션해야 할 수 있습니다. Isar 는 임의 마이그레이션 제한을 적용하기 때문에 기본 제공 솔루션을 제공하지는 않습니다. 사용자의 요구사항에 맞는 마이그레이션 로직을 쉽게 구현할 수 있습니다.
이 예시에서는 전체 데이터베이스에서 하나의 버전을 이용하려고 합니다. shared preferences 를 사용해서 현재 버전을 저장하고 마이그레이션하려는 버전과 비교합니다. 버전이 일치하지 않으면 데이터를 마이그레이션하고 버전을 업데이트 합니다.
:::tip
각 컬렉션에 자체 버전을 지정하고 개별적으로 마이그레이션 할 수 있습니다.
:::
생일 필드가 있는 사용자 컬렉션이 있다고 상상해 보십시오. 우리 앱의 버전 2에서는 나이를 기준으로 사용자를 조회할 수 있는 추가 출생 연도 필드가 필요합니다.
버전 1:
```dart
@collection
class User {
Id? id;
late String name;
late DateTime birthday;
}
```
버전 2:
```dart
@collection
class User {
Id? id;
late String name;
late DateTime birthday;
short get birthYear => birthday.year;
}
```
문제는 기존에 있던 사용자 모델들은 버전 1에서 `birthYear` 가 없었기 때문에 비어있는 `birthYear` 를 가지게 된다는 것입니다. 우리는 `birthYear` 필드를 설정하기 위해서 데이터를 마이그레이션 해야 합니다.
```dart
import 'package:isar/isar.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() async {
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[UserSchema],
directory: dir.path,
);
await performMigrationIfNeeded(isar);
runApp(MyApp(isar: isar));
}
Future performMigrationIfNeeded(Isar isar) async {
final prefs = await SharedPreferences.getInstance();
final currentVersion = prefs.getInt('version') ?? 2;
switch(currentVersion) {
case 1:
await migrateV1ToV2(isar);
break;
case 2:
// 버전이 설정되지 않았거나(새로 설치한 경우), 이미 2인 경우 마이그레이션할 필요가 없습니다.
return;
default:
throw Exception('Unknown version: $currentVersion');
}
// 버전 업데이트
await prefs.setInt('version', 2);
}
Future migrateV1ToV2(Isar isar) async {
final userCount = await isar.users.count();
// 모든 사용자를 한 번에 메모리에 로드하지 않도록 사용자를 페이지 분할합니다.
for (var i = 0; i < userCount; i += 50) {
final users = await isar.users.where().offset(i).limit(50).findAll();
await isar.writeTxn((isar) async {
// 생년월일 게터를 사용하기 때문에 업데이트할 필요가 없습니다.
await isar.users.putAll(users);
});
}
}
```
:::warning
많은 데이터를 마이그레이션 해야 하는 경우 UI 스레드에 부담이 가지 않도록 백그라운드 isolate 를 사용하는 것이 좋습니다.
:::
================================================
FILE: docs/docs/ko/recipes/full_text_search.md
================================================
---
title: Full-text search
---
# Full-text search
Full-text search is a powerful way to search text in the database. You should already be familiar with how [indexes](/indexes) work, but let's go over the basics.
An index works like a lookup table, allowing the query engine to find records with a given value quickly. For example, if you have a `title` field in your object, you can create an index on that field to make it faster to find objects with a given title.
## Why is full-text search useful?
You can easily search text using filters. There are various string operations for example `.startsWith()`, `.contains()` and `.matches()`. The problem with filters is that their runtime is `O(n)` where `n` is the number of records in the collection. String operations like `.matches()` are especially expensive.
:::tip
Full-text search is much faster than filters, but indexes have some limitations. In this recipe, we will explore how to work around these limitations.
:::
## Basic example
The idea is always the same: Instead of indexing the whole text, we index the words in the text so we can search for them individually.
Let's create the most basic full-text index:
```dart
class Message {
Id? id;
late String content;
@Index()
List get contentWords => content.split(' ');
}
```
We can now search for messages with specific words in the content:
```dart
final posts = await isar.messages
.where()
.contentWordsAnyEqualTo('hello')
.findAll();
```
This query is super fast, but there are some problems:
1. We can only search for entire words
2. We do not consider punctuation
3. We do not support other whitespace characters
## Splitting text the right way
Let's try to improve the previous example. We could try to develop a complicated regex to fix word splitting, but it will likely be slow and wrong for edge cases.
The [Unicode Annex #29](https://unicode.org/reports/tr29/) defines how to split text into words correctly for almost all languages. It is quite complicated, but fortunately, Isar does the heavy lifting for us:
```dart
Isar.splitWords('hello world'); // -> ['hello', 'world']
Isar.splitWords('The quick (“brown”) fox can’t jump 32.3 feet, right?');
// -> ['The', 'quick', 'brown', 'fox', 'can’t', 'jump', '32.3', 'feet', 'right']
```
## I want more control
Easy peasy! We can change our index also to support prefix matching and case-insensitive matching:
```dart
class Post {
Id? id;
late String title;
@Index(type: IndexType.value, caseSensitive: false)
List get titleWords => title.split(' ');
}
```
By default, Isar will store the words as hashed values which is fast and space efficient. But hashes can't be used for prefix matching. Using `IndexType.value`, we can change the index to use the words directly instead. It gives us the `.titleWordsAnyStartsWith()` where clause:
```dart
final posts = await isar.posts
.where()
.titleWordsAnyStartsWith('hel')
.or()
.titleWordsAnyStartsWith('welco')
.or()
.titleWordsAnyStartsWith('howd')
.findAll();
```
## I also need `.endsWith()`
Sure thing! We will use a trick to achieve `.endsWith()` matching:
```dart
class Post {
Id? id;
late String title;
@Index(type: IndexType.value, caseSensitive: false)
List get revTitleWords {
return Isar.splitWords(title).map(
(word) => word.reversed).toList()
);
}
}
```
Don't forget reversing the ending you want to search for:
```dart
final posts = await isar.posts
.where()
.revTitleWordsAnyStartsWith('lcome'.reversed)
.findAll();
```
## Stemming algorithms
Unfortunately, indexes do not support `.contains()` matching (this is true for other databases as well). But there are a few alternatives that are worth exploring. The choice highly depends on your use. One example is indexing word stems instead of the whole word.
A stemming algorithm is a process of linguistic normalization in which the variant forms of a word are reduced to a common form:
```
connection
connections
connective ---> connect
connected
connecting
```
Popular algorithms are the [Porter stemming algorithm](https://tartarus.org/martin/PorterStemmer/) and the [Snowball stemming algorithms](https://snowballstem.org/algorithms/).
There are also more advanced forms like [lemmatization](https://en.wikipedia.org/wiki/Lemmatisation).
## Phonetic algorithms
A [phonetic algorithm](https://en.wikipedia.org/wiki/Phonetic_algorithm) is an algorithm for indexing words by their pronunciation. In other words, it allows you to find words that sound similar to the ones you are looking for.
:::warning
Most phonetic algorithms only support a single language.
:::
### Soundex
[Soundex](https://en.wikipedia.org/wiki/Soundex) is a phonetic algorithm for indexing names by sound, as pronounced in English. The goal is for homophones to be encoded to the same representation so they can be matched despite minor differences in spelling. It is a straightforward algorithm, and there are multiple improved versions.
Using this algorithm, both `"Robert"` and `"Rupert"` return the string `"R163"` while `"Rubin"` yields `"R150"`. `"Ashcraft"` and `"Ashcroft"` both yield `"A261"`.
### Double Metaphone
The [Double Metaphone](https://en.wikipedia.org/wiki/Metaphone) phonetic encoding algorithm is the second generation of this algorithm. It makes several fundamental design improvements over the original Metaphone algorithm.
Double Metaphone accounts for various irregularities in English of Slavic, Germanic, Celtic, Greek, French, Italian, Spanish, Chinese, and other origins.
================================================
FILE: docs/docs/ko/recipes/multi_isolate.md
================================================
---
title: 다중-Isolate 사용법
---
# 다중-Isolate 사용법
스레드 대신, 모든 다트 코드는 isolate 안에서 돌아갑니다. 각 isolate 에는 고유한 메모리 힙이 있으므로, isolate 의 어떤 상태도 다른 isolate 에서 접근할 수 없습니다.
Isar can be accessed from multiple isolates at the same time, and even watchers work across isolates. In this recipe, we will check out how to use Isar in a multi-isolate environment.
## When to use multiple isolates
Isar transactions are executed in parallel even if they run in the same isolate. In some cases, it is still beneficial to access Isar from multiple isolates.
The reason is that Isar spends quite some time encoding and decoding data from and to Dart objects. You can think of it as encoding and decoding JSON (just more efficient). These operations run inside the isolate from which the data is accessed and naturally block other code in the isolate. In other words: Isar performs some of the work in your Dart isolate.
If you only need to read or write a few hundred objects at once, doing it in the UI isolate is not a problem. But for huge transactions or if the UI thread is already busy, you should consider using a separate isolate.
## Example
The first thing we need to do is to open Isar in the new isolate. Since the instance of Isar is already open in the main isolate, `Isar.open()` will return the same instance.
:::warning
Make sure to provide the same schemas as in the main isolate. Otherwise, you will get an error.
:::
`compute()` starts a new isolate in Flutter and runs the given function in it.
```dart
void main() {
// Open Isar in the UI isolate
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[MessageSchema],
directory: dir.path,
name: 'myInstance',
);
// listen to changes in the database
isar.messages.watchLazy(() {
print('omg the messages changed!');
});
// start a new isolate and create 10000 messages
compute(createDummyMessages, 10000).then(() {
print('isolate finished');
});
// after some time:
// > omg the messages changed!
// > isolate finished
}
// function that will be executed in the new isolate
Future createDummyMessages(int count) async {
// we don't need the path here because the instance is already open
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[PostSchema],
directory: dir.path,
name: 'myInstance',
);
final messages = List.generate(count, (i) => Message()..content = 'Message $i');
// we use a synchronous transactions in isolates
isar.writeTxnSync(() {
isar.messages.insertAllSync(messages);
});
}
```
There are a few interesting things to note in the example above:
- `isar.messages.watchLazy()` is called in the UI isolate and is notified of changes from another isolate.
- Instances are referenced by name. The default name is `default`, but in this example, we set it to `myInstance`.
- We used a synchronous transaction to create the mesasges. Blocking our new isolate is no problem, and synchronous transactions are a little faster.
================================================
FILE: docs/docs/ko/recipes/string_ids.md
================================================
---
title: String ids
---
# String ids
This is one of the most frequent requests I get, so here is a tutorial on using String ids.
Isar does not natively support String ids, and there is a good reason for it: integer ids are much more efficient and faster. Especially for links, the overhead of a String id is too significant.
I understand that sometimes you have to store external data that uses UUIDs or other non-integer ids. I recommend storing the String id as a property in your object and using a fast hash implementation to generate a 64-bit int that can be used as Id.
```dart
@collection
class User {
String? id;
Id get isarId => fastHash(id!);
String? name;
int? age;
}
```
With this approach, you get the best of both worlds: Efficient integer ids for links and the ability to use String ids.
## Fast hash function
Ideally, your hash function should have high quality (you don't want collisions) and be fast. I recommend using the following implementation:
```dart
/// FNV-1a 64bit hash algorithm optimized for Dart Strings
int fastHash(String string) {
var hash = 0xcbf29ce484222325;
var i = 0;
while (i < string.length) {
final codeUnit = string.codeUnitAt(i++);
hash ^= codeUnit >> 8;
hash *= 0x100000001b3;
hash ^= codeUnit & 0xFF;
hash *= 0x100000001b3;
}
return hash;
}
```
If you choose a different hash function, ensure it returns a 64-bit int and avoid using a cryptographic hash function because they are much slower.
:::warning
Avoid using `string.hashCode` because it is not guaranteed to be stable across different platforms and versions of Dart.
:::
================================================
FILE: docs/docs/ko/schema.md
================================================
---
title: 스키마
---
# 스키마
앱의 데이터를 저장하기 위해 Isar를 사용할 때마다, 컬렉션을 이용하게 됩니다. 컬렉션은 연관된 Isar 데이터베이스의 데이터베이스 테이블과 같고, 하나의 다트 객체 타입만을 포함할 수 있습니다. 각 컬렉션 객체는 해당 컬렉션의 데이터 행을 나타냅니다.
컬렉션의 정의를 "스키마" 라고 합니다. Isar Generator 가 힘든 일을 대신 해주고, 컬렉션을 사용하기 위해 필요한 대부분의 코드를 생성해줍니다.
## 컬렉션의 구조
클래스에 `@collection` 또는 `@Collection()` 어노테이션을 붙여서 Isar 컬렉션을 정의합니다. 필드들을 포함하는 하나의 Isar 컬렉션은 데이터베이스의 해당하는 테이블에 있는 각각의 열과 같으며, 여기에는 기본 키를 구성하는 하나의 필드도 포함됩니다.
아래 코드는 `User` 테이블을 정의하는 하나의 컬렉션 예시를 보여줍니다. 테이블에는 ID, 성, 이름에 해당하는 열이 포합됩니다.
```dart
@collection
class User {
Id? id;
String? firstName;
String? lastName;
}
```
:::tip
필드를 저장하기 위해서 Isar 가 반드시 필드에 접근해야 합니다. 필드를 public 으로 만들거나 게터와 세터를 제공해서 Isar가 접근할 수 있도록 만들어야 됩니다.
:::
컬렉션을 커스터마이징할 수 있는 몇 가지 선택적인 매개변수들이 있습니다.
| Config | Description |
| ------------- | ---------------------------------------------------------------------------------------------------------------- |
| `inheritance` | Isar 에 부모 클래스들과 믹스인에 있는 필드를 저장할 지를 조정합니다. 기본적으로 활성화되어 있습니다. |
| `accessor` | 기본 컬렉션 접근자의 이름을 바꿀 수 있게 해줍니다. (예: `Contact` 컬렉션에 사용되는 `isar.contacts`) |
| `ignore` | 특정 속성을 제외할 수 있습니다. 이것은 상위 클래스에도 동일하게 적용될 수 있습니다. |
### Isar 의 Id
각 컬렉션 클래스는 객체를 고유하게 식별하는 `Id` 타입으로 id 속성을 정의해야 합니다.
`Id` 는 Isar Generator 가 id 속성을 인식할 수 있도록 하는 int 의 별칭일 뿐입니다.
Isar 는 id 필드를 자동으로 인덱싱합니다. id 를 기반으로 객체를 효율적으로 가져오고 수정할 수 있습니다.
사용자가 직접 id를 설정하거나 Isar 에 자동 증분 id를 할당하도록 요청할 수 있습니다. 만약 `id` 필드가 `null` 이고 `final` 이 아니라면 Isar 는 자동 증분 id 를 할당합니다. null 이 아닌 자동 증분 id를 원하는 경우에는 `Isar.autoincrement` 를 사용할 수 있습니다.
::tip
자동 증분 아이디들은 해당 객체가 삭제되어도 다시 사용할 수 없습니다. 자동 증분 id를 초기화하는 유일할 방법은 데이터베이스를 지우는 것 뿐입니다.
:::
### 컬렉션과 필드의 이름 바꾸기
기본적으로 Isar는 클래스 이름을 컬렉션 이름으로 사용합니다. 마찬가지로 Isar는 필드 이름을 데이터베이스 열 이름으로 사용합니다. 컬렉션이나 필드에 다른 이름을 붙이고 싶다면 `@Name` 어노테이션을 추가합니다. 다음 코드는 컬렉션과 필드의 이름을 바꾸는 예입니다:
```dart
@collection
@Name("User")
class MyUserClass1 {
@Name("id")
Id myObjectId;
@Name("firstName")
String theFirstName;
@Name("lastName")
String familyNameOrWhatever;
}
```
특히, 이미 데이터베이스에 저장되어 있는 Dart 의 필드나 클래스의 이름을 변경하고 싶다면, `@Name` 어노테이션의 사용을 검토해야 합니다. 그렇지 않으면 데이터베이스가 해당 필드나 컬렉션을 삭제하거나 재작성하게 될 수 있습니다.
### 필드 무시하기
Isar 는 컬렉션 클래스의 모든 public 필드를 저장합니다. 속성이나 게터에 `@ignore` 어노테이션을 붙여서 저장하지 않을 수 있습니다. 다음 코드 조각을 보세요.
```dart
@collection
class User {
Id? id;
String? firstName;
String? lastName;
@ignore
String? password;
}
```
컬렉션이 부모 컬렉션에서 필드를 상속하는 경우 일반적으로 `@Collection` 어노테이션의 ignore 속성을 사용하는 것이 더 쉽습니다.
```dart
@collection
class User {
Image? profilePicture;
}
@Collection(ignore: {'profilePicture'})
class Member extends User {
Id? id;
String? firstName;
String? lastName;
}
```
만약 컬렉션에 Isar가 지원하지 않는 유형의 필드가 포함되어 있다면 해당 필드는 무시해야 합니다.
:::warning
저장되지 않은 Isar 객체에 정보를 저장하는 것은 좋지 않습니다.
:::
## 지원하는 타입 목록
Isar 는 아래의 데이터 타입들을 지원합니다:
- `bool`
- `byte`
- `short`
- `int`
- `float`
- `double`
- `DateTime`
- `String`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
- `List`
추가적으로 임베드된 객체와 enum 도 지원합니다. 이것들은 아래에서 다룰 것입니다.
## byte, short, float
대부분의 경우 64비트 정수형이나 double 의 전체 범위는 필요하지 않습니다. Isar는 더 작은 수치를 저장할 때를 위해서 용량과 메모리를 절약할 수 있는 추가 유형을 지원합니다.
| Type | Size in bytes | Range |
| ---------- | ------------- | ------------------------------------------------------- |
| **byte** | 1 | 0 to 255 |
| **short** | 4 | -2,147,483,647 to 2,147,483,647 |
| **int** | 8 | -9,223,372,036,854,775,807 to 9,223,372,036,854,775,807 |
| **float** | 4 | -3.4e38 to 3.4e38 |
| **double** | 8 | -1.7e308 to 1.7e308 |
추가적인 숫자 타입들은 native 다트 타입들의 별칭일 뿐입니다. 예를 들어 `short` 를 사용해도 `int` 를 사용하는 것과 같이 동작합니다.
아래에 위의 모든 유형을 포함하는 컬렉션의 예를 보여줍니다.
```dart
@collection
class TestCollection {
Id? id;
late byte byteValue;
short? shortValue;
int? intValue;
float? floatValue;
double? doubleValue;
}
```
모든 숫자 유형은 리스트로 사용할 수도 있습니다. 바이트들을 저장하려면 `List` 를 사용해야 합니다.
## 널이 허용되는 타입들
Isar 에서 nullability(DB 테이블의 열 항목이 NULL 값을 가질 수 있는지 없는지)가 어떻게 작동하는지 이해하는 것은 필수입니다. 숫자 타입들은 `null` 이라는 값을 별도로 가지지 **않습니다**. 대신, 특수한 값이 사용됩니다.
| Type | VM |
| ---------- | ------------- |
| **short** | `-2147483648` |
| **int** | `int.MIN` |
| **float** | `double.NaN` |
| **double** | `double.NaN` |
`bool`, `String`, `List` 는 별도의 `null` 표현을 사용합니다.
이런 동작을 통해서 성능이 향상되고, `null` 값을 처리하기 위한 마이그레이션이나 특별한 코드 없이도 필드의 nullability 를 자유롭게 변경할 수 있게 됩니다.
:::warning
`byte` 타입은 널 값을 지원하지 않습니다.
:::
## DateTime
Isar 는 날짜의 표준 시간대 정보를 저장하지 않습니다. 대신 `DateTime`을 UTC로 변환해서 저장합니다. Isar 는 모든 날짜를 현지 시간으로 반환합니다.
`DateTime`은 마이크로초 단위로 저장됩니다. 브라우저에서는 자바스크립트의 제한 때문에 밀리초 단위의 정밀도만 지원됩니다.
## Enum
Isar는 다른 Isar 타입들 처럼 열거형을 저장하고 사용할 수 있습니다. 하지만 Isar가 디스크의 열거형을 어떻게 나타낼지 선택해야 합니다. Isar 는 4가지의 전략을 지원합니다.
| EnumType | Description |
| ----------- | --------------------------------------------------------------------------------------------------- |
| `ordinal` | 열거형의 인덱스는 `byte` 로 저장됩니다. 이것은 매우 효율적이지만 널이 가능한 열거형에서는 허용하지 않습니다 |
| `ordinal32` | 열거형의 인덱스는 `short`(4바이트 정수)로 저장됩니다. |
| `name` | 열거형 이름은 `String` 으로 저장됩니다. |
| `value` | 사용자 지정 속성을 사용하여 열거형을 검색합니다 |
:::warning
`ordinal`과 `ordinal32` 는 열거값의 순서에 따라 달라집니다. 순서를 변경하는 경우, 기존 데이터베이스는 잘못된 값을 반환합니다.
:::
각각의 전략을 사용하는 예시를 확인하세요.
```dart
@collection
class EnumCollection {
Id? id;
@enumerated // EnumType.ordinal 과 같습니다.
late TestEnum byteIndex; // 널이 될 수 없습니다.
@Enumerated(EnumType.ordinal)
late TestEnum byteIndex2; // 널이 될 수 없습니다.
@Enumerated(EnumType.ordinal32)
TestEnum? shortIndex;
@Enumerated(EnumType.name)
TestEnum? name;
@Enumerated(EnumType.value, 'myValue')
TestEnum? myValue;
}
enum TestEnum {
first(10),
second(100),
third(1000);
const TestEnum(this.myValue);
final short myValue;
}
```
물론 열거형을 리스트에서 사용해도 됩니다.
## 임베드된 객체
컬렉션 모델에 중첩된 객체가 있는 것이 도움이 되는 경우가 많습니다. 객체를 중첩할 수 있는 깊이에는 제한이 없습니다. 그러나 깊이 중첩된 객체를 업데이트하려면 전체 객체 트리를 데이터베이스에 기록해야 합니다.
```dart
@collection
class Email {
Id? id;
String? title;
Recepient? recipient;
}
@embedded
class Recepient {
String? name;
String? address;
}
```
임베드된 객체는 null로 사용할 수 없으며 다른 객체를 확장할 수 있습니다. 유일한 요구 사항은 `@embedded` 어노테이션을 추가하고 필수 매개 변수가 없는 기본 생성자를 갖는 것입니다.
================================================
FILE: docs/docs/ko/transactions.md
================================================
---
title: 트랜잭션
---
# 트랜잭션 (Transactions)
Isar 에서 트랜잭션은 단일 작업 단위 안에서 여러 데이터베이스 작업들을 합치게 됩니다. Isar 와의 대부분의 상호작용은 암묵적으로 트랜잭션을 사용합니다. Isar 의 읽기 및 쓰기 접근은 [ACID](http://en.wikipedia.org/wiki/ACID) 를 준수합니다. 오류가 발생하면 트랜잭션은 자동으로 롤백됩니다.
## 명시적 트랜잭션
명시적 트랜잭션에서는 데이터베이스의 일관된 스냅샷을 얻을 수 있습니다. 트랜잭션 시간을 최소화하려고 노력하세요. 트랜잭션 내부에서 네트워크 호출 및 기타 장기적인 작업들은 금지됩니다.
트랜잭션 (특히 쓰기 트랜잭션) 은 비용이 많이 들기 때문에 항상 연속적인 작업들을 단일 트랜잭션으로 그룹화해야 합니다.
트랜잭션은 동기식이나 비동기식일 수 있습니다. 동기적인 트랜잭션에서는 동기 연산만 수행할 수 있습니다. 비동기식 트랜잭션에서는 비동기 연산만 수행할 수 있습니다.
| | 읽기 | 읽기 & 쓰기 |
| ------ | ------------ | ----------------- |
| 동기 | `.txnSync()` | `.writeTxnSync()` |
| 비동기 | `.txn()` | `.writeTxn()` |
### 읽기 트랜잭션
명시적 읽기 트랜잭션은 선택 사항이지만, 원자적 읽기(atomic reads) 를 수행하고 트랜잭션 내부의 데이터베이스의 일관된 상태에 의존할 수 있게 해줍니다. 내부적으로 Isar 는 모든 읽기 작업에 항상 암시적 읽기 트랜잭션을 사용합니다.
:::tip
비동기 읽기 트랜잭션은 다른 읽기 및 쓰기 트랜잭션과 병렬로 실행됩니다. 꽤 멋있지 않나요?
:::
### 쓰기 트랜잭션
읽기 연산과 달리, Isar 에서 쓰기 연산은 반드시 명시적 트랜잭션으로 감싸야 합니다.
쓰기 트랜잭션이 성공적으로 완료되면 자동으로 커밋되고 모든 변경사항이 디스크에 기록됩니다. 오류가 발생하면 트랜잭션이 중단되고 모든 변경 사항이 롤백됩니다. 트랜잭션은 "전부가 아니면 아무것도 없다(all or nothing)" 입니다. 트랜잭션 내의 모든 쓰기가 성공하거나, 아니면 데이터 일관성을 보장하기 위해서 모두 무효가 되거나 입니다.
:::warning
데이터베이스 연산이 실패하면, 그 트랜잭션은 중단되므로 더 이상 사용할 수 없습니다. 다트에서 그 오류를 캐치(catch) 하더라도요.
:::
```dart
@collection
class Contact {
Id? id;
String? name;
}
// GOOD
await isar.writeTxn(() async {
for (var contact in getContacts()) {
await isar.contacts.put(contact);
}
});
// BAD: 트랜잭션 안으로 for 루프를 이동시키세요.
for (var contact in getContacts()) {
await isar.writeTxn(() async {
await isar.contacts.put(contact);
});
}
```
================================================
FILE: docs/docs/ko/tutorials/quickstart.md
================================================
---
title: 빠른 시작
---
# 빠른 시작
세상에, 이제야 왔군요! 가장 멋진 플러터 데이터베이스를 사용해 보겠습니다...
이 빠른 시작에서는 말은 줄이고 바로 코드를 보겠습니다.
## 1. 의존성 추가하기
재미있는 부분을 보기 전에 `pubspec.yaml` 에 몇 개의 패키지를 추가해야 합니다. 우리는 펍을 이용해서 힘든 일을 쉽게 할 수 있습니다.
```bash
dart pub add isar:^0.0.0-placeholder isar_flutter_libs:^0.0.0-placeholder --hosted-url=https://pub.isar-community.dev
dart pub add dev:isar_generator:^0.0.0-placeholder --hosted-url=https://pub.isar-community.dev
```
## 2. 클래스에 주석 추가(어노테이션)
컬렉션 클래스에 `@collection` 으로 주석을 달고 `Id` 필드를 선택합니다.
```dart
part 'email.g.dart';
@collection
class User {
Id id = Isar.autoIncrement; // id = null 을 사용해도 자동 증분할 수 있습니다.
String? name;
int? age;
}
```
Id는 컬렉션에서 개체를 고유하게 식별하고 나중에 개체를 다시 찾을 수 있도록 합니다.
## 3. 코드 생성기를 실행하기
다음 명령을 실행하여 `build_runner` 를 시작합니다:
```
dart run build_runner build
```
플러터를 사용하고 있다면, 다음 명령을 사용합니다.
```
flutter pub run build_runner build
```
## 4. Isar 인스턴스 열기
새 Isar 인스턴스를 열고 모든 컬렉션 스키마를 전달합니다. 선택적으로 인스턴스 이름과 디렉토리를 지정할 수도 있습니다.
```dart
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[EmailSchema],
directory: dir.path,
);
```
## 5. 읽기와 쓰기
한번 인스턴스를 열면, 콜렉션들을 사용할 수 있습니다.
모든 기본적인 CRUD 작업은 `IsarCollection` 을 통해서 이루어집니다.
```dart
final newUser = User()..name = 'Jane Doe'..age = 36;
await isar.writeTxn(() async {
await isar.users.put(newUser); // 삽입 & 업데이트
});
final existingUser = await isar.users.get(newUser.id); // 가져오기
await isar.writeTxn(() async {
await isar.users.delete(existingUser.id!); // 삭제
});
```
## 다른 자료들
혹시 영상으로 공부를 하는 것이 더 좋나요? 다음 영상으로 Isar를 시작해보세요:
================================================
FILE: docs/docs/ko/watchers.md
================================================
---
title: 감시자(Watchers)
---
# 감시자
Isar 에서는 데이터 변경을 구독할 수 있습니다. 특정 객체, 전체 컬렉션 또는 쿼리의 변경 내용을 "감시" 할 수 있습니다.
감시자(Watchers)를 사용하면 데이터베이스의 변경사항에 효율적으로 대응할 수 있습니다. 연락처가 추가될 때 UI를 재구성하거나 문서가 업데이트될 때 네트워크 요청을 보내는 등의 작업을 수행할 수 있습니다.
트랜잭션이 성공적으로 커밋되고 대상이 실제로 변경된 후 감시자에게 통지됩니다.
## 객체 감시
특정 객체가 생성, 업데이트 또는 삭제될 때 알림을 받으려면 객체를 확인해야 합니다:
```dart
Stream userChanged = isar.users.watchObject(5);
userChanged.listen((newUser) {
print('User changed: ${newUser?.name}');
});
final user = User(id: 5)..name = 'David';
await isar.users.put(user);
// 출력: User changed: David
final user2 = User(id: 5)..name = 'Mark';
await isar.users.put(user);
// 출력: User changed: Mark
await isar.users.delete(5);
// 출력: User changed: null
```
위의 예시에서 볼 수 있듯이 객체가 아직 존재하지 않아도 됩니다. 객체가 생성되면 감시자가 알게 됩니다.
`fireImmediately` 라는 추가 매개 변수가 있습니다. `true` 로 설정하면 Isar 는 즉시 스트림에 객체의 현재 값을 추가합니다.
### 게으른 감시 (Lazy watching)
새 값 말고 변경 사항에 대해서만 구독할 수 있습니다. 그러면 Isar 가 객체를 가져올 필요가 없어집니다.
```dart
Stream userChanged = isar.users.watchObjectLazy(5);
userChanged.listen(() {
print('User 5 changed');
});
final user = User(id: 5)..name = 'David';
await isar.users.put(user);
// 출력: User 5 changed
```
## 컬렉션 감시
단일 객체를 감시하는 대신에, 전체 컬렉션을 보고 객체가 추가, 업데이트, 또는 삭제될 때 알아차릴 수 있습니다:
```dart
Stream userChanged = isar.users.watchLazy();
userChanged.listen(() {
print('A User changed');
});
final user = User()..name = 'David';
await isar.users.put(user);
// 출력: A User changed
```
## 쿼리 감시
전체 쿼리를 감시할 수 있습니다. Isar 는 쿼리 결과가 실제로 변경될 때만 알리려고 최선을 다합니다. 링크로 인해 쿼리가 변경되는 경우 알림이 표시되지 않습니다. 링크 변경에 대한 알림이 필요한 경우 컬렉션 감시를 이용합니다.
```dart
Query usersWithA = isar.users.filter()
.nameStartsWith('A')
.build();
Stream> queryChanged = usersWithA.watch(fireImmediately: true);
queryChanged.listen((users) {
print('Users with A are: $users');
});
// 출력: Users with A are: []
await isar.users.put(User()..name = 'Albert');
// 출력: Users with A are: [User(name: Albert)]
await isar.users.put(User()..name = 'Monika');
// 출력 없음
await isar.users.put(User()..name = 'Antonia');
// 출력: Users with A are: [User(name: Albert), User(name: Antonia)]
```
:::주의
오프셋과 제한 또는 별개의 쿼리를 사용하는 경우에, Isar 는 객체가 쿼리의 바깥에 있지만, 필터와 일치하는 경우에도 알림을 받습니다.
:::
`watchObject()` 처럼 `watchLazy()` 를 사용하여 쿼리 결과가 변경될 때 알림을 받을 수 있지만 결과를 가져오지는 않습니다.
:::위험
모든 변경 사항에 대해서 쿼리를 다시 실행하는 것은 매우 비효율적입니다. 대신 게으른 컬렉션 감시자를 사용하는 것이 가장 좋습니다.
:::
================================================
FILE: docs/docs/limitations.md
================================================
---
title: Limitations
---
# Limitations
As you know, Isar works on mobile devices and desktops running on the VM as well as Web. Both platforms are very different and have different limitations.
## VM Limitations
- Only the first 1024 bytes of a string can be used for a prefix where-clause
- Objects can only be 16MB in size
## Web Limitations
Because Isar Web relies on IndexedDB, there are more limitations, but they are barely noticeable while using Isar.
- Synchronous methods are unsupported
- Currently, `Isar.splitWords()` and `.matches()` filters are not yet implemented
- Schema changes are not as tightly checked as in the VM so be careful to comply with the rules
- All number types are stored as double (the only js number type) so `@Size32` has no effect
- Indexes are represented differently so hash indexes don't use less space (they still work the same)
- `col.delete()` and `col.deleteAll()` work correctly but the return value is not correct
- `col.clear()` do not reset the auto-increment value
- `NaN` is not supported as a value
================================================
FILE: docs/docs/links.md
================================================
---
title: Links
---
# Links
Links allow you to express relationships between objects, such as a comment's author (User). You can model `1:1`, `1:n`, and `n:n` relationships with Isar links. Using links is less ergonomic than using embedded objects, and you should use embedded objects whenever possible.
Think of the link as a separate table that contains the relation. It's similar to SQL relations but has a different feature set and API.
## IsarLink
`IsarLink` can contain no or one related object, and it can be used to express a to-one relationship. `IsarLink` has a single property called `value` which holds the linked object.
Links are lazy, so you need to tell the `IsarLink` to load or save the `value` explicitly. You can do this by calling `linkProperty.load()` and `linkProperty.save()`.
:::tip
The id property of the source and target collections of a link should be non-final.
:::
For non-web targets, links get loaded automatically when you use them for the first time. Let's start by adding an IsarLink to a collection:
```dart
@collection
class Teacher {
Id? id;
late String subject;
}
@collection
class Student {
Id? id;
late String name;
final teacher = IsarLink();
}
```
We defined a link between teachers and students. Every student can have exactly one teacher in this example.
First, we create the teacher and assign it to a student. We have to `.put()` the teacher and save the link manually.
```dart
final mathTeacher = Teacher()..subject = 'Math';
final linda = Student()
..name = 'Linda'
..teacher.value = mathTeacher;
await isar.writeTxn(() async {
await isar.students.put(linda);
await isar.teachers.put(mathTeacher);
await linda.teacher.save();
});
```
We can now use the link:
```dart
final linda = await isar.students.where().nameEqualTo('Linda').findFirst();
final teacher = linda.teacher.value; // > Teacher(subject: 'Math')
```
Let's try the same thing with synchronous code. We don't need to save the link manually because `.putSync()` automatically saves all links. It even creates the teacher for us.
```dart
final englishTeacher = Teacher()..subject = 'English';
final david = Student()
..name = 'David'
..teacher.value = englishTeacher;
isar.writeTxnSync(() {
isar.students.putSync(david);
});
```
## IsarLinks
It would make more sense if the student from the previous example could have multiple teachers. Fortunately, Isar has `IsarLinks`, which can contain multiple related objects and express a to-many relationship.
`IsarLinks` extends `Set` and exposes all the methods that are allowed for sets.
`IsarLinks` behaves much like `IsarLink` and is also lazy. To load all linked object call `linkProperty.load()`. To persist the changes, call `linkProperty.save()`.
Internally both `IsarLink` and `IsarLinks` are represented in the same way. We can upgrade the `IsarLink` from before to an `IsarLinks` to assign multiple teachers to a single student (without losing data).
```dart
@collection
class Student {
Id? id;
late String name;
final teachers = IsarLinks();
}
```
This works because we did not change the name of the link (`teacher`), so Isar remembers it from before.
```dart
final biologyTeacher = Teacher()..subject = 'Biology';
final linda = isar.students.where()
.filter()
.nameEqualTo('Linda')
.findFirst();
print(linda.teachers); // {Teacher('Math')}
linda.teachers.add(biologyTeacher);
await isar.writeTxn(() async {
await linda.teachers.save();
});
print(linda.teachers); // {Teacher('Math'), Teacher('Biology')}
```
## Backlinks
I hear you ask, "What if we want to express reverse relationships?". Don't worry; we'll now introduce backlinks.
Backlinks are links in the reverse direction. Each link always has an implicit backlink. You can make it available to your app by annotating an `IsarLink` or `IsarLinks` with `@Backlink()`.
Backlinks do not require additional memory or resources; you can freely add, remove and rename them without losing data.
We want to know which students a specific teacher has, so we define a backlink:
```dart
@collection
class Teacher {
Id id;
late String subject;
@Backlink(to: 'teacher')
final student = IsarLinks();
}
```
We need to specify the link to which the backlink points. It is possible to have multiple different links between two objects.
## Initialize links
`IsarLink` and `IsarLinks` have a zero-arg constructor, which should be used to assign the link property when the object is created. It is good practice to make link properties `final`.
When you `put()` your object for the first time, the link gets initialized with source and target collection, and you can call methods like `load()` and `save()`. A link starts tracking changes immediately after its creation, so you can add and remove relations even before the link is initialized.
:::danger
It is illegal to move a link to another object.
:::
================================================
FILE: docs/docs/pt/README.md
================================================
---
home: true
title: Home
heroImage: /isar.svg
actions:
- text: Vamos Começar!
link: /pt/tutorials/quickstart.html
type: primary
features:
- title: 💙 Feito para Flutter
details: Configuração mínima, fácil de usar, sem configuração, sem clichê. Basta adicionar algumas linhas de código para começar.
- title: 🚀 Altamente escalável
details: Armazene centenas de milhares de registros em um único banco de dados NoSQL e consulte-os de forma eficiente e assíncrona.
- title: 🍭 Rico em recursos
details: O Isar possui um rico conjunto de recursos para ajudá-lo a gerenciar seus dados. Índices compostos e de várias entradas, modificadores de consulta, suporte a JSON e muito mais.
- title: 🔎 Pesquisa de texto completo
details: Isar tem busca embutida de texto completo. Crie um índice de várias entradas e pesquise registros facilmente.
- title: 🧪 Semântica ACID
details: Isar é compatível com ACID e lida com transações automaticamente. Ele reverte as alterações se ocorrer um erro.
- title: 💃 Tipagem estática
details: As consultas de Isar são estaticamente tipadas e verificadas em tempo de compilação. Não há necessidade de se preocupar com erros de tempo de execução.
- title: 📱 Multi plataforma
details: iOS, Android, Desktop e SUPORTE COMPLETO DA WEB!
- title: ⏱ Assíncrono
details: Operações de consulta paralela e suporte multiisolado pronto para uso
- title: 🦄 Código Aberto
details: Tudo é de código aberto e gratuito para sempre!
footer: Apache Licensed | Copyright © 2022 Simon Leier
---
================================================
FILE: docs/docs/pt/crud.md
================================================
---
title: Criar, Ler, Atualizar, Apagar
---
# Criar, Ler, Atualizar, Apagar
Quando você tiver suas coleções definidas, aprenda a manipulá-las!
## Abrindo Isar
Antes que você possa fazer qualquer coisa, precisamos de uma instância Isar. Cada instância requer um diretório com permissão de gravação onde o arquivo de banco de dados pode ser armazenado. Se você não especificar um diretório, o Isar encontrará um diretório padrão adequado para a plataforma atual.
Forneça todos os esquemas que deseja usar com a instância Isar. Se você abrir várias instâncias, ainda precisará fornecer os mesmos esquemas para cada instância.
```dart
final dir = await getApplicationDocumentsDirectory();
final isar = await Isar.open(
[RecipeSchema],
directory: dir.path,
);
```
Você pode usar a configuração padrão ou fornecer alguns dos seguintes parâmetros:
| Config | Description |
| -------| -------------|
| `name` | Abra várias instâncias com nomes distintos. Por padrão, `"default"` em uso. |
| `directory` | O local de armazenamento para esta instância. Por padrão, `NSDocumentDirectory` é usado para iOS e `getDataDirectory` para Android. Para web não é necessário. |
| `relaxedDurability` | Diminua a garantia de durabilidade para aumentar o desempenho de gravação. Em caso de falha do sistema (não falha do aplicativo), é possível perder a última transação confirmada. A corrupção não é possível|
| `compactOnLaunch` | Condições para verificar se o banco de dados deve ser compactado quando a instância for aberta. |
| `inspector` |Inspetor habilitado para compilações de depuração. Para builds de perfil e versão, esta opção é ignorada. |
Se uma instância já estiver aberta, chamar `Isar.open()` produzirá a instância existente independentemente dos parâmetros especificados. Isso é útil para usar Isar em um isolado.
:::tip
Considere usar o pacote [path_provider](https://pub.dev/packages/path_provider) para obter um caminho válido em todas as plataformas.
:::
O local de armazenamento do arquivo de banco de dados é `directory/name.isar`
## Leitura do banco de dados
Use instâncias `IsarCollection` para localizar, consultar e criar novos objetos de um determinado tipo em Isar.
Para os exemplos abaixo, assumimos que temos uma coleção `Recipe` definida da seguinte forma:
```dart
@collection
class Recipe {
Id? id;
String? name;
DateTime? lastCooked;
bool? isFavorite;
}
```
### Obter coleção
Todas as suas coleções residem na instância Isar. Você pode obter a coleção de receitas com:
```dart
final recipes = isar.recipes;
```
Essa foi fácil! Se você não quiser usar acessadores de coleção, você também pode usar o método `collection()`:
```dart
final recipes = isar.collection();
```
### Obter um objeto (por id)
Ainda não temos dados na coleção, mas vamos fingir que temos para que possamos obter um objeto imaginário pelo id `123`
```dart
final recipe = await isar.recipes.get(123);
```
`get()` retorna um `Future` com o objeto ou `null` se não existir. Todas as operações Isar são assíncronas por padrão e a maioria delas tem uma contrapartida síncrona:
```dart
final recipe = isar.recipes.getSync(123);
```
:::warning
Você deve usar como padrão a versão assíncrona dos métodos em seu isolamento de interface do usuário. Como o Isar é muito rápido, geralmente é aceitável usar a versão síncrona.
:::
Se você quiser obter vários objetos de uma vez, use `getAll()` ou `getAllSync()`:
```dart
final recipe = await isar.recipes.getAll([1, 2]);
```
### Objetos de consulta
Em vez de obter objetos por id, você também pode consultar uma lista de objetos que correspondem a certas condições usando `.where()` e `.filter()`:
```dart
final allRecipes = await isar.recipes.where().findAll();
final favouires = await isar.recipes.filter()
.isFavoriteEqualTo(true)
.findAll();
```
➡️ Saber mais: [Consultas](queries)
## Modificando o banco de dados
Finalmente chegou a hora de modificar nossa coleção! Para criar, atualizar ou excluir objetos, use as respectivas operações envolvidas em uma transação de gravação:
```dart
await isar.writeTxn(() async {
final recipe = await isar.recipes.get(123)
recipe.isFavorite = false;
await isar.recipes.put(recipe); // realizar operações de atualização
await isar.recipes.delete(123); // ou operações de apagar
});
```
➡️ Saber mais: [Transações](transactions)
### Inserir objeto
Para persistir um objeto em Isar, insira-o em uma coleção. O método `put()` de Isar irá inserir ou atualizar o objeto dependendo da sua existência na coleção.
Se o campo id for `null` ou `Isar.autoIncrement`, Isar usará um id de incremento automático.
```dart
final pancakes = Recipe()
..name = 'Pancakes'
..lastCooked = DateTime.now()
..isFavorite = true;
await isar.writeTxn(() async {
await isar.recipes.put(pancakes);
})
```
Isar atribuirá automaticamente o id ao objeto se o campo `id` não for final.
Inserir vários objetos de uma só vez é extremamente fácil:
```dart
await isar.writeTxn(() async {
await isar.recipes.putAll([pancakes, pizza]);
})
```
### Atualizar objeto
Tanto a criação quanto a atualização funcionam com `collection.put(object)`. Se o id for `null` (ou não existir), o objeto será inserido; caso contrário, ele é atualizado.
Então, se quisermos desfavoritar nossas panquecas, podemos fazer o seguinte:
```dart
await isar.writeTxn(() async {
pancakes.isFavorite = false;
await isar.recipes.put(recipe);
});
```
### Apagar objeto
Quer se livrar de um objeto em Isar? Use `collection.delete(id)`. O método delete retorna se um objeto com o id especificado foi encontrado e excluído. Se você quiser excluir o objeto com id `123`, por exemplo, você pode fazer:
```dart
await isar.writeTxn(() async {
final success = await isar.recipes.delete(123);
print('Receita apagada: $success');
});
```
Da mesma forma para obter e colocar, também há uma operação de exclusão em massa que retorna o número de objetos excluídos:
```dart
await isar.writeTxn(() async {
final count = await isar.recipes.deleteAll([1, 2, 3]);
print('Apagamos $count receitas');
});
```
Se você não souber os ids dos objetos que deseja excluir, poderá usar uma consulta:
```dart
await isar.writeTxn(() async {
final count = await isar.recipes.filter()
.isFavoriteEqualTo(false)
.deleteAll();
print('Apagamos $count receitas');
});
```
================================================
FILE: docs/docs/pt/faq.md
================================================
---
title: QF
---
# Questões Frequentes
Uma coleção aleatória de questões frequentes sobre bancos de dados Isar e Flutter.
### Por que preciso de um banco de dados?
> Armazeno meus dados em um banco de dados backend, por que preciso do Isar?.
Ainda hoje, é muito comum não ter conexão de dados se você estiver no metrô ou no avião ou se for visitar sua avó, que não tem WiFi e o sinal do celular é muito ruim. Você não deve deixar uma conexão ruim paralisar seu aplicativo!
### Isar vs Hive
A resposta é fácil: o Isar foi [iniciado como substituto do Hive](https://github.com/hivedb/hive/issues/246) e agora está em um estado em que recomendo sempre usar o Isar sobre o Hive.
### Cláusulas Where?!
> Por que **_I_** precisa escolher qual índice usar?
Existem várias razões. Muitos bancos de dados usam heurísticas para escolher o melhor índice para uma determinada consulta. O banco de dados precisa coletar dados de uso adicionais (-> sobrecarga) e ainda pode escolher o índice errado. Também torna a criação de uma consulta mais lenta.
Ninguém conhece seus dados melhor do que você, o desenvolvedor. Assim, você pode escolher o índice ideal e decidir, por exemplo, se deseja usar um índice para consulta ou classificação.
### Eu tenho que usar índices / cláusulas where?
Não! Isar provavelmente é rápido o suficiente se você confiar apenas em filtros.
### Isar é rápido o suficiente?
O Isar está entre os bancos de dados mais rápidos para dispositivos móveis, portanto, deve ser rápido o suficiente para a maioria dos casos de uso. Se você tiver problemas de desempenho, é provável que esteja fazendo algo errado.
### O Isar aumenta o tamanho do meu aplicativo?
Um pouco, sim. O Isar aumentará o tamanho do download do seu aplicativo em cerca de 1 a 1,5 MB. Isar Web adiciona apenas alguns KB.
### Os documentos estão incorretos/há um erro de digitação.
Ah não, desculpe. Por favor [abra um issue](https://github.com/isar-community/isar/issues/new/choose) ou, melhor ainda, um PR para corrigi-lo 💪.
================================================
FILE: docs/docs/pt/indexes.md
================================================
---
title: Índices
---
# Índices
Os índices são o recurso mais poderoso do Isar. Muitos bancos de dados incorporados oferecem índices "normais" (se houver), mas o Isar também possui índices compostos e de várias entradas. Compreender como os índices funcionam é essencial para otimizar o desempenho da consulta. Isar permite que você escolha qual índice você deseja usar e como deseja usá-lo. Começaremos com uma rápida introdução sobre o que são índices.
## O que são índices?
Quando uma coleção não é indexada, a ordem das linhas provavelmente não será discernível pela consulta como otimizada de forma alguma e, portanto, sua consulta terá que pesquisar os objetos linearmente. Em outras palavras, a consulta terá que pesquisar em todos os objetos para encontrar aqueles que correspondam às condições. Como você pode imaginar, isso pode levar algum tempo. Olhar através de cada objeto não é muito eficiente.
Por exemplo, esta coleção `Product` é totalmente desordenada.
```dart
@collection
class Product {
Id? id;
late String name;
late int price;
}
```
**Dados:**
| id | name | price |
| --- | --------- | ----- |
| 1 | Book | 15 |
| 2 | Table | 55 |
| 3 | Chair | 25 |
| 4 | Pencil | 3 |
| 5 | Lightbulb | 12 |
| 6 | Carpet | 60 |
| 7 | Pillow | 30 |
| 8 | Computer | 650 |
| 9 | Soap | 2 |
Uma consulta que tenta encontrar todos os produtos que custam mais de € 30 deve pesquisar todas as nove linhas. Isso não é um problema para nove linhas, mas pode se tornar um problema para 100 mil linhas.
```dart
final expensiveProducts = await isar.products.filter()
.priceGreaterThan(30)
.findAll();
```
Para melhorar o desempenho desta consulta, indexamos a propriedade `price`. Um índice é como uma tabela de pesquisa classificada:
```dart
@collection
class Product {
Id? id;
late String name;
@Index()
late int price;
}
```
**Índice gerado:**
| price | id |
| -------------------- | ------------------ |
| 2 | 9 |
| 3 | 4 |
| 12 | 5 |
| 15 | 1 |
| 25 | 3 |
| 30 | 7 |
| **55** | **2** |
| **60** | **6** |
| **650** | **8** |
Agora, a consulta pode ser executada muito mais rápido. O executor pode pular diretamente para as últimas três linhas do índice e encontrar os objetos correspondentes por seu id.
### Ordenação
Outra coisa legal: os índices podem fazer uma classificação super rápida. As consultas classificadas são caras porque o banco de dados precisa carregar todos os resultados na memória antes de classificá-los. Mesmo se você especificar um deslocamento ou limite, eles serão aplicados após a classificação.
Vamos imaginar que queremos encontrar os quatro produtos mais baratos. Poderíamos usar a seguinte consulta:
```dart
final cheapest = await isar.products.filter()
.sortByPrice()
.limit(4)
.findAll();
```
Neste exemplo, o banco de dados teria que carregar todos os objetos (!), classificá-los por preço e retornar os quatro produtos com o menor preço.
Como você provavelmente pode imaginar, isso pode ser feito de forma muito mais eficiente com o índice anterior. O banco de dados pega as primeiras quatro linhas do índice e retorna os objetos correspondentes, pois eles já estão na ordem correta.
Para usar o índice para classificação, escreveríamos a consulta assim:
```dart
final cheapestFast = await isar.products.where()
.anyPrice()
.limit(4)
.findAll();
```
A cláusula where `.anyX()` diz ao Isar para usar um índice apenas para ordenar. Você também pode usar uma cláusula where como `.priceGreaterThan()` e obter resultados ordenados.
## Índices únicos
Um índice único garante que o índice não contenha valores duplicados. Pode consistir em uma ou várias propriedades. Se um índice único tiver uma propriedade, os valores dessa propriedade serão exclusivos. Se o índice exclusivo tiver mais de uma propriedade, a combinação de valores nessas propriedades será única.
```dart
@collection
class User {
Id? id;
@Index(unique: true)
late String username;
late int age;
}
```
Qualquer tentativa de inserir ou atualizar dados no índice exclusivo que cause uma duplicata resultará em um erro:
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1); // -> ok
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
// tente inserir usuário com o mesmo username
await isar.users.put(user2); // -> error: unique constraint violated
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
```
## Substituir índices
Às vezes, não é preferível lançar um erro se uma restrição exclusiva for violada. Em vez disso, você pode substituir o objeto existente pelo novo. Isso pode ser feito definindo a propriedade `replace` do índice como `true`.
```dart
@collection
class User {
Id? id;
@Index(unique: true, replace: true)
late String username;
}
```
Agora, quando tentamos inserir um usuário com um nome de usuário existente, Isar substituirá o usuário existente pelo novo.
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
await isar.users.put(user1);
print(await isar.user.where().findAll());
// > [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
print(await isar.user.where().findAll());
// > [{id: 2, username: 'user1' age: 30}]
```
Os índices de substituição também geram métodos `putBy()` que permitem atualizar objetos em vez de substituí-los. O id existente é reutilizado e os links ainda são preenchidos.
```dart
final user1 = User()
..id = 1
..username = 'user1'
..age = 25;
//usuário não existe, então é o mesmo que o put()
await isar.users.putByUsername(user1);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1', age: 25}]
final user2 = User()
..id = 2;
..username = 'user1'
..age = 30;
await isar.users.put(user2);
await isar.user.where().findAll(); // -> [{id: 1, username: 'user1' age: 30}]
```
Como você pode ver, o id do primeiro usuário inserido é reutilizado.
## Índices Case-insensitive
Todos os índices nas propriedades `String` e `List` diferenciam maiúsculas de minúsculas por padrão. Se você deseja criar um índice que não diferencia maiúsculas de minúsculas, pode usar a opção `caseSensitive`:
```dart
@collection
class Person {
Id? id;
@Index(caseSensitive: false)
late String name;
@Index(caseSensitive: false)
late List tags;
}
```
## Tipo de índice
Existem diferentes tipos de índices. Na maioria das vezes, você desejará usar um índice `IndexType.value`, mas os índices de hash são mais eficientes.
### Índice de valor
Índices de valor são o tipo padrão e o único permitido para todas as propriedades que não contêm Strings ou Lists. Os valores de propriedade são usados para construir o índice. No caso de listas, os elementos da lista são usados. É o mais flexível, mas também consome espaço dos três tipos de índice.
:::tip
Use `IndexType.value` para primitivos, Strings onde você precisa de cláusulas `startsWith()` where e Lists se você quiser procurar por elementos individuais.
:::
### Índice Hash
Strings e Lists podem ser hash para reduzir significativamente o armazenamento exigido pelo índice. A desvantagem dos índices de hash é que eles não podem ser usados para varreduras de prefixo (cláusulas `startsWith` where).
:::tip
Use `IndexType.hash` para Strings e Lists se você não precisar das cláusulas `startsWith` e `elementEqualTo` where.
:::
### Índice HashElements
Lists de strings podem ser hash como um todo (usando `IndexType.hash`), ou os elementos da list podem ser hash separadamente (usando `IndexType.hashElements`), criando efetivamente um índice de várias entradas com elementos hash.
:::tip
Use `IndexType.hashElements` para `List` onde você precisa das cláusulas where `elementEqualTo`.
:::
## Índices compostos
Um índice composto é um índice em várias propriedades. Isar permite criar índices compostos de até três propriedades.
Índices compostos também são conhecidos como índices de várias colunas.
Provavelmente é melhor começar com um exemplo. Criamos uma coleção de pessoas e definimos um índice composto nas propriedades age e name:
```dart
@collection
class Person {
Id? id;
late String name;
@Index(composite: [CompositeIndex('name')])
late int age;
late String hometown;
}
```
**Dados:**
| id | name | age | hometown |
| --- | ------ | --- | --------- |
| 1 | Daniel | 20 | Berlin |
| 2 | Anne | 20 | Paris |
| 3 | Carl | 24 | San Diego |
| 4 | Simon | 24 | Munich |
| 5 | David | 20 | New York |
| 6 | Carl | 24 | London |
| 7 | Audrey | 30 | Prague |
| 8 | Anne | 24 | Paris |
**Índice gerado:**
| age | name | id |
| --- | ------ | --- |
| 20 | Anne | 2 |
| 20 | Daniel | 1 |
| 20 | David | 5 |
| 24 | Anne | 8 |
| 24 | Carl | 3 |
| 24 | Carl | 6 |
| 24 | Simon | 4 |
| 30 | Audrey | 7 |
O índice composto gerado contém todas as pessoas classificadas por idade e nome.
Índices compostos são ótimos se você deseja criar consultas eficientes classificadas por várias propriedades. Eles também permitem cláusulas where avançadas com várias propriedades:
```dart
final result = await isar.where()
.ageNameEqualTo(24, 'Carl')
.hometownProperty()
.findAll() // -> ['San Diego', 'London']
```
A última propriedade de um índice composto também suporta condições como `startsWith()` ou `lessThan()`:
```dart
final result = await isar.where()
.ageEqualToNameStartsWith(20, 'Da')
.findAll() // -> [Daniel, David]
```
## Índices de várias entradas
Se você indexar uma lista usando `IndexType.value`, o Isar criará automaticamente um índice de várias entradas e cada item da lista será indexado em relação ao objeto. Funciona para todos os tipos de listas.
As aplicações práticas para índices de várias entradas incluem a indexação de uma lista de tags ou a criação de um índice de texto completo.
```dart
@collection
class Product {
Id? id;
late String description;
@Index(type: IndexType.value, caseSensitive: false)
List get descriptionWords => Isar.splitWords(description);
}
```
`Isar.splitWords()` divide uma string em palavras de acordo com a especificação do [Unicode Annex #29](https://unicode.org/reports/tr29/), então funciona para quase todos os idiomas corretamente.
**Dados:**
| id | description | descriptionWords |
| --- | ---------------------------- | ---------------------------- |
| 1 | comfortable blue t-shirt | [comfortable, blue, t-shirt] |
| 2 | comfortable, red pullover!!! | [comfortable, red, pullover] |
| 3 | plain red t-shirt | [plain, red, t-shirt] |
| 4 | red necktie (super red) | [red, necktie, super, red] |
As entradas com palavras duplicadas aparecem apenas uma vez no índice.
**Índice gerado:**
| descriptionWords | id |
| ---------------- | --------- |
| comfortable | [1, 2] |
| blue | 1 |
| necktie | 4 |
| plain | 3 |
| pullover | 2 |
| red | [2, 3, 4] |
| super | 4 |
| t-shirt | [1, 3] |
Este índice agora pode ser usado para prefixo (ou igualdade) onde cláusulas das palavras individuais da descrição.
:::tip
Em vez de armazenar as palavras diretamente, considere também usar o resultado de um [algoritmo fonético](https://en.wikipedia.org/wiki/Phonetic_algorithm) como [Soundex](https://en.wikipedia.org/wiki/ Soundex).
:::
================================================
FILE: docs/docs/pt/limitations.md
================================================
# Limitações
Como você sabe, o Isar funciona em dispositivos móveis e desktops executados na VM e na Web. Ambas as plataformas são muito diferentes e têm limitações diferentes.
## Limitações da VM
- Apenas os primeiros 1024 bytes de uma string podem ser usados para um prefixo where-clause
- Objetos podem ter apenas 16 MB de tamanho
## Limitações da Web
Como o Isar Web depende do IndexedDB, há mais limitações, mas elas são quase imperceptíveis ao usar o Isar.
- Métodos síncronos não são suportados
- Atualmente, os filtros `Isar.splitWords()` e `.matches()` ainda não estão implementados
- As alterações de esquema não são tão rigorosamente verificadas quanto na VM, portanto, tenha cuidado para cumprir as regras
- Todos os tipos de números são armazenados como double (o único tipo de número js) para que `@Size32` não tenha efeito
- Os índices são representados de forma diferente para que os índices de hash não usem menos espaço (eles ainda funcionam da mesma forma)
- `col.delete()` e `col.deleteAll()` funcionam corretamente, mas o valor de retorno não está correto
- `col.clear()` não redefine o valor de incremento automático
- `NaN` não é suportado como valor
================================================
FILE: docs/docs/pt/links.md
================================================
---
title: Links
---
# Links
Os links permitem que você expresse relacionamentos entre objetos, como o autor de um comentário (Usuário). Você pode modelar relacionamentos `1:1`, `1:n` e `n:n` com links Isar. Usar links é menos ergonômico do que usar objetos incorporados e você deve usar objetos incorporados sempre que possível.
Pense no link como uma tabela separada que contém a relação. É semelhante às relações SQL, mas possui um conjunto de recursos e API diferentes.
## IsarLink
`IsarLink` pode conter nenhum ou um objeto relacionado e pode ser usado para expressar um relacionamento de um para um. `IsarLink` tem uma única propriedade chamada `value` que contém o objeto vinculado.
Os links são preguiçosos, então você precisa dizer ao `IsarLink` para carregar ou salvar o `valor` explicitamente. Você pode fazer isso chamando `linkProperty.load()` e `linkProperty.save()`.
:::tip
A propriedade id das coleções de origem e destino de um link não deve ser final.
:::
Para destinos não Web, os links são carregados automaticamente quando você os usa pela primeira vez. Vamos começar adicionando um IsarLink a uma coleção:
```dart
@collection
class Teacher {
Id? id;
late String subject;
}
@collection
class Student {
Id? id;
late String name;
final teacher = IsarLink();
}
```
Definimos um vínculo entre teachers e students. Cada student pode ter exatamente um teacher neste exemplo.
Primeiro, criamos o teacher e o atribuímos a um student. Temos que fazer o `.put()` do teacher e salvar o link manualmente.
```dart
final mathTeacher = Teacher()..subject = 'Math';
final linda = Student()
..name = 'Linda'
..teacher.value = mathTeacher;
await isar.writeTxn(() async {
await isar.students.put(linda);
await isar.teachers.put(mathTeacher);
await linda.teacher.save();
});
```
Agora podemos usar o link:
```dart
final linda = await isar.students.where().nameEqualTo('Linda').findFirst();
final teacher = linda.teacher.value; // > Teacher(subject: 'Math')
```
Vamos tentar a mesma coisa com código síncrono. Não precisamos salvar o link manualmente porque `.putSync()` salva automaticamente todos os links. Até cria o professor para nós.
```dart
final englishTeacher = Teacher()..subject = 'English';
final david = Student()
..name = 'David'
..teacher.value = englishTeacher;
isar.writeTxnSync(() {
isar.students.putSync(david);
});
```
## IsarLinks
Faria mais sentido se o aluno do exemplo anterior pudesse ter vários professores. Felizmente, Isar tem `IsarLinks`, que pode conter vários objetos relacionados e expressar um relacionamento para muitos.
`IsarLinks` estende `Set