Isar

Isarは「Flutter/Dart向けの超高速NoSQLデータベース」として開発された、ネイティブパフォーマンスを誇るクロスプラットフォーム対応データベースです。C++で書かれたコアエンジンにより、SQLiteを上回る圧倒的な読み書き性能を実現し、Dart FFI (Foreign Function Interface) を通じてネイティブコードと直接通信します。型安全で直感的なクエリAPI、強力なインデックス機能、自動スキーママイグレーション、リアクティブなストリーム監視により、現代的なFlutterアプリケーション開発における高性能ローカルストレージソリューションの新しい標準を確立しています。

NoSQLFlutterDartデータベース高性能インデックス

GitHub概要

isar/isar

Extremely fast, easy to use, and fully async NoSQL database for Flutter

ホームページ:https://isar.dev
スター3,861
ウォッチ53
フォーク508
作成日:2020年6月10日
言語:Dart
ライセンス:Apache License 2.0

トピックス

androidcross-platformdartdatabaseflutteriosisarweb

スター履歴

isar/isar Star History
データ取得日時: 2025/7/17 06:57

ライブラリ

Isar

概要

Isarは「Flutter/Dart向けの超高速NoSQLデータベース」として開発された、ネイティブパフォーマンスを誇るクロスプラットフォーム対応データベースです。C++で書かれたコアエンジンにより、SQLiteを上回る圧倒的な読み書き性能を実現し、Dart FFI (Foreign Function Interface) を通じてネイティブコードと直接通信します。型安全で直感的なクエリAPI、強力なインデックス機能、自動スキーママイグレーション、リアクティブなストリーム監視により、現代的なFlutterアプリケーション開発における高性能ローカルストレージソリューションの新しい標準を確立しています。

詳細

Isar 2025年版は、Flutter 3.16以降とDart 3.0に完全対応し、null safetyとソリッドな型システムの恩恵を最大限活用した次世代データベースエンジンです。高度なインデックス戦略により、複雑なクエリも超高速で実行でき、複合インデックス、部分インデックス、全文検索インデックスを使い分けてパフォーマンスを最適化できます。暗号化機能、バックアップ・復元、スキーママイグレーション、リンクとバックリンクによる柔軟なリレーションシップ管理、そして強力なクエリビルダーにより、エンタープライズレベルのモバイルアプリケーション開発に必要な全機能を提供します。

主な特徴

  • 超高速パフォーマンス: C++コアによりSQLiteより最大10倍高速
  • 強力なインデックス: 複合、部分、全文検索インデックスサポート
  • 型安全クエリ: コンパイル時型チェックによる安全なデータアクセス
  • リアクティブストリーム: データ変更の自動監視とUI更新
  • スキーママイグレーション: 自動・手動両方のマイグレーション対応
  • 暗号化対応: AES-256による暗号化とセキュアなデータ保存

メリット・デメリット

メリット

  • SQLiteを大幅に上回る圧倒的な読み書き性能
  • Dart/Flutterエコシステムとの完璧な統合と型安全性
  • 強力なインデックス機能による複雑なクエリの高速実行
  • リアクティブプログラミングとの優れた親和性
  • 自動スキーママイグレーションによる開発効率向上
  • 豊富なクエリ機能とフルテキスト検索対応

デメリット

  • 比較的新しいライブラリで実績がまだ限定的
  • Rust/C++に依存し、デバッグが困難な場合がある
  • ドキュメントとコミュニティがHiveより少ない
  • 複雑なリレーションシップの表現にはコツが必要
  • バイナリサイズがやや大きく、軽量アプリには不向き
  • プラットフォーム固有のコンパイルが必要

参考ページ

書き方の例

セットアップ

# pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  isar: ^3.1.0+1
  isar_flutter_libs: ^3.1.0+1

dev_dependencies:
  isar_generator: ^3.1.0+1
  build_runner: ^2.4.9
// main.dart - 初期化
import 'package:flutter/material.dart';
import 'package:isar/isar.dart';
import 'package:isar_flutter_libs/isar_flutter_libs.dart';
import 'package:path_provider/path_provider.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  // Isarの初期化
  final dir = await getApplicationDocumentsDirectory();
  final isar = await Isar.open(
    [UserSchema, PostSchema, CategorySchema],
    directory: dir.path,
  );

  runApp(MyApp(isar: isar));
}

