Skip to main content

Chapter 4: Database Operations and Transactions

Overview

Vektagraf provides a comprehensive set of database operations built around an object-centric design philosophy. This chapter covers all database operations, from basic CRUD operations to advanced transaction management, with complete API coverage and practical examples. You'll learn how to leverage Vektagraf's fluent method chaining for complex queries, implement robust transaction handling, and optimize batch operations for maximum performance.

Learning Objectives

By the end of this chapter, you will be able to:

  • Perform all basic database operations (Create, Read, Update, Delete) using Vektagraf's native API
  • Implement robust transaction handling with proper isolation levels and error handling
  • Use fluent method chaining to build complex queries efficiently
  • Optimize batch operations for high-performance data processing
  • Handle concurrency conflicts and implement optimistic locking strategies
  • Monitor and optimize database operation performance

Prerequisites

  • Completed Chapter 3: Schema Design and Code Generation
  • Basic understanding of database concepts and ACID properties
  • Familiarity with Dart async/await patterns

Core Concepts

Object-Centric Operations

Vektagraf treats all data as objects with automatic metadata management. Every object stored in the database receives:

  • Unique ID: Generated using VektagrafId with timestamp, machine ID, and sequence
  • Revision Number: For optimistic concurrency control
  • Timestamps: Creation and last update times
  • Type Information: Automatically derived from Dart types
  • Relationship Metadata: Automatic graph edge creation

VektagrafList: The Core Interface

The VektagrafList<T> class extends Dart's ListBase<T> while adding database-specific operations. This design provides natural Dart collection behavior with powerful database capabilities:

// Get a typed list of objects from the database
final users = await database.objects<User>();

// Use familiar Dart list operations
print('Total users: ${users.length}');
for (final user in users) {
  print('User: ${user.name}');
}

// Chain database operations fluently
final activeEngineers = await users
    .where((u) => u.isActive)
    .whereProperty('department', 'Engineering')
    .orderByProperty('createdAt', descending: true)
    .take(10);

Transaction System Architecture

Vektagraf implements ACID transactions with the following characteristics:

  • Atomicity: All operations within a transaction succeed or fail together
  • Consistency: Database constraints are maintained across transactions
  • Isolation: Concurrent transactions don't interfere with each other
  • Durability: Committed changes survive system failures

Practical Examples

Basic CRUD Operations

Creating and Saving Objects

import 'package:vektagraf/vektagraf.dart';

// Define your data model
class User {
  final String name;
  final String email;
  final int age;
  final List<double> profileVector;
  final DateTime createdAt;
  
  User({
    required this.name,
    required this.email,
    required this.age,
    required this.profileVector,
    DateTime? createdAt,
  }) : createdAt = createdAt ?? DateTime.now();
}

Future<void> basicCrudExample() async {
  // Open database connection
  final database = VektagrafDatabaseImpl();
  await database.open('users.db');
  
  try {
    // Get typed list for User objects
    final users = await database.objects<User>();
    
    // Create new user
    final newUser = User(
      name: 'Alice Johnson',
      email: 'alice@example.com',
      age: 30,
      profileVector: [0.1, 0.2, 0.3, 0.4, 0.5],
    );
    
    // Save user and get generated ID
    final userId = await users.save(newUser);
    print('Created user with ID: $userId');
    
    // The user is now automatically added to the list
    print('Total users: ${users.length}');
    
    // Access metadata
    print('User revision: ${users.revisionOf(newUser)}');
    print('Created at: ${users.createdAt(newUser)}');
    
  } finally {
    await database.close();
  }
}

Reading and Querying Objects