class MyApp extends StatelessWidget {
  final Isar isar;

  const MyApp({super.key, required this.isar});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Isar Demo',
      home: HomePage(isar: isar),
    );
  }
}

基本的な使い方

// コレクションの定義
import 'package:isar/isar.dart';

part 'user.g.dart'; // 生成されるファイル

@collection
class User {
  Id id = Isar.autoIncrement;

  @Index()
  late String name;

  @Index(unique: true)
  late String email;

  late int age;

  late DateTime createdAt;

  @Index()
  late bool isActive;

  List<String> hobbies = [];

  @ignore
  String? temporaryData; // データベースに保存されない

  // リンク(リレーションシップ)
  final posts = IsarLinks<Post>();
  final profile = IsarLink<UserProfile>();

  // 計算プロパティ
  @Index()
  String get nameEmail => '$name $email';

  // オブジェクトコンストラクタ
  User({
    required this.name,
    required this.email,
    required this.age,
    DateTime? createdAt,
    this.isActive = true,
    this.hobbies = const [],
  }) : createdAt = createdAt ?? DateTime.now();
}

@collection
class Post {
  Id id = Isar.autoIncrement;

  @Index()
  late String title;

  late String content;

  @Index()
  late DateTime publishedAt;

  @Index()
  late bool isPublished;

  List<String> tags = [];

  @Index()
  late int categoryId;

  // バックリンク
  @Backlink(to: 'posts')
  final author = IsarLink<User>();
}

@collection
class Category {
  Id id = Isar.autoIncrement;

  @Index(unique: true)
  late String name;

  late String description;

  late DateTime createdAt;
}

@collection
class UserProfile {
  Id id = Isar.autoIncrement;

  late String bio;

  String? website;

  String? location;

  DateTime? birthDate;

  // バックリンク
  @Backlink(to: 'profile')
  final user = IsarLink<User>();
}

// アダプター生成コマンド:
// dart run build_runner build

データ操作

// データベース操作クラス
class UserService {
  final Isar isar;

  UserService(this.isar);

  // ユーザー作成
  Future<User> createUser({
    required String name,
    required String email,
    required int age,
    List<String> hobbies = const [],
  }) async {
    final user = User(
      name: name,
      email: email,
      age: age,
      hobbies: hobbies,
    );

    await isar.writeTxn(() async {
      await isar.users.put(user);
    });

    return user;
  }

  // ユーザー更新
  Future<void> updateUser(User user) async {
    await isar.writeTxn(() async {
      await isar.users.put(user);
    });
  }

  // ユーザー削除
  Future<bool> deleteUser(int id) async {
    return await isar.writeTxn(() async {
      return await isar.users.delete(id);
    });
  }

  // 単一ユーザー取得
  Future<User?> getUser(int id) async {
    return await isar.users.get(id);
  }

  // 全ユーザー取得
  Future<List<User>> getAllUsers() async {
    return await isar.users.where().findAll();
  }

  // メールでユーザー検索
  Future<User?> getUserByEmail(String email) async {
    return await isar.users.filter().emailEqualTo(email).findFirst();
  }

  // 名前で検索(部分一致)
  Future<List<User>> searchUsersByName(String nameQuery) async {
    return await isar.users
        .filter()
        .nameContains(nameQuery, caseSensitive: false)
        .findAll();
  }

  // 年齢範囲でフィルタ
  Future<List<User>> getUsersByAgeRange(int minAge, int maxAge) async {
    return await isar.users
        .filter()
        .ageBetween(minAge, maxAge)
        .findAll();
  }

  // アクティブユーザーのみ取得
  Future<List<User>> getActiveUsers() async {
    return await isar.users
        .filter()
        .isActiveEqualTo(true)
        .sortByCreatedAtDesc()
        .findAll();
  }

  // 複雑なクエリ例
  Future<List<User>> getActiveAdultUsersWithHobbies() async {
    return await isar.users
        .filter()
        .isActiveEqualTo(true)
        .and()
        .ageGreaterThan(18)
        .and()
        .hobbiesIsNotEmpty()
        .sortByName()
        .findAll();
  }

  // ページネーション
  Future<List<User>> getUsersPaged(int offset, int limit) async {
    return await isar.users
        .where()
        .offset(offset)
        .limit(limit)
        .findAll();
  }