Future<void> queryingExample() async {
  final database = VektagrafDatabaseImpl();
  await database.open('users.db');
  
  try {
    final users = await database.objects<User>();
    
    // Basic filtering with Dart predicates
    final youngUsers = users.where((user) => user.age < 30);
    print('Young users: ${youngUsers.length}');
    
    // Optimized property queries (uses indexes when available)
    final engineers = await users.whereProperty('department', 'Engineering');
    print('Engineers: ${engineers.length}');
    
    // Range queries
    final millennials = await users.wherePropertyRange<int>(
      'age', 25, 40
    );
    print('Millennials: ${millennials.length}');
    
    // Sorted queries with limits
    final recentUsers = await users.orderByProperty<DateTime>(
      'createdAt',
      descending: true,
      limit: 10,
    );
    print('Recent users: ${recentUsers.length}');
    
    // Complex chained queries
    final activeRecentEngineers = await users
        .whereProperty('department', 'Engineering')
        .whereProperty('isActive', true)
        .orderByProperty('createdAt', descending: true)
        .take(5);
    
    print('Active recent engineers: ${activeRecentEngineers.length}');
    
  } finally {
    await database.close();
  }
}

Updating Objects with Concurrency Control

Future<void> updateExample() async {
  final database = VektagrafDatabaseImpl();
  await database.open('users.db');
  
  try {
    final users = await database.objects<User>();
    
    // Find user to update
    final user = users.firstWhere((u) => u.email == 'alice@example.com');
    final currentRevision = users.currentRevision(user)!;
    
    // Create updated user object
    final updatedUser = User(
      name: user.name,
      email: user.email,
      age: user.age + 1, // Birthday!
      profileVector: user.profileVector,
      createdAt: user.createdAt,
    );
    
    try {
      // Update with concurrency control
      final newId = await users.update(updatedUser, currentRevision);
      print('Updated user, new revision: ${users.revisionOf(updatedUser)}');
      
    } on ConcurrencyConflictException catch (e) {
      print('Concurrency conflict: ${e.message}');
      print('Expected revision: ${e.expectedRevision}');
      print('Actual revision: ${e.actualRevision}');
      
      // Handle conflict - could retry, merge, or ask user
      await handleConcurrencyConflict(e, user, updatedUser);
    }
    
  } finally {
    await database.close();
  }
}

Future<void> handleConcurrencyConflict(
  ConcurrencyConflictException conflict,
  User originalUser,
  User updatedUser,
) async {
  // Example conflict resolution strategy
  print('Resolving conflict for user: ${originalUser.email}');
  
  // Could implement:
  // 1. Last-writer-wins
  // 2. Field-level merging
  // 3. User intervention
  // 4. Automatic retry with exponential backoff
  
  // For this example, we'll retry once
  await Future.delayed(Duration(milliseconds: 100));
  // ... retry logic here
}

Deleting Objects

Future<void> deleteExample() async {
  final database = VektagrafDatabaseImpl();
  await database.open('users.db');
  
  try {
    final users = await database.objects<User>();
    
    // Find user to delete
    final userToDelete = users.firstWhere((u) => u.email == 'old@example.com');
    
    // Remove from both list and database
    await users.removeObject(userToDelete);
    print('User deleted successfully');
    
    // Batch deletion with predicate
    await users.removeWhere((user) => user.age > 65);
    print('Removed users over 65');
    
    // The list automatically reflects the changes
    print('Remaining users: ${users.length}');
    
  } finally {
    await database.close();
  }
}

Advanced Transaction Management

Basic Transaction Usage

Future<void> basicTransactionExample() async {
  final database = VektagrafDatabaseImpl();
  await database.open('ecommerce.db');
  
  try {
    // Execute operations within a transaction
    await database.transaction((txn) async {
      // All operations use the same transaction context
      final orders = await txn.objects<Order>();
      final inventory = await txn.objects<InventoryItem>();
      
      // Create new order
      final order = Order(
        customerId: 'customer123',
        items: [
          OrderItem(productId: 'product456', quantity: 2),
          OrderItem(productId: 'product789', quantity: 1),
        ],
        total: 299.99,
      );
      
      final orderId = await txn.save(order);
      
      // Update inventory for each item
      for (final item in order.items) {
        final product = await txn.getById<InventoryItem>(
          VektagrafId.fromString(item.productId)
        );
        
        if (product == null) {
          throw Exception('Product not found: ${item.productId}');
        }
        
        if (product.quantity < item.quantity) {
          throw Exception('Insufficient inventory for ${item.productId}');
        }
        
        // Update inventory
        final updatedProduct = product.copyWith(
          quantity: product.quantity - item.quantity,
        );
        
        await txn.update(
          product.id,
          updatedProduct,
          product.revision,
        );
      }
      
      print('Order created successfully: $orderId');
      
      // Transaction automatically commits if no exceptions are thrown
    });
    
  } catch (e) {
    print('Transaction failed: $e');
    // Transaction automatically rolls back on exception
  } finally {
    await database.close();
  }
}