  // カウント取得
  Future<int> getUserCount() async {
    return await isar.users.count();
  }

  Future<int> getActiveUserCount() async {
    return await isar.users.filter().isActiveEqualTo(true).count();
  }
}

// 使用例
void demonstrateBasicOperations() async {
  final userService = UserService(isar);

  // ユーザー作成
  final user1 = await userService.createUser(
    name: '田中太郎',
    email: '[email protected]',
    age: 30,
    hobbies: ['読書', 'プログラミング', 'ゲーム'],
  );

  print('Created user: ${user1.id}');

  // 複数ユーザー作成
  final users = [
    ('佐藤花子', '[email protected]', 25, ['料理', '旅行']),
    ('鈴木次郎', '[email protected]', 35, ['スポーツ', '音楽']),
    ('高橋美咲', '[email protected]', 28, ['写真', 'アート']),
  ];

  for (final (name, email, age, hobbies) in users) {
    await userService.createUser(
      name: name,
      email: email,
      age: age,
      hobbies: hobbies,
    );
  }

  // データ取得
  final allUsers = await userService.getAllUsers();
  print('Total users: ${allUsers.length}');

  // 検索
  final searchResults = await userService.searchUsersByName('田中');
  print('Search results: ${searchResults.length}');

  // 年齢フィルタ
  final youngAdults = await userService.getUsersByAgeRange(20, 30);
  print('Young adults: ${youngAdults.length}');
}

リンクとリレーションシップ

// リレーションシップ操作サービス
class RelationshipService {
  final Isar isar;

  RelationshipService(this.isar);

  // ユーザーにプロフィールを追加
  Future<void> createUserProfile({
    required User user,
    required String bio,
    String? website,
    String? location,
    DateTime? birthDate,
  }) async {
    final profile = UserProfile()
      ..bio = bio
      ..website = website
      ..location = location
      ..birthDate = birthDate;

    await isar.writeTxn(() async {
      await isar.userProfiles.put(profile);
      user.profile.value = profile;
      await user.profile.save();
    });
  }

  // ユーザーの投稿を作成
  Future<Post> createUserPost({
    required User user,
    required String title,
    required String content,
    List<String> tags = const [],
    required int categoryId,
    bool isPublished = false,
  }) async {
    final post = Post()
      ..title = title
      ..content = content
      ..tags = tags
      ..categoryId = categoryId
      ..publishedAt = DateTime.now()
      ..isPublished = isPublished;

    await isar.writeTxn(() async {
      await isar.posts.put(post);
      user.posts.add(post);
      await user.posts.save();
    });

    return post;
  }

  // ユーザーの投稿を取得(リレーション込み)
  Future<User?> getUserWithPosts(int userId) async {
    final user = await isar.users.get(userId);
    if (user != null) {
      await user.posts.load();
      await user.profile.load();
    }
    return user;
  }

  // 投稿の作者を取得
  Future<Post?> getPostWithAuthor(int postId) async {
    final post = await isar.posts.get(postId);
    if (post != null) {
      await post.author.load();
    }
    return post;
  }

  // ユーザーの投稿をカテゴリ別に取得
  Future<List<Post>> getUserPostsByCategory(int userId, int categoryId) async {
    final user = await isar.users.get(userId);
    if (user == null) return [];

    await user.posts.load();
    return user.posts.where((post) => post.categoryId == categoryId).toList();
  }

  // 公開投稿のみを取得
  Future<List<Post>> getPublishedPosts({
    int? limit,
    int? offset,
  }) async {
    var query = isar.posts
        .filter()
        .isPublishedEqualTo(true)
        .sortByPublishedAtDesc();

    if (offset != null) {
      query = query.offset(offset);
    }

    if (limit != null) {
      query = query.limit(limit);
    }

    final posts = await query.findAll();

    // 各投稿の作者情報を読み込み
    for (final post in posts) {
      await post.author.load();
    }

    return posts;
  }

  // 複雑なリレーションクエリ
  Future<List<User>> getActiveUsersWithRecentPosts(int days) async {
    final cutoffDate = DateTime.now().subtract(Duration(days: days));

    return await isar.users
        .filter()
        .isActiveEqualTo(true)
        .and()
        .posts((q) => q
            .isPublishedEqualTo(true)
            .and()
            .publishedAtGreaterThan(cutoffDate))
        .findAll();
  }