Complex Transaction with Error Handling

Future<void> complexTransactionExample() async {
  final database = VektagrafDatabaseImpl();
  await database.open('banking.db');
  
  try {
    final result = await database.transaction((txn) async {
      final accounts = await txn.objects<Account>();
      
      // Find source and destination accounts
      final sourceAccount = accounts.firstWhere(
        (a) => a.accountNumber == 'ACC001'
      );
      final destAccount = accounts.firstWhere(
        (a) => a.accountNumber == 'ACC002'
      );
      
      final transferAmount = 1000.0;
      
      // Validate sufficient funds
      if (sourceAccount.balance < transferAmount) {
        throw InsufficientFundsException(
          'Account ${sourceAccount.accountNumber} has insufficient funds'
        );
      }
      
      // Create transaction record
      final transaction = BankTransaction(
        id: VektagrafId.generate(),
        fromAccount: sourceAccount.accountNumber,
        toAccount: destAccount.accountNumber,
        amount: transferAmount,
        type: TransactionType.transfer,
        timestamp: DateTime.now(),
      );
      
      await txn.save(transaction);
      
      // Update account balances
      final updatedSource = sourceAccount.copyWith(
        balance: sourceAccount.balance - transferAmount,
        lastTransactionId: transaction.id,
      );
      
      final updatedDest = destAccount.copyWith(
        balance: destAccount.balance + transferAmount,
        lastTransactionId: transaction.id,
      );
      
      await txn.update(
        sourceAccount.id,
        updatedSource,
        sourceAccount.revision,
      );
      
      await txn.update(
        destAccount.id,
        updatedDest,
        destAccount.revision,
      );
      
      return TransferResult(
        transactionId: transaction.id,
        sourceBalance: updatedSource.balance,
        destBalance: updatedDest.balance,
      );
    });
    
    print('Transfer completed: ${result.transactionId}');
    print('New balances - Source: ${result.sourceBalance}, Dest: ${result.destBalance}');
    
  } on InsufficientFundsException catch (e) {
    print('Transfer failed: ${e.message}');
  } on ConcurrencyConflictException catch (e) {
    print('Concurrency conflict during transfer: ${e.message}');
    // Could implement retry logic here
  } catch (e) {
    print('Unexpected error during transfer: $e');
  } finally {
    await database.close();
  }
}

Batch Operations and Performance Optimization

High-Performance Batch Inserts

Future<void> batchInsertExample() async {
  final database = VektagrafDatabaseImpl();
  await database.open('analytics.db');
  
  try {
    final events = await database.objects<AnalyticsEvent>();
    
    // Generate sample data
    final batchData = List.generate(10000, (index) => AnalyticsEvent(
      userId: 'user_${index % 1000}',
      eventType: EventType.values[index % EventType.values.length],
      timestamp: DateTime.now().subtract(Duration(minutes: index)),
      properties: {
        'session_id': 'session_${index ~/ 100}',
        'page': '/page${index % 50}',
        'duration': index * 1.5,
      },
    ));
    
    print('Inserting ${batchData.length} events...');
    final stopwatch = Stopwatch()..start();
    
    // Method 1: Transaction-based batch insert (recommended)
    await events.saveAllInTransaction(batchData);
    
    stopwatch.stop();
    print('Batch insert completed in ${stopwatch.elapsedMilliseconds}ms');
    print('Rate: ${(batchData.length / stopwatch.elapsedMilliseconds * 1000).toInt()} events/second');
    
    // Method 2: Chunked batch processing for very large datasets
    await chunkedBatchInsert(events, batchData, chunkSize: 1000);
    
  } finally {
    await database.close();
  }
}

Future<void> chunkedBatchInsert<T>(
  VektagrafList<T> list,
  List<T> data,
  {int chunkSize = 1000}
) async {
  print('Processing ${data.length} items in chunks of $chunkSize...');
  
  for (int i = 0; i < data.length; i += chunkSize) {
    final chunk = data.skip(i).take(chunkSize);
    
    await list.saveAllInTransaction(chunk);
    
    print('Processed chunk ${(i ~/ chunkSize) + 1}/${(data.length / chunkSize).ceil()}');
    
    // Optional: Add small delay to prevent overwhelming the system
    if (i + chunkSize < data.length) {
      await Future.delayed(Duration(milliseconds: 10));
    }
  }
  
  print('Chunked batch insert completed');
}

Batch Updates with Concurrency Handling

Future<void> batchUpdateExample() async {
  final database = VektagrafDatabaseImpl();
  await database.open('users.db');
  
  try {
    final users = await database.objects<User>();
    
    // Find users to update
    final inactiveUsers = users.where((u) => !u.isActive).toList();
    print('Found ${inactiveUsers.length} inactive users to update');
    
    // Prepare updates with current revisions
    final updatesWithRevisions = <User, int>{};
    for (final user in inactiveUsers) {
      final currentRevision = users.currentRevision(user)!;
      final updatedUser = user.copyWith(
        isActive: true,
        lastLoginAt: DateTime.now(),
      );
      updatesWithRevisions[updatedUser] = currentRevision;
    }
    
    // Perform batch update with retry logic
    await batchUpdateWithRetry(users, updatesWithRevisions);
    
  } finally {
    await database.close();
  }
}

Future<void> batchUpdateWithRetry<T>(
  VektagrafList<T> list,
  Map<T, int> updatesWithRevisions,
  {int maxRetries = 3}
) async {
  var remainingUpdates = Map<T, int>.from(updatesWithRevisions);
  int retryCount = 0;
  
  while (remainingUpdates.isNotEmpty && retryCount < maxRetries) {
    final failedUpdates = <T, int>{};
    
    try {
      await list.updateAll(remainingUpdates);
      print('Batch update completed successfully');
      break;
      
    } on ConcurrencyConflictException catch (e) {
      print('Concurrency conflicts detected, retrying...');
      
      // Refresh revisions for failed objects and retry
      for (final entry in remainingUpdates.entries) {
        final object = entry.key;
        final currentRevision = list.currentRevision(object);
        if (currentRevision != null) {
          failedUpdates[object] = currentRevision;
        }
      }
      
      remainingUpdates = failedUpdates;
      retryCount++;
      
      // Exponential backoff
      await Future.delayed(Duration(milliseconds: 100 * (1 << retryCount)));
    }
  }
  
  if (remainingUpdates.isNotEmpty) {
    print('Warning: ${remainingUpdates.length} updates failed after $maxRetries retries');
  }
}

Fluent Method Chaining for Complex Queries

Building Complex Query Chains