  // ユーザー統計取得
  Future<Map<String, dynamic>> getUserStats(int userId) async {
    final user = await isar.users.get(userId);
    if (user == null) return {};

    await user.posts.load();

    final totalPosts = user.posts.length;
    final publishedPosts = user.posts.where((p) => p.isPublished).length;
    final recentPosts = user.posts
        .where((p) => p.publishedAt.isAfter(
            DateTime.now().subtract(const Duration(days: 30))))
        .length;

    return {
      'totalPosts': totalPosts,
      'publishedPosts': publishedPosts,
      'draftPosts': totalPosts - publishedPosts,
      'recentPosts': recentPosts,
      'joinDate': user.createdAt,
      'hasProfile': user.profile.value != null,
    };
  }
}

リアクティブクエリとストリーム

// リアクティブUIの実装
class UserListWidget extends StatelessWidget {
  final Isar isar;

  const UserListWidget({super.key, required this.isar});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('User List')),
      body: StreamBuilder<List<User>>(
        stream: isar.users
            .filter()
            .isActiveEqualTo(true)
            .watch(fireImmediately: true),
        builder: (context, snapshot) {
          if (snapshot.connectionState == ConnectionState.waiting) {
            return const Center(child: CircularProgressIndicator());
          }

          if (snapshot.hasError) {
            return Center(child: Text('Error: ${snapshot.error}'));
          }

          final users = snapshot.data ?? [];

          if (users.isEmpty) {
            return const Center(child: Text('No active users found'));
          }

          return ListView.builder(
            itemCount: users.length,
            itemBuilder: (context, index) {
              final user = users[index];
              return UserTile(user: user, isar: isar);
            },
          );
        },
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => _showAddUserDialog(context),
        child: const Icon(Icons.add),
      ),
    );
  }

  void _showAddUserDialog(BuildContext context) {
    showDialog(
      context: context,
      builder: (context) => AddUserDialog(isar: isar),
    );
  }
}

// 個別ユーザータイル
class UserTile extends StatelessWidget {
  final User user;
  final Isar isar;

  const UserTile({super.key, required this.user, required this.isar});

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<User?>(
      stream: isar.users.watchObject(user.id, fireImmediately: true),
      builder: (context, snapshot) {
        final currentUser = snapshot.data ?? user;

        return ListTile(
          title: Text(currentUser.name),
          subtitle: Text('${currentUser.age}歳 - ${currentUser.email}'),
          trailing: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              IconButton(
                icon: Icon(
                  currentUser.isActive ? Icons.pause : Icons.play_arrow,
                ),
                onPressed: () => _toggleActiveStatus(currentUser),
              ),
              IconButton(
                icon: const Icon(Icons.delete),
                onPressed: () => _deleteUser(currentUser),
              ),
            ],
          ),
          leading: CircleAvatar(
            child: Text(currentUser.name.substring(0, 1)),
          ),
          onTap: () => _showUserDetails(context, currentUser),
        );
      },
    );
  }

  void _toggleActiveStatus(User user) async {
    await isar.writeTxn(() async {
      user.isActive = !user.isActive;
      await isar.users.put(user);
    });
  }

  void _deleteUser(User user) async {
    await isar.writeTxn(() async {
      await isar.users.delete(user.id);
    });
  }

  void _showUserDetails(BuildContext context, User user) {
    Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => UserDetailPage(user: user, isar: isar),
      ),
    );
  }
}

// ユーザー詳細ページ
class UserDetailPage extends StatelessWidget {
  final User user;
  final Isar isar;

  const UserDetailPage({super.key, required this.user, required this.isar});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(user.name)),
      body: StreamBuilder<User?>(
        stream: isar.users.watchObject(user.id, fireImmediately: true),
        builder: (context, snapshot) {
          final currentUser = snapshot.data;
          if (currentUser == null) {
            return const Center(child: Text('User not found'));
          }

          return SingleChildScrollView(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                _UserInfoCard(user: currentUser),
                const SizedBox(height: 16),
                _UserPostsSection(user: currentUser, isar: isar),
              ],
            ),
          );
        },
      ),
    );
  }
}

class _UserInfoCard extends StatelessWidget {
  final User user;

  const _UserInfoCard({required this.user});

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              user.name,
              style: Theme.of(context).textTheme.headlineSmall,
            ),
            const SizedBox(height: 8),
            Text('Email: ${user.email}'),
            Text('Age: ${user.age}'),
            Text('Status: ${user.isActive ? "Active" : "Inactive"}'),
            Text('Joined: ${_formatDate(user.createdAt)}'),
            if (user.hobbies.isNotEmpty) ...[
              const SizedBox(height: 8),
              Text('Hobbies: ${user.hobbies.join(", ")}'),
            ],
          ],
        ),
      ),
    );
  }

  String _formatDate(DateTime date) {
    return '${date.year}/${date.month}/${date.day}';
  }
}

class _UserPostsSection extends StatelessWidget {
  final User user;
  final Isar isar;

  const _UserPostsSection({required this.user, required this.isar});

  @override
  Widget build(BuildContext context) {
    return StreamBuilder<List<Post>>(
      stream: isar.posts
          .filter()
          .author((q) => q.idEqualTo(user.id))
          .watch(fireImmediately: true),
      builder: (context, snapshot) {
        final posts = snapshot.data ?? [];

        return Card(
          child: Padding(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  'Posts (${posts.length})',
                  style: Theme.of(context).textTheme.titleMedium,
                ),
                const SizedBox(height: 8),
                if (posts.isEmpty)
                  const Text('No posts yet')
                else
                  ...posts.map((post) => ListTile(
                        title: Text(post.title),
                        subtitle: Text(
                          'Published: ${_formatDate(post.publishedAt)}',
                        ),
                        trailing: Icon(
                          post.isPublished
                              ? Icons.visibility
                              : Icons.visibility_off,
                        ),
                      )),
              ],
            ),
          ),
        );
      },
    );
  }

  String _formatDate(DateTime date) {
    return '${date.year}/${date.month}/${date.day}';
  }
}

// 新規ユーザー追加ダイアログ
class AddUserDialog extends StatefulWidget {
  final Isar isar;

  const AddUserDialog({super.key, required this.isar});

  @override
  State<AddUserDialog> createState() => _AddUserDialogState();
}

class _AddUserDialogState extends State<AddUserDialog> {
  final _formKey = GlobalKey<FormState>();
  final _nameController = TextEditingController();
  final _emailController = TextEditingController();
  final _ageController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return AlertDialog(
      title: const Text('Add New User'),
      content: Form(
        key: _formKey,
        child: Column(
          mainAxisSize: MainAxisSize.min,
          children: [
            TextFormField(
              controller: _nameController,
              decoration: const InputDecoration(labelText: 'Name'),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter a name';
                }
                return null;
              },
            ),
            TextFormField(
              controller: _emailController,
              decoration: const InputDecoration(labelText: 'Email'),
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter an email';
                }
                if (!value.contains('@')) {
                  return 'Please enter a valid email';
                }
                return null;
              },
            ),
            TextFormField(
              controller: _ageController,
              decoration: const InputDecoration(labelText: 'Age'),
              keyboardType: TextInputType.number,
              validator: (value) {
                if (value == null || value.isEmpty) {
                  return 'Please enter an age';
                }
                if (int.tryParse(value) == null) {
                  return 'Please enter a valid number';
                }
                return null;
              },
            ),
          ],
        ),
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.of(context).pop(),
          child: const Text('Cancel'),
        ),
        ElevatedButton(
          onPressed: _saveUser,
          child: const Text('Save'),
        ),
      ],
    );
  }

  void _saveUser() async {
    if (_formKey.currentState!.validate()) {
      final user = User(
        name: _nameController.text,
        email: _emailController.text,
        age: int.parse(_ageController.text),
      );

      await widget.isar.writeTxn(() async {
        await widget.isar.users.put(user);
      });

      if (mounted) {
        Navigator.of(context).pop();
      }
    }
  }

  @override
  void dispose() {
    _nameController.dispose();
    _emailController.dispose();
    _ageController.dispose();
    super.dispose();
  }
}

インデックスとパフォーマンス最適化

// 高度なインデックス戦略
@collection
class OptimizedUser {
  Id id = Isar.autoIncrement;

  // 基本インデックス
  @Index()
  late String name;

  // ユニークインデックス
  @Index(unique: true)
  late String email;