Future<void> fluentQueryExample() async {
  final database = VektagrafDatabaseImpl();
  await database.open('social_network.db');
  
  try {
    final users = await database.objects<User>();
    final posts = await database.objects<Post>();
    
    // Complex query: Find active users who posted recently and have high engagement
    final influencers = await users
        .whereProperty('isActive', true)
        .whereProperty('isVerified', true)
        .wherePropertyRange<int>('followerCount', 1000, null)
        .whereAsync((user) async {
          // Check if user posted in last 7 days
          final recentPosts = await posts
              .whereProperty('authorId', user.id.toString())
              .wherePropertyRange<DateTime>(
                'createdAt',
                DateTime.now().subtract(Duration(days: 7)),
                null,
              );
          return recentPosts.isNotEmpty;
        })
        .orderByProperty<int>('followerCount', descending: true)
        .take(50);
    
    print('Found ${influencers.length} influencers');
    
    // Relationship-based queries
    final engagedUsers = await users
        .whereHasRelation('posts')
        .whereCountRelation('followers', 100)
        .expandRelation<Post>('posts')
        .whereProperty('likeCount', 50)
        .distinct();
    
    print('Found ${engagedUsers.length} highly engaged users');
    
    // Vector similarity queries (covered in detail in Chapter 5)
    final queryVector = [0.1, 0.2, 0.3, 0.4, 0.5];
    final similarUsers = await users
        .whereSimilar('profileVector', queryVector, limit: 20)
        .whereProperty('isActive', true);
    
    print('Found ${similarUsers.length} similar active users');
    
  } finally {
    await database.close();
  }
}

Optimized Query Execution

Future<void> queryOptimizationExample() async {
  final database = VektagrafDatabaseImpl();
  await database.open('ecommerce.db');
  
  try {
    final products = await database.objects<Product>();
    
    // The query optimizer automatically reorders operations for efficiency
    final optimizedQuery = await products.executeChainedQuery([
      QueryOperation(
        type: QueryOperationType.where,
        parameters: {'property': 'category', 'value': 'Electronics'},
      ),
      QueryOperation(
        type: QueryOperationType.where,
        parameters: {'property': 'inStock', 'value': true},
      ),
      QueryOperation(
        type: QueryOperationType.orderBy,
        parameters: {
          'property': 'price',
          'descending': false,
        },
      ),
      QueryOperation(
        type: QueryOperationType.take,
        parameters: {'count': 20},
      ),
    ]);
    
    print('Optimized query returned ${optimizedQuery.length} products');
    
    // Monitor query performance
    final optimizer = database.queryOptimizer;
    if (optimizer != null) {
      final stats = optimizer.queryStats;
      for (final entry in stats.entries) {
        final stat = entry.value;
        print('Query ${entry.key}:');
        print('  Executions: ${stat.totalExecutions}');
        print('  Avg time: ${stat.averageExecutionTime.inMilliseconds}ms');
        print('  Avg results: ${stat.averageResultCount.toInt()}');
      }
    }
    
  } finally {
    await database.close();
  }
}

Best Practices

Transaction Design Patterns

1. Keep Transactions Short and Focused

// ✅ Good: Short, focused transaction
Future<void> goodTransactionPattern() async {
  await database.transaction((txn) async {
    final account = await txn.getById<Account>(accountId);
    final updatedAccount = account.copyWith(
      balance: account.balance + amount,
    );
    await txn.update(account.id, updatedAccount, account.revision);
  });
}

// ❌ Bad: Long-running transaction with external calls
Future<void> badTransactionPattern() async {
  await database.transaction((txn) async {
    final account = await txn.getById<Account>(accountId);
    
    // Don't do this: external API call within transaction
    final exchangeRate = await fetchExchangeRateFromAPI();
    
    // Don't do this: complex calculations
    final convertedAmount = performComplexCalculation(amount, exchangeRate);
    
    final updatedAccount = account.copyWith(
      balance: account.balance + convertedAmount,
    );
    await txn.update(account.id, updatedAccount, account.revision);
  });
}

2. Handle Concurrency Conflicts Gracefully

Future<T> withRetryOnConflict<T>(
  Future<T> Function() operation,
  {int maxRetries = 3}
) async {
  int attempts = 0;
  
  while (attempts < maxRetries) {
    try {
      return await operation();
    } on ConcurrencyConflictException catch (e) {
      attempts++;
      if (attempts >= maxRetries) {
        rethrow;
      }
      
      // Exponential backoff with jitter
      final delay = Duration(
        milliseconds: (100 * (1 << attempts)) + Random().nextInt(50)
      );
      await Future.delayed(delay);
    }
  }
  
  throw StateError('Should not reach here');
}