  // 複合インデックス(高速範囲検索)
  @Index(composite: [CompositeIndex('age')])
  late bool isActive;
  late int age;

  // 大文字小文字を無視したインデックス
  @Index(caseSensitive: false)
  late String username;

  // 部分インデックス(条件付きインデックス)
  @Index(
    type: IndexType.hash,
    replace: true, // 重複キーを置き換え
  )
  String? phoneNumber;

  // 全文検索用インデックス
  @Index(type: IndexType.words)
  late String biography;

  late DateTime createdAt;
  late DateTime lastLoginAt;
  List<String> skills = [];
  
  // 複合インデックス用計算プロパティ
  @Index(composite: [CompositeIndex('lastLoginAt')])
  bool get isRecentlyActive => 
      lastLoginAt.isAfter(DateTime.now().subtract(const Duration(days: 7)));
}

// パフォーマンス最適化されたクエリサービス
class OptimizedQueryService {
  final Isar isar;

  OptimizedQueryService(this.isar);

  // インデックスを活用した高速検索
  Future<List<OptimizedUser>> findActiveUsersByAge(int minAge, int maxAge) async {
    // 複合インデックス (isActive, age) を活用
    return await isar.optimizedUsers
        .where()
        .isActiveAge(true, between: [minAge, maxAge])
        .findAll();
  }

  // ハッシュインデックスを活用した検索
  Future<OptimizedUser?> findUserByPhone(String phone) async {
    return await isar.optimizedUsers
        .where()
        .phoneNumberEqualTo(phone)
        .findFirst();
  }

  // 全文検索の例
  Future<List<OptimizedUser>> searchUsersByBio(String searchTerm) async {
    final words = searchTerm.toLowerCase().split(' ');
    return await isar.optimizedUsers
        .filter()
        .biographyWordsAnyStartWith(words)
        .findAll();
  }

  // 大文字小文字を無視した検索
  Future<List<OptimizedUser>> findUsersByUsername(String username) async {
    return await isar.optimizedUsers
        .where()
        .usernameEqualTo(username)
        .findAll();
  }

  // 複雑な複合クエリ(最適化済み)
  Future<List<OptimizedUser>> getRecentlyActiveSkillfulUsers(
    List<String> requiredSkills,
  ) async {
    return await isar.optimizedUsers
        .filter()
        .isRecentlyActiveEqualTo(true)
        .and()
        .repeat((q) => q.skillsAnyOf(requiredSkills), requiredSkills.length)
        .sortByLastLoginAtDesc()
        .findAll();
  }

  // バッチ操作によるパフォーマンス向上
  Future<void> batchUpdateUsers(List<OptimizedUser> users) async {
    await isar.writeTxn(() async {
      await isar.optimizedUsers.putAll(users);
    });
  }

  // 集計クエリの最適化
  Future<Map<String, dynamic>> getUserAnalytics() async {
    final total = await isar.optimizedUsers.count();
    final active = await isar.optimizedUsers
        .filter()
        .isActiveEqualTo(true)
        .count();

    final recentlyActive = await isar.optimizedUsers
        .filter()
        .isRecentlyActiveEqualTo(true)
        .count();

    // 年齢分布の取得
    final ageGroups = <String, int>{};
    const ageRanges = [(18, 25), (26, 35), (36, 45), (46, 60), (61, 100)];
    
    for (final (min, max) in ageRanges) {
      final count = await isar.optimizedUsers
          .filter()
          .ageBetween(min, max)
          .count();
      ageGroups['$min-$max'] = count;
    }

    return {
      'total': total,
      'active': active,
      'recentlyActive': recentlyActive,
      'ageGroups': ageGroups,
      'activePercentage': total > 0 ? (active / total * 100).round() : 0,
    };
  }
}

// インデックスのメンテナンスとモニタリング
class IndexMaintenanceService {
  final Isar isar;

  IndexMaintenanceService(this.isar);

  // データベースの統計情報取得
  Future<Map<String, dynamic>> getDatabaseStats() async {
    final stats = <String, dynamic>{};

    // 各コレクションのサイズ
    stats['collections'] = {
      'users': await isar.optimizedUsers.count(),
      'posts': await isar.posts.count(),
      'categories': await isar.categorys.count(),
    };

    // データベースファイルサイズ(概算)
    stats['estimatedSize'] = await _getEstimatedSize();

    return stats;
  }

  // データベースの圧縮
  Future<void> compactDatabase() async {
    await isar.compact();
  }

  // インデックスの再構築(必要な場合)
  Future<void> rebuildIndexes() async {
    // Isarでは自動的にインデックスが管理されるため、
    // 通常は手動でのインデックス再構築は不要
    // スキーマ変更時に自動的に処理される
  }

  Future<int> _getEstimatedSize() async {
    // 実際の実装では、ファイルシステムのサイズを取得
    // ここでは概算値を返す
    final userCount = await isar.optimizedUsers.count();
    final postCount = await isar.posts.count();
    
    // 1ユーザーあたり約1KB、1投稿あたり約2KBと仮定
    return userCount * 1024 + postCount * 2048;
  }
}

エラーハンドリング

// 堅牢なデータベース操作サービス
class RobustIsarService {
  final Isar isar;

  RobustIsarService(this.isar);

  // 安全なユーザー作成
  Future<Result<User, String>> createUserSafely({
    required String name,
    required String email,
    required int age,
    List<String> hobbies = const [],
  }) async {
    try {
      // 入力検証
      final validation = _validateUserInput(name, email, age);
      if (validation != null) {
        return Result.error(validation);
      }

      // 重複チェック
      final existingUser = await isar.users
          .filter()
          .emailEqualTo(email)
          .findFirst();

      if (existingUser != null) {
        return Result.error('User with email $email already exists');
      }

      // ユーザー作成
      final user = User(
        name: name,
        email: email,
        age: age,
        hobbies: hobbies,
      );

      await isar.writeTxn(() async {
        await isar.users.put(user);
      });

      return Result.success(user);

    } on IsarError catch (e) {
      return Result.error('Database error: ${e.message}');
    } catch (e) {
      return Result.error('Unexpected error: $e');
    }
  }

  // 安全なユーザー更新
  Future<Result<User, String>> updateUserSafely(
    int userId,
    Map<String, dynamic> updates,
  ) async {
    try {
      final user = await isar.users.get(userId);
      if (user == null) {
        return Result.error('User not found');
      }

      // 更新フィールドの検証と適用
      if (updates.containsKey('name')) {
        final name = updates['name'] as String?;
        if (name == null || name.trim().isEmpty) {
          return Result.error('Name cannot be empty');
        }
        user.name = name.trim();
      }

      if (updates.containsKey('email')) {
        final email = updates['email'] as String?;
        if (email == null || !_isValidEmail(email)) {
          return Result.error('Invalid email format');
        }

        // 重複チェック(自分以外)
        final existingUser = await isar.users
            .filter()
            .emailEqualTo(email)
            .and()
            .not()
            .idEqualTo(userId)
            .findFirst();

        if (existingUser != null) {
          return Result.error('Email already exists');
        }

        user.email = email;
      }

      if (updates.containsKey('age')) {
        final age = updates['age'] as int?;
        if (age == null || age < 0 || age > 150) {
          return Result.error('Invalid age');
        }
        user.age = age;
      }

      if (updates.containsKey('hobbies')) {
        final hobbies = updates['hobbies'] as List<String>?;
        user.hobbies = hobbies ?? [];
      }

      if (updates.containsKey('isActive')) {
        final isActive = updates['isActive'] as bool?;
        user.isActive = isActive ?? true;
      }

      await isar.writeTxn(() async {
        await isar.users.put(user);
      });

      return Result.success(user);

    } on IsarError catch (e) {
      return Result.error('Database error: ${e.message}');
    } catch (e) {
      return Result.error('Unexpected error: $e');
    }
  }

  // 安全なバッチ操作
  Future<Result<BatchResult, String>> batchOperationSafely(
    List<BatchOperation> operations,
  ) async {
    try {
      final results = BatchResult();

      await isar.writeTxn(() async {
        for (final operation in operations) {
          try {
            switch (operation.type) {
              case BatchOperationType.create:
                final user = operation.data as User;
                await isar.users.put(user);
                results.successful.add(operation);
                break;

              case BatchOperationType.update:
                final userId = operation.id!;
                final updates = operation.data as Map<String, dynamic>;
                final user = await isar.users.get(userId);
                
                if (user != null) {
                  _applyUpdates(user, updates);
                  await isar.users.put(user);
                  results.successful.add(operation);
                } else {
                  results.failed.add(FailedOperation(operation, 'User not found'));
                }
                break;

              case BatchOperationType.delete:
                final userId = operation.id!;
                final deleted = await isar.users.delete(userId);
                if (deleted) {
                  results.successful.add(operation);
                } else {
                  results.failed.add(FailedOperation(operation, 'User not found'));
                }
                break;
            }
          } catch (e) {
            results.failed.add(FailedOperation(operation, e.toString()));
          }
        }
      });

      return Result.success(results);

    } on IsarError catch (e) {
      return Result.error('Database transaction failed: ${e.message}');
    } catch (e) {
      return Result.error('Batch operation failed: $e');
    }
  }