// Usage
final result = await withRetryOnConflict(() async {
  return await database.transaction((txn) async {
    // Your transaction logic here
  });
});

3. Use Batch Operations for Better Performance

// ✅ Good: Batch operations within transaction
Future<void> efficientBatchOperation() async {
  await database.transaction((txn) async {
    final users = await txn.objects<User>();
    
    // Process in batches
    final batchSize = 1000;
    for (int i = 0; i < newUsers.length; i += batchSize) {
      final batch = newUsers.skip(i).take(batchSize);
      await users.saveAll(batch);
    }
  });
}

// ❌ Bad: Individual transactions for each operation
Future<void> inefficientOperation() async {
  for (final user in newUsers) {
    await database.transaction((txn) async {
      await txn.save(user);
    });
  }
}

Query Optimization Strategies

1. Use Property Queries for Indexed Fields

// ✅ Good: Uses indexes when available
final engineers = await users.whereProperty('department', 'Engineering');

// ❌ Less efficient: Full scan with predicate
final engineers = users.where((u) => u.department == 'Engineering');

2. Order Operations for Maximum Efficiency

// ✅ Good: Filter first, then sort and limit
final results = await users
    .whereProperty('isActive', true)        // Filter early
    .wherePropertyRange('age', 25, 65)      // Further filter
    .orderByProperty('createdAt')           // Sort reduced dataset
    .take(10);                              // Limit final results

// ❌ Less efficient: Sort everything first
final results = await users
    .orderByProperty('createdAt')           // Sorts all users
    .whereProperty('isActive', true)        // Filters sorted results
    .take(10);

3. Monitor and Analyze Query Performance

Future<void> monitorQueryPerformance() async {
  final stopwatch = Stopwatch()..start();
  
  final results = await users.whereProperty('department', 'Engineering');
  
  stopwatch.stop();
  
  if (stopwatch.elapsedMilliseconds > 100) {
    print('Slow query detected: ${stopwatch.elapsedMilliseconds}ms');
    print('Consider adding index for department field');
  }
  
  // Check query optimizer statistics
  final optimizer = database.queryOptimizer;
  if (optimizer != null) {
    final stats = optimizer.queryStats['property_department'];
    if (stats != null && stats.averageExecutionTime.inMilliseconds > 50) {
      print('Department queries are consistently slow');
      print('Average execution time: ${stats.averageExecutionTime.inMilliseconds}ms');
    }
  }
}

Error Handling and Recovery

1. Comprehensive Error Handling

Future<void> robustDatabaseOperation() async {
  try {
    await database.transaction((txn) async {
      // Your transaction logic here
    });
    
  } on ConcurrencyConflictException catch (e) {
    // Handle concurrency conflicts
    print('Concurrency conflict: ${e.message}');
    // Implement retry logic or conflict resolution
    
  } on StorageException catch (e) {
    // Handle storage-related errors
    print('Storage error: ${e.message}');
    // Implement recovery or fallback logic
    
  } on ValidationException catch (e) {
    // Handle validation errors
    print('Validation error: ${e.message}');
    // Return user-friendly error message
    
  } catch (e, stackTrace) {
    // Handle unexpected errors
    print('Unexpected error: $e');
    print('Stack trace: $stackTrace');
    // Log error for debugging
  }
}

2. Graceful Degradation

Future<List<User>> getUsersWithFallback() async {
  try {
    // Try optimized query first
    return await users.whereProperty('isActive', true);
    
  } on Exception catch (e) {
    print('Optimized query failed, falling back to simple query: $e');
    
    // Fallback to basic filtering
    return users.where((u) => u.isActive).toList();
  }
}

Advanced Topics

Custom Transaction Isolation

// Custom transaction wrapper with specific isolation behavior
class CustomTransactionManager {
  final VektagrafDatabase _database;
  
  CustomTransactionManager(this._database);
  
  Future<T> withReadCommitted<T>(
    Future<T> Function(VektagrafTransaction) operation
  ) async {
    // Implement read-committed isolation level
    return await _database.transaction((txn) async {
      // Custom isolation logic here
      return await operation(txn);
    });
  }
  
  Future<T> withSerializable<T>(
    Future<T> Function(VektagrafTransaction) operation
  ) async {
    // Implement serializable isolation level
    return await _database.transaction((txn) async {
      // Custom serialization logic here
      return await operation(txn);
    });
  }
}

Performance Monitoring and Metrics

class DatabaseMetrics {
  final Map<String, OperationStats> _operationStats = {};
  
  Future<T> measureOperation<T>(
    String operationName,
    Future<T> Function() operation,
  ) async {
    final stopwatch = Stopwatch()..start();
    
    try {
      final result = await operation();
      
      stopwatch.stop();
      _recordSuccess(operationName, stopwatch.elapsed);
      
      return result;
      
    } catch (e) {
      stopwatch.stop();
      _recordFailure(operationName, stopwatch.elapsed, e);
      rethrow;
    }
  }
  
  void _recordSuccess(String operation, Duration duration) {
    final stats = _operationStats.putIfAbsent(
      operation, 
      () => OperationStats(operation)
    );
    stats.recordSuccess(duration);
  }
  
  void _recordFailure(String operation, Duration duration, dynamic error) {
    final stats = _operationStats.putIfAbsent(
      operation, 
      () => OperationStats(operation)
    );
    stats.recordFailure(duration, error);
  }
  
  Map<String, OperationStats> get stats => Map.unmodifiable(_operationStats);
}

class OperationStats {
  final String operationName;
  int successCount = 0;
  int failureCount = 0;
  Duration totalDuration = Duration.zero;
  Duration maxDuration = Duration.zero;
  Duration minDuration = Duration(days: 1);
  
  OperationStats(this.operationName);
  
  void recordSuccess(Duration duration) {
    successCount++;
    totalDuration += duration;
    if (duration > maxDuration) maxDuration = duration;
    if (duration < minDuration) minDuration = duration;
  }
  
  void recordFailure(Duration duration, dynamic error) {
    failureCount++;
    totalDuration += duration;
  }
  
  double get successRate => 
      (successCount + failureCount) > 0 
          ? successCount / (successCount + failureCount) 
          : 0.0;
          
  Duration get averageDuration => 
      (successCount + failureCount) > 0 
          ? Duration(microseconds: totalDuration.inMicroseconds ~/ (successCount + failureCount))
          : Duration.zero;
}

Reference

Core Database Operations API

VektagrafDatabase Methods

abstract class VektagrafDatabase {
  // Connection management
  Future<void> open(String path, {VektagrafConfig? config});
  Future<void> close();
  
  // Object access
  Future<VektagrafList<T>> objects<T>();
  
  // Transaction management
  Future<R> transaction<R>(Future<R> Function(VektagrafTransaction) operation);
  
  // Graph and vector access
  VektagrafGraph get graph;
  VektagrafVectorSpace vectorSpace(String name, int dimensions);
  
  // Status and configuration
  bool get isOpen;
  VektagrafConfig? get config;
}

VektagrafTransaction Methods

abstract class VektagrafTransaction {
  // Object operations
  Future<List<T>> objects<T>();
  Future<VektagrafId> save<T>(T object);
  Future<VektagrafId> update<T>(VektagrafId id, T object, int expectedRevision);
  Future<List<VektagrafId>> saveAll<T>(Iterable<T> objects);
  Future<void> remove(VektagrafId id);
  Future<void> removeAll(Iterable<VektagrafId> ids);
  Future<T?> getById<T>(VektagrafId id);
  
  // Transaction control
  Future<void> commit();
  Future<void> rollback();
  
  // Status
  bool get isActive;
  bool get isCommitted;
  bool get isRolledBack;
}

VektagrafList Methods