  // データベースの整合性チェック
  Future<Result<IntegrityReport, String>> checkDatabaseIntegrity() async {
    try {
      final report = IntegrityReport();

      // ユーザーデータの整合性チェック
      final users = await isar.users.where().findAll();
      for (final user in users) {
        if (user.name.trim().isEmpty) {
          report.errors.add('User ${user.id}: Empty name');
        }

        if (!_isValidEmail(user.email)) {
          report.errors.add('User ${user.id}: Invalid email format');
        }

        if (user.age < 0 || user.age > 150) {
          report.errors.add('User ${user.id}: Invalid age ${user.age}');
        }
      }

      // 重複チェック
      final emailCounts = <String, int>{};
      for (final user in users) {
        emailCounts[user.email] = (emailCounts[user.email] ?? 0) + 1;
      }

      for (final entry in emailCounts.entries) {
        if (entry.value > 1) {
          report.errors.add('Duplicate email: ${entry.key} (${entry.value} occurrences)');
        }
      }

      report.totalUsers = users.length;
      report.checkCompleted = DateTime.now();

      return Result.success(report);

    } catch (e) {
      return Result.error('Integrity check failed: $e');
    }
  }

  // プライベートヘルパーメソッド
  String? _validateUserInput(String name, String email, int age) {
    if (name.trim().isEmpty) {
      return 'Name cannot be empty';
    }

    if (!_isValidEmail(email)) {
      return 'Invalid email format';
    }

    if (age < 0 || age > 150) {
      return 'Age must be between 0 and 150';
    }

    return null;
  }

  bool _isValidEmail(String email) {
    return RegExp(r'^[^@]+@[^@]+\.[^@]+$').hasMatch(email);
  }

  void _applyUpdates(User user, Map<String, dynamic> updates) {
    updates.forEach((key, value) {
      switch (key) {
        case 'name':
          user.name = value as String;
          break;
        case 'email':
          user.email = value as String;
          break;
        case 'age':
          user.age = value as int;
          break;
        case 'hobbies':
          user.hobbies = List<String>.from(value as List);
          break;
        case 'isActive':
          user.isActive = value as bool;
          break;
      }
    });
  }
}

// 結果型とエラーハンドリング用クラス
class Result<T, E> {
  final T? _value;
  final E? _error;

  const Result._(this._value, this._error);

  factory Result.success(T value) => Result._(value, null);
  factory Result.error(E error) => Result._(null, error);

  bool get isSuccess => _value != null;
  bool get isError => _error != null;

  T get value => _value!;
  E get error => _error!;

  R fold<R>(R Function(T value) onSuccess, R Function(E error) onError) {
    if (isSuccess) {
      return onSuccess(value);
    } else {
      return onError(error);
    }
  }
}

// バッチ操作関連クラス
enum BatchOperationType { create, update, delete }

class BatchOperation {
  final BatchOperationType type;
  final int? id;
  final dynamic data;

  BatchOperation.create(this.data) : type = BatchOperationType.create, id = null;
  BatchOperation.update(this.id, this.data) : type = BatchOperationType.update;
  BatchOperation.delete(this.id) : type = BatchOperationType.delete, data = null;
}

class BatchResult {
  final List<BatchOperation> successful = [];
  final List<FailedOperation> failed = [];
}

class FailedOperation {
  final BatchOperation operation;
  final String error;

  FailedOperation(this.operation, this.error);
}

class IntegrityReport {
  int totalUsers = 0;
  final List<String> errors = [];
  DateTime? checkCompleted;

  bool get hasErrors => errors.isNotEmpty;
  int get errorCount => errors.length;
}