class VektagrafList<T> extends ListBase<T> {
  // Database operations
  Future<VektagrafId> save(T object);
  Future<void> saveAll(Iterable<T> objects);
  Future<void> saveAllInTransaction(Iterable<T> objects);
  Future<VektagrafId> update(T object, int expectedRevision);
  Future<void> updateAll(Map<T, int> objectsWithRevisions);
  Future<void> removeObject(T object);
  Future<void> removeAllInTransaction(Iterable<T> objects);
  
  // Query operations
  Future<VektagrafList<T>> whereProperty(String propertyName, dynamic value);
  Future<VektagrafList<T>> wherePropertyRange<V extends Comparable<V>>(
    String propertyName, V? minValue, V? maxValue);
  Future<VektagrafList<T>> orderByProperty<V extends Comparable<V>>(
    String propertyName, {bool descending = false, int? limit});
  Future<VektagrafList<T>> executeChainedQuery(List<QueryOperation> operations);
  
  // Async operations
  Future<VektagrafList<T>> whereAsync(Future<bool> Function(T) test);
  Future<VektagrafList<R>> expandAsync<R>(Future<Iterable<R>> Function(T) f);
  Future<VektagrafList<T>> orderByAsync<K extends Comparable<dynamic>>(
    Future<K> Function(T) keyExtractor, {bool descending = false});
  
  // Relationship operations
  Future<VektagrafList<R>> expandRelation<R>(String relationName);
  Future<VektagrafList<T>> whereRelated<R>(String relationName, bool Function(R) relationTest);
  Future<VektagrafList<T>> whereHasRelation(String relationName);
  Future<VektagrafList<T>> whereCountRelation(String relationName, int count);
  
  // Vector operations
  Future<VektagrafList<T>> whereSimilar(String vectorField, List<double> queryVector, 
    {int limit = 10, double? threshold});
  Future<VektagrafList<T>> whereSimilarTo(T referenceObject, String vectorField, 
    {int limit = 10});
  
  // Metadata access
  VektagrafId? idOf(T object);
  int? revisionOf(T object);
  DateTime? createdAt(T object);
  DateTime? updatedAt(T object);
  int? currentRevision(T object);
  
  // Transaction support
  Future<R> withTransaction<R>(Future<R> Function(VektagrafList<T>) operation);
}

Configuration Options

class VektagrafConfig {
  // Performance settings
  final int maxMemoryBytes;
  final int maxConcurrentTransactions;
  final Duration transactionTimeout;
  
  // Concurrency settings
  final int maxRetryAttempts;
  final Duration baseRetryDelay;
  final bool enableAutoConflictResolution;
  
  // Query optimization
  final bool enableQueryCache;
  final int maxCacheSize;
  final Duration defaultCacheTtl;
  final bool enableQueryOptimization;
  
  // Storage settings
  final bool enableCompression;
  final bool enableEncryption;
  final Duration syncInterval;
}

Error Types and Handling

// Concurrency-related exceptions
class ConcurrencyConflictException implements Exception {
  final VektagrafId objectId;
  final int expectedRevision;
  final int actualRevision;
  final String message;
}

// Storage-related exceptions
class StorageException implements Exception {
  final String message;
  final dynamic cause;
}

// Validation exceptions
class ValidationException implements Exception {
  final String message;
  final Map<String, String> fieldErrors;
}

// Transaction exceptions
class TransactionException implements Exception {
  final String message;
  final TransactionState state;
}

Summary

This chapter covered the comprehensive database operations and transaction system in Vektagraf. Key takeaways include:

  • Object-Centric Design: All operations work with native Dart objects while providing automatic metadata management
  • Fluent API: VektagrafList provides natural Dart collection behavior with powerful database operations
  • ACID Transactions: Full transaction support with proper isolation, concurrency control, and error handling
  • Performance Optimization: Built-in query optimization, caching, and batch operation support
  • Concurrency Control: Optimistic locking with automatic conflict detection and resolution strategies

Next Steps

  • Chapter 5: Learn about vector search and similarity operations for AI/ML applications
  • Chapter 6: Explore graph operations and relationship modeling for complex data relationships
  • Chapter 7: Dive deep into query optimization and performance tuning techniques