Chapter 6: Graph Operations and Relationship Modeling
Overview
Graph operations and relationship modeling are fundamental to modern applications, enabling complex data relationships, social networks, recommendation systems, and knowledge graphs. Vektagraf provides a comprehensive graph system that seamlessly integrates with its object-centric design, allowing you to model and traverse relationships naturally while maintaining performance and security. This chapter covers graph concepts, relationship modeling patterns, traversal operations, and advanced graph algorithms.
Learning Objectives
By the end of this chapter, you will be able to:
- Model complex relationships using Vektagraf's graph system
- Perform efficient graph traversals and pathfinding operations
- Implement social network features with relationship-based queries
- Build recommendation systems using graph algorithms
- Optimize graph operations for performance and scalability
- Secure graph operations with relationship-level access control
- Analyze graph structures and extract insights from connected data
Prerequisites
- Completed Chapter 4: Database Operations and Transactions
- Completed Chapter 5: Vector Search and Similarity Operations
- Basic understanding of graph theory concepts (nodes, edges, paths)
- Familiarity with social network and recommendation system concepts
Core Concepts
Graph Model in Vektagraf
Vektagraf implements a property graph model where:
- Nodes represent entities with properties and labels
- Edges represent relationships with types and properties
- Labels categorize nodes (e.g., "User", "Product", "Document")
- Types categorize edges (e.g., "FRIENDS_WITH", "PURCHASED", "AUTHORED")
- Properties store additional data on both nodes and edges
Object-Graph Integration
Unlike traditional graph databases, Vektagraf automatically creates graph nodes for your objects:
// When you save an object, a graph node is automatically created
final user = User(
name: 'Alice Johnson',
email: 'alice@example.com',
interests: ['technology', 'music'],
);
final userId = await users.save(user);
// Graph node created automatically with labels: ['User']
// Properties: {name: 'Alice Johnson', email: 'alice@example.com', ...}
Relationship Types
Vektagraf supports various relationship patterns:
- One-to-One: User ↔ Profile
- One-to-Many: User → Posts
- Many-to-Many: Users ↔ Groups
- Hierarchical: Categories → Subcategories
- Temporal: Events → Timeline
- Weighted: Friendship strength, similarity scores
Practical Examples
Basic Graph Operations
Creating Nodes and Edges
import 'package:vektagraf/vektagraf.dart';
Future<void> basicGraphOperations() async {
final database = VektagrafDatabaseImpl();
await database.open('social_graph.db');
try {
final graph = database.graph;
// Create nodes with labels and properties
final aliceId = await graph.addNode({
'name': 'Alice Johnson',
'age': 30,
'city': 'San Francisco',
'occupation': 'Software Engineer',
}, labels: {'User', 'Employee'});
final bobId = await graph.addNode({
'name': 'Bob Smith',
'age': 28,
'city': 'New York',
'occupation': 'Data Scientist',
}, labels: {'User', 'Employee'});
final companyId = await graph.addNode({
'name': 'TechCorp Inc',
'industry': 'Technology',
'founded': 2010,
'employees': 5000,
}, labels: {'Company', 'Organization'});
print('Created nodes: Alice($aliceId), Bob($bobId), Company($companyId)');
// Create relationships between nodes
final friendshipId = await graph.addEdge(
aliceId,
bobId,
type: 'FRIENDS_WITH',
properties: {
'since': DateTime(2020, 1, 15),
'strength': 0.8,
'met_through': 'college',
},
);
final aliceEmploymentId = await graph.addEdge(
aliceId,
companyId,
type: 'WORKS_FOR',
properties: {
'position': 'Senior Software Engineer',
'start_date': DateTime(2021, 3, 1),
'department': 'Engineering',
'salary_band': 'L5',
},
);
final bobEmploymentId = await graph.addEdge(
bobId,
companyId,
type: 'WORKS_FOR',
properties: {
'position': 'Data Scientist',
'start_date': DateTime(2022, 1, 15),
'department': 'Analytics',
'salary_band': 'L4',
},
);
print('Created relationships:');
print(' - Friendship: $friendshipId');
print(' - Alice employment: $aliceEmploymentId');
print(' - Bob employment: $bobEmploymentId');
// Query nodes and edges
final aliceNode = await graph.getNode(aliceId);
print('\nAlice node:');
print(' Labels: ${aliceNode?.labels}');
print(' Properties: ${aliceNode?.properties}');
final friendshipEdge = await graph.getEdge(friendshipId);
print('\nFriendship edge:');
print(' Type: ${friendshipEdge?.type}');
print(' Properties: ${friendshipEdge?.properties}');
} finally {
await database.close();
}
}
Graph Traversal Operations
Future<void> graphTraversalExample() async {
final database = VektagrafDatabaseImpl();
await database.open('social_graph.db');
try {
final graph = database.graph;
// Create a more complex social network
final users = <VektagrafId>[];
final userNames = ['Alice', 'Bob', 'Carol', 'David', 'Eve', 'Frank'];
// Create user nodes
for (final name in userNames) {
final userId = await graph.addNode({
'name': name,
'age': 25 + Random().nextInt(15),
'interests': _generateInterests(),
'join_date': DateTime.now().subtract(Duration(days: Random().nextInt(365))),
}, labels: {'User'});
users.add(userId);
}
// Create friendship network
final friendships = <String>[];
for (int i = 0; i < users.length; i++) {
for (int j = i + 1; j < users.length; j++) {
// Create some friendships (not everyone is friends with everyone)
if (Random().nextDouble() < 0.4) {
await graph.addEdge(
users[i],
users[j],
type: 'FRIENDS_WITH',
properties: {
'since': DateTime.now().subtract(Duration(days: Random().nextInt(200))),
'strength': Random().nextDouble(),
},
);
friendships.add('${userNames[i]} ↔ ${userNames[j]}');
}
}
}
print('Created social network:');
print(' Users: ${userNames.join(', ')}');
print(' Friendships: ${friendships.join(', ')}');
// Breadth-first traversal from Alice
print('\n🔍 Breadth-first traversal from Alice:');
final aliceId = users[0]; // Alice is first
await for (final node in graph.traverse(aliceId)) {
final name = node.getProperty<String>('name');
final age = node.getProperty<int>('age');
print(' - $name (age $age)');
}
// Find shortest path between two users
print('\n🛤️ Shortest path from Alice to Frank:');
final frankId = users[5]; // Frank is last
final shortestPath = await graph.shortestPath(aliceId, frankId);
if (shortestPath != null) {
final pathNames = shortestPath
.map((node) => node.getProperty<String>('name'))
.join(' → ');
print(' Path: $pathNames (${shortestPath.length} hops)');
} else {
print(' No path found between Alice and Frank');
}
// Find all users with specific labels
print('\n👥 All users in the network:');
final allUsers = await graph.getNodesByLabel('User');
for (final user in allUsers) {
final name = user.getProperty<String>('name');
final interests = user.getProperty<List<dynamic>>('interests');
print(' - $name: ${interests?.join(', ')}');
}
// Find all friendship edges
print('\n🤝 All friendships:');
final friendshipEdges = await graph.getEdgesByType('FRIENDS_WITH');
for (final edge in friendshipEdges) {
final fromNode = await edge.fromNode;
final toNode = await edge.toNode;
final strength = edge.getProperty<double>('strength');
print(' - ${fromNode?.getProperty('name')} ↔ ${toNode?.getProperty('name')} '
'(strength: ${strength?.toStringAsFixed(2)})');
}
} finally {
await database.close();
}
}
List<String> _generateInterests() {
final allInterests = [
'technology', 'music', 'sports', 'reading', 'travel', 'cooking',
'photography', 'gaming', 'art', 'fitness', 'movies', 'science'
];
final count = 2 + Random().nextInt(4); // 2-5 interests
final selected = <String>[];
while (selected.length < count) {
final interest = allInterests[Random().nextInt(allInterests.length)];
if (!selected.contains(interest)) {
selected.add(interest);
}
}
return selected;
}
Advanced Relationship Modeling
Social Network Implementation
class SocialNetworkGraph {
final VektagrafDatabase database;
late VektagrafGraph graph;
SocialNetworkGraph(this.database) {
graph = database.graph;
}
/// Creates a user profile with automatic graph node creation
Future<VektagrafId> createUser({
required String username,
required String email,
required String displayName,
String? bio,
List<String>? interests,
Map<String, dynamic>? metadata,
}) async {
return await graph.addNode({
'username': username,
'email': email,
'display_name': displayName,
'bio': bio ?? '',
'interests': interests ?? [],
'created_at': DateTime.now().toIso8601String(),
'last_active': DateTime.now().toIso8601String(),
'follower_count': 0,
'following_count': 0,
'post_count': 0,
...?metadata,
}, labels: {'User', 'Profile'});
}
/// Creates a follow relationship between users
Future<VektagrafId> followUser(VektagrafId followerId, VektagrafId followeeId) async {
// Check if relationship already exists
final existingFollow = await _findExistingRelationship(
followerId, followeeId, 'FOLLOWS'
);
if (existingFollow != null) {
throw StateError('User is already following this person');
}
// Create follow relationship
final followEdgeId = await graph.addEdge(
followerId,
followeeId,
type: 'FOLLOWS',
properties: {
'created_at': DateTime.now().toIso8601String(),
'notification_enabled': true,
},
);
// Update follower/following counts
await _updateFollowCounts(followerId, followeeId);
return followEdgeId;
}
/// Creates a mutual friendship (bidirectional relationship)
Future<List<VektagrafId>> createFriendship(
VektagrafId user1Id,
VektagrafId user2Id,
) async {
final friendshipData = {
'created_at': DateTime.now().toIso8601String(),
'strength': 1.0,
'interaction_count': 0,
};
// Create bidirectional friendship edges
final edge1 = await graph.addEdge(
user1Id, user2Id,
type: 'FRIENDS_WITH',
properties: friendshipData,
);
final edge2 = await graph.addEdge(
user2Id, user1Id,
type: 'FRIENDS_WITH',
properties: friendshipData,
);
return [edge1, edge2];
}
/// Gets a user's social network (friends and followers)
Future<SocialNetwork> getUserSocialNetwork(VektagrafId userId) async {
final user = await graph.getNode(userId);
if (user == null) {
throw ArgumentError('User not found: $userId');
}
// Get friends (bidirectional FRIENDS_WITH relationships)
final friends = <VektagrafNode>[];
await for (final friend in user.connectedNodesOfType('FRIENDS_WITH')) {
friends.add(friend);
}
// Get followers (incoming FOLLOWS relationships)
final followers = <VektagrafNode>[];
final incomingFollows = await user.incomingOfType('FOLLOWS');
for (final edge in incomingFollows) {
final follower = await edge.fromNode;
if (follower != null) followers.add(follower);
}
// Get following (outgoing FOLLOWS relationships)
final following = <VektagrafNode>[];
final outgoingFollows = await user.outgoingOfType('FOLLOWS');
for (final edge in outgoingFollows) {
final followee = await edge.toNode;
if (followee != null) following.add(followee);
}
return SocialNetwork(
user: user,
friends: friends,
followers: followers,
following: following,
);
}
/// Finds mutual friends between two users
Future<List<VektagrafNode>> findMutualFriends(
VektagrafId user1Id,
VektagrafId user2Id,
) async {
final user1Network = await getUserSocialNetwork(user1Id);
final user2Network = await getUserSocialNetwork(user2Id);
final user1FriendIds = user1Network.friends.map((f) => f.id).toSet();
final user2FriendIds = user2Network.friends.map((f) => f.id).toSet();
final mutualFriendIds = user1FriendIds.intersection(user2FriendIds);
final mutualFriends = <VektagrafNode>[];
for (final friendId in mutualFriendIds) {
final friend = await graph.getNode(friendId);
if (friend != null) mutualFriends.add(friend);
}
return mutualFriends;
}
/// Suggests friends based on mutual connections and interests
Future<List<FriendSuggestion>> suggestFriends(
VektagrafId userId, {
int limit = 10,
}) async {
final userNetwork = await getUserSocialNetwork(userId);
final user = userNetwork.user;
final userInterests = user.getProperty<List<dynamic>>('interests') ?? [];
final suggestions = <FriendSuggestion>[];
final processedUsers = <VektagrafId>{userId};
// Add current friends to processed set
for (final friend in userNetwork.friends) {
processedUsers.add(friend.id);
}
// Find friends of friends
for (final friend in userNetwork.friends) {
final friendNetwork = await getUserSocialNetwork(friend.id);
for (final friendOfFriend in friendNetwork.friends) {
if (processedUsers.contains(friendOfFriend.id)) continue;
processedUsers.add(friendOfFriend.id);
// Calculate suggestion score
final mutualFriends = await findMutualFriends(userId, friendOfFriend.id);
final friendInterests = friendOfFriend.getProperty<List<dynamic>>('interests') ?? [];
final commonInterests = userInterests
.where((interest) => friendInterests.contains(interest))
.length;
final score = (mutualFriends.length * 0.7) + (commonInterests * 0.3);
if (score > 0) {
suggestions.add(FriendSuggestion(
user: friendOfFriend,
score: score,
mutualFriends: mutualFriends,
commonInterests: commonInterests,
reason: _generateSuggestionReason(mutualFriends.length, commonInterests),
));
}
}
}
// Sort by score and return top suggestions
suggestions.sort((a, b) => b.score.compareTo(a.score));
return suggestions.take(limit).toList();
}
/// Analyzes the social graph structure
Future<SocialGraphAnalytics> analyzeSocialGraph() async {
final allUsers = await graph.getNodesByLabel('User');
final allFriendships = await graph.getEdgesByType('FRIENDS_WITH');
final allFollows = await graph.getEdgesByType('FOLLOWS');
// Calculate network density
final maxPossibleEdges = allUsers.length * (allUsers.length - 1);
final actualEdges = allFriendships.length + allFollows.length;
final density = maxPossibleEdges > 0 ? actualEdges / maxPossibleEdges : 0.0;
// Find most connected users
final userConnections = <VektagrafId, int>{};
for (final user in allUsers) {
final network = await getUserSocialNetwork(user.id);
final totalConnections = network.friends.length +
network.followers.length +
network.following.length;
userConnections[user.id] = totalConnections;
}
final sortedUsers = userConnections.entries.toList()
..sort((a, b) => b.value.compareTo(a.value));
final topInfluencers = <UserInfluence>[];
for (final entry in sortedUsers.take(10)) {
final user = await graph.getNode(entry.key);
if (user != null) {
topInfluencers.add(UserInfluence(
user: user,
connectionCount: entry.value,
influenceScore: _calculateInfluenceScore(user, entry.value),
));
}
}
return SocialGraphAnalytics(
totalUsers: allUsers.length,
totalFriendships: allFriendships.length ~/ 2, // Bidirectional
totalFollows: allFollows.length,
networkDensity: density,
averageConnections: userConnections.values.isEmpty
? 0.0
: userConnections.values.reduce((a, b) => a + b) / userConnections.length,
topInfluencers: topInfluencers,
);
}
// Helper methods
Future<VektagrafEdge?> _findExistingRelationship(
VektagrafId fromId,
VektagrafId toId,
String type,
) async {
final fromNode = await graph.getNode(fromId);
if (fromNode == null) return null;
final outgoingEdges = await fromNode.outgoingOfType(type);
for (final edge in outgoingEdges) {
if (edge.toId == toId) return edge;
}
return null;
}
Future<void> _updateFollowCounts(VektagrafId followerId, VektagrafId followeeId) async {
final follower = await graph.getNode(followerId);
final followee = await graph.getNode(followeeId);
if (follower != null) {
final currentCount = follower.getProperty<int>('following_count') ?? 0;
await follower.setProperty('following_count', currentCount + 1);
}
if (followee != null) {
final currentCount = followee.getProperty<int>('follower_count') ?? 0;
await followee.setProperty('follower_count', currentCount + 1);
}
}
String _generateSuggestionReason(int mutualFriends, int commonInterests) {
if (mutualFriends > 0 && commonInterests > 0) {
return '$mutualFriends mutual friends, $commonInterests shared interests';
} else if (mutualFriends > 0) {
return '$mutualFriends mutual friends';
} else if (commonInterests > 0) {
return '$commonInterests shared interests';
} else {
return 'Suggested based on network analysis';
}
}
double _calculateInfluenceScore(VektagrafNode user, int connections) {
// Simple influence calculation - could be more sophisticated
final followerCount = user.getProperty<int>('follower_count') ?? 0;
final postCount = user.getProperty<int>('post_count') ?? 0;
return (followerCount * 0.5) + (connections * 0.3) + (postCount * 0.2);
}
}
// Supporting classes
class SocialNetwork {
final VektagrafNode user;
final List<VektagrafNode> friends;
final List<VektagrafNode> followers;
final List<VektagrafNode> following;
SocialNetwork({
required this.user,
required this.friends,
required this.followers,
required this.following,
});
}
class FriendSuggestion {
final VektagrafNode user;
final double score;
final List<VektagrafNode> mutualFriends;
final int commonInterests;
final String reason;
FriendSuggestion({
required this.user,
required this.score,
required this.mutualFriends,
required this.commonInterests,
required this.reason,
});
}
class UserInfluence {
final VektagrafNode user;
final int connectionCount;
final double influenceScore;
UserInfluence({
required this.user,
required this.connectionCount,
required this.influenceScore,
});
}
class SocialGraphAnalytics {
final int totalUsers;
final int totalFriendships;
final int totalFollows;
final double networkDensity;
final double averageConnections;
final List<UserInfluence> topInfluencers;
SocialGraphAnalytics({
required this.totalUsers,
required this.totalFriendships,
required this.totalFollows,
required this.networkDensity,
required this.averageConnections,
required this.topInfluencers,
});
}
```#
### Content Recommendation System
```dart
class ContentRecommendationGraph {
final VektagrafDatabase database;
late VektagrafGraph graph;
ContentRecommendationGraph(this.database) {
graph = database.graph;
}
/// Creates a content item with automatic categorization
Future<VektagrafId> createContent({
required String title,
required String content,
required String contentType,
required VektagrafId authorId,
List<String>? tags,
List<String>? categories,
Map<String, dynamic>? metadata,
}) async {
final contentId = await graph.addNode({
'title': title,
'content': content,
'content_type': contentType,
'author_id': authorId.toString(),
'tags': tags ?? [],
'categories': categories ?? [],
'created_at': DateTime.now().toIso8601String(),
'view_count': 0,
'like_count': 0,
'share_count': 0,
...?metadata,
}, labels: {'Content', contentType});
// Create authorship relationship
await graph.addEdge(
authorId,
contentId,
type: 'AUTHORED',
properties: {
'created_at': DateTime.now().toIso8601String(),
'role': 'primary_author',
},
);
// Create category relationships
if (categories != null) {
for (final category in categories) {
final categoryId = await _getOrCreateCategory(category);
await graph.addEdge(
contentId,
categoryId,
type: 'BELONGS_TO',
properties: {'relevance_score': 1.0},
);
}
}
return contentId;
}
/// Records user interaction with content
Future<void> recordInteraction({
required VektagrafId userId,
required VektagrafId contentId,
required String interactionType,
double? duration,
double? rating,
Map<String, dynamic>? metadata,
}) async {
// Create or update interaction edge
final existingInteraction = await _findExistingInteraction(userId, contentId);
if (existingInteraction != null) {
// Update existing interaction
final currentCount = existingInteraction.getProperty<int>('interaction_count') ?? 0;
await existingInteraction.setProperty('interaction_count', currentCount + 1);
await existingInteraction.setProperty('last_interaction', DateTime.now().toIso8601String());
if (duration != null) {
final totalDuration = existingInteraction.getProperty<double>('total_duration') ?? 0.0;
await existingInteraction.setProperty('total_duration', totalDuration + duration);
}
if (rating != null) {
await existingInteraction.setProperty('rating', rating);
}
} else {
// Create new interaction
await graph.addEdge(
userId,
contentId,
type: 'INTERACTED_WITH',
properties: {
'interaction_type': interactionType,
'first_interaction': DateTime.now().toIso8601String(),
'last_interaction': DateTime.now().toIso8601String(),
'interaction_count': 1,
'total_duration': duration ?? 0.0,
'rating': rating,
...?metadata,
},
);
}
// Update content engagement metrics
await _updateContentMetrics(contentId, interactionType);
}
/// Generates personalized content recommendations
Future<List<ContentRecommendation>> getPersonalizedRecommendations(
VektagrafId userId, {
int limit = 20,
List<String>? contentTypes,
double minScore = 0.1,
}) async {
final user = await graph.getNode(userId);
if (user == null) {
throw ArgumentError('User not found: $userId');
}
final recommendations = <ContentRecommendation>[];
final processedContent = <VektagrafId>{};
// Get user's interaction history
final userInteractions = await user.outgoingOfType('INTERACTED_WITH');
final interactedContentIds = userInteractions.map((e) => e.toId).toSet();
// Strategy 1: Collaborative filtering - users with similar tastes
final similarUsers = await _findSimilarUsers(userId);
for (final similarUser in similarUsers.take(10)) {
final similarUserInteractions = await similarUser.user.outgoingOfType('INTERACTED_WITH');
for (final interaction in similarUserInteractions) {
if (interactedContentIds.contains(interaction.toId) ||
processedContent.contains(interaction.toId)) continue;
final content = await interaction.toNode;
if (content == null) continue;
// Filter by content type if specified
if (contentTypes != null &&
!contentTypes.contains(content.getProperty<String>('content_type'))) continue;
final rating = interaction.getProperty<double>('rating') ?? 0.5;
final score = similarUser.similarity * rating * 0.7; // Collaborative filtering weight
if (score >= minScore) {
recommendations.add(ContentRecommendation(
content: content,
score: score,
reason: 'Users with similar interests liked this',
strategy: 'collaborative_filtering',
));
processedContent.add(content.id);
}
}
}
// Strategy 2: Content-based filtering - similar content categories
for (final interaction in userInteractions) {
final content = await interaction.toNode;
if (content == null) continue;
final rating = interaction.getProperty<double>('rating') ?? 0.5;
if (rating < 0.6) continue; // Only use positively rated content
// Find similar content through categories
final categoryEdges = await content.outgoingOfType('BELONGS_TO');
for (final categoryEdge in categoryEdges) {
final category = await categoryEdge.toNode;
if (category == null) continue;
// Find other content in the same category
final categoryContent = await category.incomingOfType('BELONGS_TO');
for (final contentEdge in categoryContent) {
if (interactedContentIds.contains(contentEdge.fromId) ||
processedContent.contains(contentEdge.fromId)) continue;
final similarContent = await contentEdge.fromNode;
if (similarContent == null) continue;
// Filter by content type if specified
if (contentTypes != null &&
!contentTypes.contains(similarContent.getProperty<String>('content_type'))) continue;
final relevanceScore = categoryEdge.getProperty<double>('relevance_score') ?? 1.0;
final score = rating * relevanceScore * 0.5; // Content-based weight
if (score >= minScore) {
recommendations.add(ContentRecommendation(
content: similarContent,
score: score,
reason: 'Similar to content you liked in ${category.getProperty<String>('name')}',
strategy: 'content_based',
));
processedContent.add(similarContent.id);
}
}
}
}
// Strategy 3: Trending content - popular among similar users
final trendingContent = await _getTrendingContent(userId, contentTypes);
for (final trending in trendingContent) {
if (interactedContentIds.contains(trending.content.id) ||
processedContent.contains(trending.content.id)) continue;
if (trending.score >= minScore) {
recommendations.add(ContentRecommendation(
content: trending.content,
score: trending.score * 0.3, // Trending weight
reason: 'Trending among users like you',
strategy: 'trending',
));
processedContent.add(trending.content.id);
}
}
// Sort by score and return top recommendations
recommendations.sort((a, b) => b.score.compareTo(a.score));
return recommendations.take(limit).toList();
}
/// Finds content discovery paths through the graph
Future<List<ContentPath>> discoverContentPaths(
VektagrafId userId,
VektagrafId targetContentId, {
int maxDepth = 4,
}) async {
final paths = <ContentPath>[];
// Find paths through different relationship types
await _findPathsRecursive(
userId,
targetContentId,
<VektagrafId>{},
[userId],
paths,
maxDepth,
['INTERACTED_WITH', 'AUTHORED', 'BELONGS_TO', 'FRIENDS_WITH'],
);
// Score and rank paths
for (final path in paths) {
path.score = _calculatePathScore(path);
}
paths.sort((a, b) => b.score.compareTo(a.score));
return paths;
}
/// Analyzes content performance and engagement patterns
Future<ContentAnalytics> analyzeContentPerformance(VektagrafId contentId) async {
final content = await graph.getNode(contentId);
if (content == null) {
throw ArgumentError('Content not found: $contentId');
}
// Get all interactions with this content
final interactions = await content.incomingOfType('INTERACTED_WITH');
final analytics = ContentAnalytics(
contentId: contentId,
title: content.getProperty<String>('title') ?? 'Unknown',
totalInteractions: interactions.length,
uniqueUsers: interactions.map((i) => i.fromId).toSet().length,
averageRating: 0.0,
totalDuration: 0.0,
interactionTypes: {},
engagementOverTime: [],
);
// Calculate metrics
double totalRating = 0.0;
int ratedInteractions = 0;
for (final interaction in interactions) {
final rating = interaction.getProperty<double>('rating');
if (rating != null) {
totalRating += rating;
ratedInteractions++;
}
final duration = interaction.getProperty<double>('total_duration') ?? 0.0;
analytics.totalDuration += duration;
final interactionType = interaction.getProperty<String>('interaction_type') ?? 'unknown';
analytics.interactionTypes[interactionType] =
(analytics.interactionTypes[interactionType] ?? 0) + 1;
}
analytics.averageRating = ratedInteractions > 0 ? totalRating / ratedInteractions : 0.0;
return analytics;
}
// Helper methods
Future<VektagrafId> _getOrCreateCategory(String categoryName) async {
final existingCategories = await graph.getNodesByLabel('Category');
for (final category in existingCategories) {
if (category.getProperty<String>('name') == categoryName) {
return category.id;
}
}
// Create new category
return await graph.addNode({
'name': categoryName,
'created_at': DateTime.now().toIso8601String(),
'content_count': 0,
}, labels: {'Category'});
}
Future<VektagrafEdge?> _findExistingInteraction(
VektagrafId userId,
VektagrafId contentId,
) async {
final user = await graph.getNode(userId);
if (user == null) return null;
final interactions = await user.outgoingOfType('INTERACTED_WITH');
for (final interaction in interactions) {
if (interaction.toId == contentId) return interaction;
}
return null;
}
Future<void> _updateContentMetrics(VektagrafId contentId, String interactionType) async {
final content = await graph.getNode(contentId);
if (content == null) return;
switch (interactionType) {
case 'view':
final currentCount = content.getProperty<int>('view_count') ?? 0;
await content.setProperty('view_count', currentCount + 1);
break;
case 'like':
final currentCount = content.getProperty<int>('like_count') ?? 0;
await content.setProperty('like_count', currentCount + 1);
break;
case 'share':
final currentCount = content.getProperty<int>('share_count') ?? 0;
await content.setProperty('share_count', currentCount + 1);
break;
}
}
Future<List<SimilarUser>> _findSimilarUsers(VektagrafId userId) async {
// Simplified similarity calculation based on common interactions
final user = await graph.getNode(userId);
if (user == null) return [];
final userInteractions = await user.outgoingOfType('INTERACTED_WITH');
final userContentIds = userInteractions.map((i) => i.toId).toSet();
final allUsers = await graph.getNodesByLabel('User');
final similarUsers = <SimilarUser>[];
for (final otherUser in allUsers) {
if (otherUser.id == userId) continue;
final otherInteractions = await otherUser.outgoingOfType('INTERACTED_WITH');
final otherContentIds = otherInteractions.map((i) => i.toId).toSet();
final commonContent = userContentIds.intersection(otherContentIds);
final totalContent = userContentIds.union(otherContentIds);
if (totalContent.isNotEmpty) {
final similarity = commonContent.length / totalContent.length;
if (similarity > 0.1) {
similarUsers.add(SimilarUser(
user: otherUser,
similarity: similarity,
commonInteractions: commonContent.length,
));
}
}
}
similarUsers.sort((a, b) => b.similarity.compareTo(a.similarity));
return similarUsers;
}
Future<List<TrendingContent>> _getTrendingContent(
VektagrafId userId,
List<String>? contentTypes,
) async {
final allContent = await graph.getNodesByLabel('Content');
final trending = <TrendingContent>[];
for (final content in allContent) {
if (contentTypes != null &&
!contentTypes.contains(content.getProperty<String>('content_type'))) continue;
final viewCount = content.getProperty<int>('view_count') ?? 0;
final likeCount = content.getProperty<int>('like_count') ?? 0;
final shareCount = content.getProperty<int>('share_count') ?? 0;
// Simple trending score calculation
final score = (viewCount * 0.1) + (likeCount * 0.5) + (shareCount * 1.0);
if (score > 0) {
trending.add(TrendingContent(
content: content,
score: score,
viewCount: viewCount,
likeCount: likeCount,
shareCount: shareCount,
));
}
}
trending.sort((a, b) => b.score.compareTo(a.score));
return trending;
}
Future<void> _findPathsRecursive(
VektagrafId currentId,
VektagrafId targetId,
Set<VektagrafId> visited,
List<VektagrafId> currentPath,
List<ContentPath> allPaths,
int maxDepth,
List<String> allowedEdgeTypes,
) async {
if (currentPath.length > maxDepth) return;
if (currentId == targetId) {
allPaths.add(ContentPath(
path: List.from(currentPath),
score: 0.0, // Will be calculated later
));
return;
}
visited.add(currentId);
final node = await graph.getNode(currentId);
if (node != null) {
for (final edgeType in allowedEdgeTypes) {
final edges = await node.outgoingOfType(edgeType);
for (final edge in edges) {
if (!visited.contains(edge.toId)) {
await _findPathsRecursive(
edge.toId,
targetId,
Set.from(visited),
[...currentPath, edge.toId],
allPaths,
maxDepth,
allowedEdgeTypes,
);
}
}
}
}
}
double _calculatePathScore(ContentPath path) {
// Simple path scoring - shorter paths with more interactions score higher
final lengthPenalty = 1.0 / path.path.length;
return lengthPenalty; // Could be more sophisticated
}
}
// Supporting classes for content recommendation
class ContentRecommendation {
final VektagrafNode content;
final double score;
final String reason;
final String strategy;
ContentRecommendation({
required this.content,
required this.score,
required this.reason,
required this.strategy,
});
}
class SimilarUser {
final VektagrafNode user;
final double similarity;
final int commonInteractions;
SimilarUser({
required this.user,
required this.similarity,
required this.commonInteractions,
});
}
class TrendingContent {
final VektagrafNode content;
final double score;
final int viewCount;
final int likeCount;
final int shareCount;
TrendingContent({
required this.content,
required this.score,
required this.viewCount,
required this.likeCount,
required this.shareCount,
});
}
class ContentPath {
final List<VektagrafId> path;
double score;
ContentPath({
required this.path,
required this.score,
});
}
class ContentAnalytics {
final VektagrafId contentId;
final String title;
final int totalInteractions;
final int uniqueUsers;
double averageRating;
double totalDuration;
final Map<String, int> interactionTypes;
final List<EngagementPoint> engagementOverTime;
ContentAnalytics({
required this.contentId,
required this.title,
required this.totalInteractions,
required this.uniqueUsers,
required this.averageRating,
required this.totalDuration,
required this.interactionTypes,
required this.engagementOverTime,
});
}
class EngagementPoint {
final DateTime timestamp;
final int interactions;
final double averageRating;
EngagementPoint({
required this.timestamp,
required this.interactions,
required this.averageRating,
});
}
```###
Integration with VektagrafList
#### Object-Relationship Queries
```dart
Future<void> objectRelationshipQueries() async {
final database = VektagrafDatabaseImpl();
await database.open('integrated_graph.db');
try {
// Create objects that automatically become graph nodes
final users = await database.objects<User>();
final posts = await database.objects<Post>();
final comments = await database.objects<Comment>();
// Create users
final alice = User(
name: 'Alice Johnson',
email: 'alice@example.com',
bio: 'Software engineer passionate about technology',
interests: ['programming', 'AI', 'music'],
);
final bob = User(
name: 'Bob Smith',
email: 'bob@example.com',
bio: 'Data scientist and machine learning enthusiast',
interests: ['data science', 'AI', 'photography'],
);
final aliceId = await users.save(alice);
final bobId = await users.save(bob);
// Create posts
final alicePost = Post(
title: 'Introduction to Graph Databases',
content: 'Graph databases are powerful for modeling relationships...',
authorId: aliceId,
tags: ['database', 'graph', 'technology'],
);
final bobPost = Post(
title: 'Machine Learning with Graphs',
content: 'Graph neural networks are revolutionizing ML...',
authorId: bobId,
tags: ['ML', 'graph', 'AI'],
);
final alicePostId = await posts.save(alicePost);
final bobPostId = await posts.save(bobPost);
// Create comments (relationships)
final comment = Comment(
content: 'Great post! Very informative.',
authorId: bobId,
postId: alicePostId,
);
await comments.save(comment);
// Query using relationship traversal through VektagrafList
print('=== Relationship-based Queries ===');
// Find all posts by users interested in AI
final aiPosts = await users
.where((user) => user.interests.contains('AI'))
.expandRelation<Post>('authored') // Traverse authorship relationship
.toList();
print('Posts by AI enthusiasts:');
for (final post in aiPosts) {
print(' - "${post.title}" by ${post.author?.name}');
}
// Find users who commented on Alice's posts
final alicePostCommenters = await posts
.where((post) => post.authorId == aliceId)
.expandRelation<Comment>('has_comments') // Traverse to comments
.expandRelation<User>('authored_by') // Traverse to comment authors
.distinct()
.toList();
print('\nUsers who commented on Alice\'s posts:');
for (final user in alicePostCommenters) {
print(' - ${user.name}');
}
// Find posts with similar tags (graph-based similarity)
final graphTaggedPosts = await posts
.where((post) => post.tags.contains('graph'))
.toList();
print('\nPosts tagged with "graph":');
for (final post in graphTaggedPosts) {
print(' - "${post.title}" (tags: ${post.tags.join(', ')})');
}
// Complex multi-hop relationship query
final relatedContent = await users
.where((user) => user.name == 'Alice Johnson')
.expandRelation<Post>('authored') // Alice's posts
.expandRelation<Comment>('has_comments') // Comments on her posts
.expandRelation<User>('authored_by') // Comment authors
.expandRelation<Post>('authored') // Posts by comment authors
.where((post) => post.authorId != aliceId) // Exclude Alice's own posts
.distinct()
.toList();
print('\nContent by users who engaged with Alice:');
for (final post in relatedContent) {
print(' - "${post.title}" by ${post.author?.name}');
}
} finally {
await database.close();
}
}
// Example model classes with relationship annotations
class User {
final String name;
final String email;
final String bio;
final List<String> interests;
// Relationships (would be defined in schema)
final List<Post> authoredPosts;
final List<Comment> authoredComments;
final List<User> friends;
final List<User> followers;
User({
required this.name,
required this.email,
required this.bio,
required this.interests,
this.authoredPosts = const [],
this.authoredComments = const [],
this.friends = const [],
this.followers = const [],
});
}
class Post {
final String title;
final String content;
final VektagrafId authorId;
final List<String> tags;
// Relationships
final User? author;
final List<Comment> comments;
final List<User> likedBy;
Post({
required this.title,
required this.content,
required this.authorId,
required this.tags,
this.author,
this.comments = const [],
this.likedBy = const [],
});
}
class Comment {
final String content;
final VektagrafId authorId;
final VektagrafId postId;
// Relationships
final User? author;
final Post? post;
Comment({
required this.content,
required this.authorId,
required this.postId,
this.author,
this.post,
});
}
Best Practices
Graph Schema Design
Relationship Modeling Patterns
class GraphSchemaDesigner {
/// Designs optimal relationship structures for different use cases
static Map<String, dynamic> designSocialNetworkSchema() {
return {
'nodes': {
'User': {
'labels': ['User', 'Person'],
'properties': {
'username': 'string',
'email': 'string',
'display_name': 'string',
'bio': 'text',
'created_at': 'datetime',
'last_active': 'datetime',
},
'indexes': ['username', 'email', 'created_at'],
},
'Post': {
'labels': ['Post', 'Content'],
'properties': {
'title': 'string',
'content': 'text',
'created_at': 'datetime',
'view_count': 'integer',
'like_count': 'integer',
},
'indexes': ['created_at', 'view_count'],
},
'Group': {
'labels': ['Group', 'Community'],
'properties': {
'name': 'string',
'description': 'text',
'privacy_level': 'string',
'member_count': 'integer',
},
'indexes': ['name', 'privacy_level'],
},
},
'relationships': {
'FRIENDS_WITH': {
'description': 'Bidirectional friendship',
'properties': {
'since': 'datetime',
'strength': 'float',
'interaction_count': 'integer',
},
'constraints': ['bidirectional'],
},
'FOLLOWS': {
'description': 'Unidirectional follow relationship',
'properties': {
'since': 'datetime',
'notification_enabled': 'boolean',
},
},
'AUTHORED': {
'description': 'Content authorship',
'properties': {
'created_at': 'datetime',
'role': 'string', // primary, co-author, editor
},
},
'MEMBER_OF': {
'description': 'Group membership',
'properties': {
'joined_at': 'datetime',
'role': 'string', // member, admin, moderator
'status': 'string', // active, inactive, banned
},
},
'LIKED': {
'description': 'Content appreciation',
'properties': {
'liked_at': 'datetime',
'reaction_type': 'string', // like, love, laugh, etc.
},
},
},
};
}
/// Designs e-commerce recommendation schema
static Map<String, dynamic> designEcommerceSchema() {
return {
'nodes': {
'Customer': {
'labels': ['Customer', 'User'],
'properties': {
'customer_id': 'string',
'email': 'string',
'registration_date': 'datetime',
'total_orders': 'integer',
'lifetime_value': 'float',
},
},
'Product': {
'labels': ['Product', 'Item'],
'properties': {
'sku': 'string',
'name': 'string',
'description': 'text',
'price': 'float',
'category': 'string',
'brand': 'string',
'rating': 'float',
},
},
'Category': {
'labels': ['Category'],
'properties': {
'name': 'string',
'parent_category': 'string',
'level': 'integer',
},
},
'Brand': {
'labels': ['Brand'],
'properties': {
'name': 'string',
'country': 'string',
'established': 'integer',
},
},
},
'relationships': {
'PURCHASED': {
'description': 'Product purchase',
'properties': {
'order_date': 'datetime',
'quantity': 'integer',
'unit_price': 'float',
'total_amount': 'float',
'rating': 'float',
'review': 'text',
},
},
'VIEWED': {
'description': 'Product view/browse',
'properties': {
'viewed_at': 'datetime',
'duration': 'integer',
'source': 'string', // search, recommendation, category
},
},
'BELONGS_TO': {
'description': 'Product category membership',
'properties': {
'relevance_score': 'float',
},
},
'MANUFACTURED_BY': {
'description': 'Product brand relationship',
'properties': {
'since': 'datetime',
},
},
'SIMILAR_TO': {
'description': 'Product similarity',
'properties': {
'similarity_score': 'float',
'similarity_type': 'string', // visual, functional, price
},
},
},
};
}
}
Performance Optimization Strategies
class GraphPerformanceOptimizer {
final VektagrafGraph graph;
GraphPerformanceOptimizer(this.graph);
/// Optimizes graph queries using indexes and caching
Future<List<VektagrafNode>> optimizedTraversal({
required VektagrafId startNodeId,
required List<String> relationshipTypes,
int maxDepth = 3,
Map<String, dynamic>? nodeFilters,
Map<String, dynamic>? edgeFilters,
}) async {
// Use breadth-first traversal with early termination
final visited = <VektagrafId>{};
final queue = <TraversalState>[];
final results = <VektagrafNode>[];
queue.add(TraversalState(nodeId: startNodeId, depth: 0));
while (queue.isNotEmpty) {
final current = queue.removeAt(0);
if (current.depth > maxDepth || visited.contains(current.nodeId)) {
continue;
}
visited.add(current.nodeId);
final node = await graph.getNode(current.nodeId);
if (node != null) {
// Apply node filters
if (nodeFilters == null || _matchesFilters(node, nodeFilters)) {
results.add(node);
}
// Add connected nodes to queue
if (current.depth < maxDepth) {
for (final relationshipType in relationshipTypes) {
final edges = await node.outgoingOfType(relationshipType);
for (final edge in edges) {
// Apply edge filters
if (edgeFilters == null || _matchesEdgeFilters(edge, edgeFilters)) {
queue.add(TraversalState(
nodeId: edge.toId,
depth: current.depth + 1,
));
}
}
}
}
}
}
return results;
}
/// Implements efficient pathfinding with A* algorithm
Future<List<VektagrafNode>?> findOptimalPath(
VektagrafId startId,
VektagrafId goalId, {
double Function(VektagrafNode, VektagrafNode)? heuristic,
double Function(VektagrafEdge)? edgeCost,
}) async {
final openSet = PriorityQueue<PathNode>((a, b) => a.fScore.compareTo(b.fScore));
final closedSet = <VektagrafId>{};
final gScore = <VektagrafId, double>{};
final fScore = <VektagrafId, double>{};
final cameFrom = <VektagrafId, VektagrafId>{};
gScore[startId] = 0.0;
fScore[startId] = 0.0;
openSet.add(PathNode(nodeId: startId, fScore: 0.0));
while (openSet.isNotEmpty) {
final current = openSet.removeFirst();
if (current.nodeId == goalId) {
return _reconstructPath(cameFrom, goalId);
}
closedSet.add(current.nodeId);
final currentNode = await graph.getNode(current.nodeId);
if (currentNode != null) {
final outgoingEdges = await currentNode.outgoing;
for (final edge in outgoingEdges) {
if (closedSet.contains(edge.toId)) continue;
final tentativeGScore = (gScore[current.nodeId] ?? double.infinity) +
(edgeCost?.call(edge) ?? 1.0);
if (tentativeGScore < (gScore[edge.toId] ?? double.infinity)) {
cameFrom[edge.toId] = current.nodeId;
gScore[edge.toId] = tentativeGScore;
final goalNode = await graph.getNode(goalId);
final neighborNode = await graph.getNode(edge.toId);
final h = (heuristic != null && goalNode != null && neighborNode != null)
? heuristic(neighborNode, goalNode)
: 0.0;
fScore[edge.toId] = tentativeGScore + h;
openSet.add(PathNode(nodeId: edge.toId, fScore: fScore[edge.toId]!));
}
}
}
}
return null; // No path found
}
/// Implements community detection using Louvain algorithm
Future<Map<VektagrafId, int>> detectCommunities() async {
final allNodes = await graph.getAllNodes();
final communities = <VektagrafId, int>{};
// Initialize each node in its own community
for (int i = 0; i < allNodes.length; i++) {
communities[allNodes[i].id] = i;
}
bool improved = true;
int iteration = 0;
while (improved && iteration < 100) {
improved = false;
iteration++;
for (final node in allNodes) {
final currentCommunity = communities[node.id]!;
final neighborCommunities = <int, double>{};
// Calculate modularity gain for each neighbor community
final edges = await node.outgoing;
for (final edge in edges) {
final neighborCommunity = communities[edge.toId];
if (neighborCommunity != null) {
neighborCommunities[neighborCommunity] =
(neighborCommunities[neighborCommunity] ?? 0.0) + 1.0;
}
}
// Find best community to move to
int bestCommunity = currentCommunity;
double bestGain = 0.0;
for (final entry in neighborCommunities.entries) {
final community = entry.key;
final weight = entry.value;
if (community != currentCommunity && weight > bestGain) {
bestCommunity = community;
bestGain = weight;
}
}
// Move node to best community if beneficial
if (bestCommunity != currentCommunity) {
communities[node.id] = bestCommunity;
improved = true;
}
}
}
return communities;
}
/// Calculates centrality measures for nodes
Future<Map<VektagrafId, CentralityMeasures>> calculateCentrality() async {
final allNodes = await graph.getAllNodes();
final centrality = <VektagrafId, CentralityMeasures>{};
for (final node in allNodes) {
final incomingEdges = await node.incoming;
final outgoingEdges = await node.outgoing;
// Degree centrality
final degreeCentrality = (incomingEdges.length + outgoingEdges.length) /
(allNodes.length - 1);
// Betweenness centrality (simplified calculation)
final betweennessCentrality = await _calculateBetweennessCentrality(node.id);
// Closeness centrality (simplified calculation)
final closenessCentrality = await _calculateClosenessCentrality(node.id);
centrality[node.id] = CentralityMeasures(
degree: degreeCentrality,
betweenness: betweennessCentrality,
closeness: closenessCentrality,
);
}
return centrality;
}
// Helper methods
bool _matchesFilters(VektagrafNode node, Map<String, dynamic> filters) {
for (final entry in filters.entries) {
final property = node.getProperty(entry.key);
if (property != entry.value) return false;
}
return true;
}
bool _matchesEdgeFilters(VektagrafEdge edge, Map<String, dynamic> filters) {
for (final entry in filters.entries) {
final property = edge.getProperty(entry.key);
if (property != entry.value) return false;
}
return true;
}
Future<List<VektagrafNode>> _reconstructPath(
Map<VektagrafId, VektagrafId> cameFrom,
VektagrafId goalId,
) async {
final path = <VektagrafNode>[];
VektagrafId? current = goalId;
while (current != null) {
final node = await graph.getNode(current);
if (node != null) path.insert(0, node);
current = cameFrom[current];
}
return path;
}
Future<double> _calculateBetweennessCentrality(VektagrafId nodeId) async {
// Simplified betweenness centrality calculation
// In practice, this would use algorithms like Brandes' algorithm
return 0.0;
}
Future<double> _calculateClosenessCentrality(VektagrafId nodeId) async {
// Simplified closeness centrality calculation
// In practice, this would calculate shortest paths to all other nodes
return 0.0;
}
}
// Supporting classes
class TraversalState {
final VektagrafId nodeId;
final int depth;
TraversalState({required this.nodeId, required this.depth});
}
class PathNode {
final VektagrafId nodeId;
final double fScore;
PathNode({required this.nodeId, required this.fScore});
}
class CentralityMeasures {
final double degree;
final double betweenness;
final double closeness;
CentralityMeasures({
required this.degree,
required this.betweenness,
required this.closeness,
});
}
// Simple priority queue implementation
class PriorityQueue<T> {
final List<T> _items = [];
final int Function(T, T) _compare;
PriorityQueue(this._compare);
void add(T item) {
_items.add(item);
_items.sort(_compare);
}
T removeFirst() => _items.removeAt(0);
bool get isNotEmpty => _items.isNotEmpty;
}
```### Securit
y and Access Control
#### Relationship-Level Security
```dart
class SecureGraphOperations {
final VektagrafGraph graph;
final SecurityContext securityContext;
SecureGraphOperations(this.graph, this.securityContext);
/// Performs secure graph traversal with relationship-level access control
Future<List<VektagrafNode>> secureTraversal({
required VektagrafId startNodeId,
required List<String> relationshipTypes,
int maxDepth = 3,
Map<String, dynamic>? filters,
}) async {
final results = <VektagrafNode>[];
final visited = <VektagrafId>{};
final queue = <TraversalState>[];
// Check initial node access
if (!await _canAccessNode(startNodeId)) {
throw SecurityException('Access denied to start node: $startNodeId');
}
queue.add(TraversalState(nodeId: startNodeId, depth: 0));
while (queue.isNotEmpty) {
final current = queue.removeAt(0);
if (current.depth > maxDepth || visited.contains(current.nodeId)) {
continue;
}
visited.add(current.nodeId);
// Check node access permissions
if (await _canAccessNode(current.nodeId)) {
final node = await graph.getNode(current.nodeId);
if (node != null && _matchesFilters(node, filters)) {
results.add(node);
}
// Traverse relationships with security checks
if (current.depth < maxDepth) {
for (final relationshipType in relationshipTypes) {
if (await _canTraverseRelationship(current.nodeId, relationshipType)) {
final edges = await node?.outgoingOfType(relationshipType) ?? [];
for (final edge in edges) {
if (await _canAccessRelationship(edge.id)) {
queue.add(TraversalState(
nodeId: edge.toId,
depth: current.depth + 1,
));
}
}
}
}
}
}
}
return results;
}
/// Creates a relationship with security validation
Future<VektagrafId> createSecureRelationship({
required VektagrafId fromNodeId,
required VektagrafId toNodeId,
required String relationshipType,
Map<String, dynamic>? properties,
}) async {
// Validate permissions
if (!await _canAccessNode(fromNodeId)) {
throw SecurityException('Access denied to source node: $fromNodeId');
}
if (!await _canAccessNode(toNodeId)) {
throw SecurityException('Access denied to target node: $toNodeId');
}
if (!await _canCreateRelationship(relationshipType)) {
throw SecurityException('Permission denied to create relationship: $relationshipType');
}
// Add security metadata to relationship
final secureProperties = {
...?properties,
'created_by': securityContext.userId.toString(),
'created_at': DateTime.now().toIso8601String(),
'security_level': _determineSecurityLevel(fromNodeId, toNodeId),
'access_control': _generateAccessControl(relationshipType),
};
// Create relationship with audit logging
final relationshipId = await graph.addEdge(
fromNodeId,
toNodeId,
type: relationshipType,
properties: secureProperties,
);
// Log the relationship creation
await _auditLog('relationship_created', {
'relationship_id': relationshipId.toString(),
'from_node': fromNodeId.toString(),
'to_node': toNodeId.toString(),
'type': relationshipType,
'user_id': securityContext.userId.toString(),
});
return relationshipId;
}
/// Implements privacy-preserving graph analytics
Future<GraphAnalytics> getPrivacyPreservingAnalytics({
double epsilon = 1.0,
double delta = 1e-5,
}) async {
// Get accessible nodes only
final accessibleNodes = <VektagrafNode>[];
final allNodes = await graph.getAllNodes();
for (final node in allNodes) {
if (await _canAccessNode(node.id)) {
accessibleNodes.add(node);
}
}
// Calculate analytics with differential privacy
final nodeCount = _addNoise(accessibleNodes.length.toDouble(), epsilon);
// Calculate degree distribution with privacy
final degreeDistribution = <int, int>{};
for (final node in accessibleNodes) {
final degree = await _getSecureNodeDegree(node.id);
final noisyDegree = _addNoise(degree.toDouble(), epsilon).round();
degreeDistribution[noisyDegree] = (degreeDistribution[noisyDegree] ?? 0) + 1;
}
// Calculate relationship type distribution
final relationshipTypes = <String, int>{};
for (final node in accessibleNodes) {
final edges = await node.outgoing;
for (final edge in edges) {
if (await _canAccessRelationship(edge.id)) {
final type = edge.type ?? 'unknown';
relationshipTypes[type] = (relationshipTypes[type] ?? 0) + 1;
}
}
}
// Add noise to relationship counts
final noisyRelationshipTypes = <String, int>{};
for (final entry in relationshipTypes.entries) {
final noisyCount = _addNoise(entry.value.toDouble(), epsilon).round();
noisyRelationshipTypes[entry.key] = noisyCount;
}
return GraphAnalytics(
nodeCount: nodeCount.round(),
degreeDistribution: degreeDistribution,
relationshipTypeDistribution: noisyRelationshipTypes,
privacyParameters: PrivacyParameters(epsilon: epsilon, delta: delta),
);
}
// Security validation methods
Future<bool> _canAccessNode(VektagrafId nodeId) async {
final node = await graph.getNode(nodeId);
if (node == null) return false;
// Check node-level permissions
final securityLevel = node.getProperty<String>('security_level');
final ownerId = node.getProperty<String>('owner_id');
// System admin can access everything
if (securityContext.roles.contains('system_admin')) return true;
// Owner can access their own nodes
if (ownerId == securityContext.userId.toString()) return true;
// Check security clearance
if (securityLevel != null) {
return _hasSecurityClearance(securityLevel);
}
// Default to public access for nodes without security level
return true;
}
Future<bool> _canAccessRelationship(VektagrafId relationshipId) async {
final relationship = await graph.getEdge(relationshipId);
if (relationship == null) return false;
// Check relationship-level permissions
final accessControl = relationship.getProperty<Map<String, dynamic>>('access_control');
if (accessControl != null) {
return _checkAccessControl(accessControl);
}
// Check if user can access both connected nodes
return await _canAccessNode(relationship.fromId) &&
await _canAccessNode(relationship.toId);
}
Future<bool> _canTraverseRelationship(VektagrafId nodeId, String relationshipType) async {
// Check if user has permission to traverse this type of relationship
final permissions = securityContext.permissions;
return permissions.contains('traverse:*') ||
permissions.contains('traverse:$relationshipType') ||
permissions.contains('read:relationships');
}
Future<bool> _canCreateRelationship(String relationshipType) async {
final permissions = securityContext.permissions;
return permissions.contains('create:*') ||
permissions.contains('create:relationships') ||
permissions.contains('create:$relationshipType');
}
bool _hasSecurityClearance(String requiredLevel) {
final userClearance = securityContext.clearanceLevel;
if (userClearance == null) return false;
// Define clearance hierarchy
const clearanceLevels = {
'public': 0,
'internal': 1,
'confidential': 2,
'secret': 3,
'top_secret': 4,
};
final userLevel = clearanceLevels[userClearance] ?? 0;
final requiredLevelValue = clearanceLevels[requiredLevel] ?? 0;
return userLevel >= requiredLevelValue;
}
bool _checkAccessControl(Map<String, dynamic> accessControl) {
// Check role-based access
final allowedRoles = accessControl['allowed_roles'] as List<dynamic>?;
if (allowedRoles != null) {
final hasRole = securityContext.roles.any((role) => allowedRoles.contains(role));
if (!hasRole) return false;
}
// Check user-based access
final allowedUsers = accessControl['allowed_users'] as List<dynamic>?;
if (allowedUsers != null) {
final hasAccess = allowedUsers.contains(securityContext.userId.toString());
if (!hasAccess) return false;
}
// Check time-based access
final validFrom = accessControl['valid_from'] as String?;
final validTo = accessControl['valid_to'] as String?;
if (validFrom != null || validTo != null) {
final now = DateTime.now();
if (validFrom != null && now.isBefore(DateTime.parse(validFrom))) {
return false;
}
if (validTo != null && now.isAfter(DateTime.parse(validTo))) {
return false;
}
}
return true;
}
String _determineSecurityLevel(VektagrafId fromNodeId, VektagrafId toNodeId) {
// Determine security level based on connected nodes
// This is a simplified implementation
return 'internal';
}
Map<String, dynamic> _generateAccessControl(String relationshipType) {
// Generate access control rules based on relationship type
switch (relationshipType) {
case 'FRIENDS_WITH':
return {
'allowed_roles': ['user', 'admin'],
'visibility': 'friends',
};
case 'WORKS_FOR':
return {
'allowed_roles': ['employee', 'hr', 'admin'],
'visibility': 'internal',
};
default:
return {
'allowed_roles': ['user', 'admin'],
'visibility': 'public',
};
}
}
Future<int> _getSecureNodeDegree(VektagrafId nodeId) async {
final node = await graph.getNode(nodeId);
if (node == null) return 0;
int degree = 0;
// Count accessible incoming edges
final incomingEdges = await node.incoming;
for (final edge in incomingEdges) {
if (await _canAccessRelationship(edge.id)) {
degree++;
}
}
// Count accessible outgoing edges
final outgoingEdges = await node.outgoing;
for (final edge in outgoingEdges) {
if (await _canAccessRelationship(edge.id)) {
degree++;
}
}
return degree;
}
double _addNoise(double value, double epsilon) {
// Add Laplace noise for differential privacy
final random = Random();
final u = random.nextDouble() - 0.5;
final noise = -1 / epsilon * (u >= 0 ? 1 : -1) * log(1 - 2 * u.abs());
return value + noise;
}
bool _matchesFilters(VektagrafNode node, Map<String, dynamic>? filters) {
if (filters == null) return true;
for (final entry in filters.entries) {
final property = node.getProperty(entry.key);
if (property != entry.value) return false;
}
return true;
}
Future<void> _auditLog(String action, Map<String, dynamic> details) async {
// Log security-relevant actions
final logEntry = {
'timestamp': DateTime.now().toIso8601String(),
'user_id': securityContext.userId.toString(),
'action': action,
'details': details,
'ip_address': securityContext.ipAddress,
'user_agent': securityContext.userAgent,
};
// In practice, this would write to an audit log system
print('AUDIT: $logEntry');
}
}
// Supporting classes for security
class SecurityContext {
final VektagrafId userId;
final List<String> roles;
final List<String> permissions;
final String? clearanceLevel;
final String? ipAddress;
final String? userAgent;
SecurityContext({
required this.userId,
required this.roles,
required this.permissions,
this.clearanceLevel,
this.ipAddress,
this.userAgent,
});
}
class SecurityException implements Exception {
final String message;
SecurityException(this.message);
@override
String toString() => 'SecurityException: $message';
}
class GraphAnalytics {
final int nodeCount;
final Map<int, int> degreeDistribution;
final Map<String, int> relationshipTypeDistribution;
final PrivacyParameters privacyParameters;
GraphAnalytics({
required this.nodeCount,
required this.degreeDistribution,
required this.relationshipTypeDistribution,
required this.privacyParameters,
});
}
class PrivacyParameters {
final double epsilon;
final double delta;
PrivacyParameters({required this.epsilon, required this.delta});
}
Advanced Topics
Graph Algorithms Implementation
class AdvancedGraphAlgorithms {
final VektagrafGraph graph;
AdvancedGraphAlgorithms(this.graph);
/// Implements PageRank algorithm for node importance ranking
Future<Map<VektagrafId, double>> calculatePageRank({
double dampingFactor = 0.85,
double tolerance = 1e-6,
int maxIterations = 100,
}) async {
final allNodes = await graph.getAllNodes();
final nodeIds = allNodes.map((n) => n.id).toList();
final nodeCount = nodeIds.length;
if (nodeCount == 0) return {};
// Initialize PageRank values
final pageRank = <VektagrafId, double>{};
final newPageRank = <VektagrafId, double>{};
final initialValue = 1.0 / nodeCount;
for (final nodeId in nodeIds) {
pageRank[nodeId] = initialValue;
}
// Build adjacency information
final outgoingLinks = <VektagrafId, List<VektagrafId>>{};
final incomingLinks = <VektagrafId, List<VektagrafId>>{};
for (final node in allNodes) {
final outgoing = await node.outgoing;
outgoingLinks[node.id] = outgoing.map((e) => e.toId).toList();
for (final edge in outgoing) {
incomingLinks.putIfAbsent(edge.toId, () => []).add(node.id);
}
}
// Iterative calculation
for (int iteration = 0; iteration < maxIterations; iteration++) {
double totalDiff = 0.0;
for (final nodeId in nodeIds) {
double sum = 0.0;
// Sum contributions from incoming links
final incoming = incomingLinks[nodeId] ?? [];
for (final incomingNodeId in incoming) {
final incomingPageRank = pageRank[incomingNodeId] ?? 0.0;
final outgoingCount = outgoingLinks[incomingNodeId]?.length ?? 1;
sum += incomingPageRank / outgoingCount;
}
final newValue = (1.0 - dampingFactor) / nodeCount + dampingFactor * sum;
newPageRank[nodeId] = newValue;
totalDiff += (newValue - (pageRank[nodeId] ?? 0.0)).abs();
}
// Update PageRank values
pageRank.addAll(newPageRank);
// Check for convergence
if (totalDiff < tolerance) {
break;
}
}
return pageRank;
}
/// Implements community detection using Label Propagation Algorithm
Future<Map<VektagrafId, String>> detectCommunitiesLPA({
int maxIterations = 100,
}) async {
final allNodes = await graph.getAllNodes();
final labels = <VektagrafId, String>{};
// Initialize each node with unique label
for (int i = 0; i < allNodes.length; i++) {
labels[allNodes[i].id] = 'community_$i';
}
// Iterative label propagation
for (int iteration = 0; iteration < maxIterations; iteration++) {
bool changed = false;
// Randomize node order to avoid bias
final shuffledNodes = List<VektagrafNode>.from(allNodes)..shuffle();
for (final node in shuffledNodes) {
final neighborLabels = <String, int>{};
// Count neighbor labels
final edges = await node.outgoing;
for (final edge in edges) {
final neighborLabel = labels[edge.toId];
if (neighborLabel != null) {
neighborLabels[neighborLabel] = (neighborLabels[neighborLabel] ?? 0) + 1;
}
}
// Also consider incoming edges
final incomingEdges = await node.incoming;
for (final edge in incomingEdges) {
final neighborLabel = labels[edge.fromId];
if (neighborLabel != null) {
neighborLabels[neighborLabel] = (neighborLabels[neighborLabel] ?? 0) + 1;
}
}
// Find most frequent label
if (neighborLabels.isNotEmpty) {
final mostFrequentLabel = neighborLabels.entries
.reduce((a, b) => a.value > b.value ? a : b)
.key;
if (labels[node.id] != mostFrequentLabel) {
labels[node.id] = mostFrequentLabel;
changed = true;
}
}
}
// Stop if no changes occurred
if (!changed) break;
}
return labels;
}
/// Implements graph clustering using Markov Clustering (MCL)
Future<List<List<VektagrafId>>> clusterGraphMCL({
double inflation = 2.0,
double expansion = 2.0,
double tolerance = 1e-6,
int maxIterations = 100,
}) async {
final allNodes = await graph.getAllNodes();
final nodeIds = allNodes.map((n) => n.id).toList();
final nodeCount = nodeIds.length;
if (nodeCount == 0) return [];
// Build adjacency matrix
final adjacencyMatrix = List.generate(
nodeCount,
(_) => List.filled(nodeCount, 0.0),
);
// Add self-loops and normalize
for (int i = 0; i < nodeCount; i++) {
adjacencyMatrix[i][i] = 1.0;
final node = allNodes[i];
final outgoing = await node.outgoing;
for (final edge in outgoing) {
final targetIndex = nodeIds.indexOf(edge.toId);
if (targetIndex >= 0) {
adjacencyMatrix[i][targetIndex] = 1.0;
}
}
}
// Normalize columns
for (int j = 0; j < nodeCount; j++) {
double sum = 0.0;
for (int i = 0; i < nodeCount; i++) {
sum += adjacencyMatrix[i][j];
}
if (sum > 0) {
for (int i = 0; i < nodeCount; i++) {
adjacencyMatrix[i][j] /= sum;
}
}
}
// MCL iterations
var currentMatrix = adjacencyMatrix;
for (int iteration = 0; iteration < maxIterations; iteration++) {
// Expansion (matrix multiplication)
final expandedMatrix = _matrixMultiply(currentMatrix, currentMatrix);
// Inflation (element-wise power and normalization)
final inflatedMatrix = _inflateMatrix(expandedMatrix, inflation);
// Check convergence
if (_matrixConverged(currentMatrix, inflatedMatrix, tolerance)) {
break;
}
currentMatrix = inflatedMatrix;
}
// Extract clusters from final matrix
return _extractClusters(currentMatrix, nodeIds);
}
/// Finds strongly connected components using Tarjan's algorithm
Future<List<List<VektagrafId>>> findStronglyConnectedComponents() async {
final allNodes = await graph.getAllNodes();
final components = <List<VektagrafId>>[];
final visited = <VektagrafId>{};
final stack = <VektagrafId>[];
final indices = <VektagrafId, int>{};
final lowLinks = <VektagrafId, int>{};
final onStack = <VektagrafId, bool>{};
int index = 0;
Future<void> strongConnect(VektagrafId nodeId) async {
indices[nodeId] = index;
lowLinks[nodeId] = index;
index++;
stack.add(nodeId);
onStack[nodeId] = true;
final node = await graph.getNode(nodeId);
if (node != null) {
final outgoing = await node.outgoing;
for (final edge in outgoing) {
final targetId = edge.toId;
if (!indices.containsKey(targetId)) {
await strongConnect(targetId);
lowLinks[nodeId] = min(lowLinks[nodeId]!, lowLinks[targetId]!);
} else if (onStack[targetId] == true) {
lowLinks[nodeId] = min(lowLinks[nodeId]!, indices[targetId]!);
}
}
}
// If nodeId is a root node, pop the stack and create component
if (lowLinks[nodeId] == indices[nodeId]) {
final component = <VektagrafId>[];
VektagrafId? poppedId;
do {
poppedId = stack.removeLast();
onStack[poppedId] = false;
component.add(poppedId);
} while (poppedId != nodeId);
components.add(component);
}
}
// Run Tarjan's algorithm on all unvisited nodes
for (final node in allNodes) {
if (!indices.containsKey(node.id)) {
await strongConnect(node.id);
}
}
return components;
}
// Helper methods for matrix operations
List<List<double>> _matrixMultiply(
List<List<double>> a,
List<List<double>> b,
) {
final rows = a.length;
final cols = b[0].length;
final result = List.generate(rows, (_) => List.filled(cols, 0.0));
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
for (int k = 0; k < a[0].length; k++) {
result[i][j] += a[i][k] * b[k][j];
}
}
}
return result;
}
List<List<double>> _inflateMatrix(List<List<double>> matrix, double inflation) {
final rows = matrix.length;
final cols = matrix[0].length;
final result = List.generate(rows, (_) => List.filled(cols, 0.0));
// Apply inflation (element-wise power)
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
result[i][j] = pow(matrix[i][j], inflation).toDouble();
}
}
// Normalize columns
for (int j = 0; j < cols; j++) {
double sum = 0.0;
for (int i = 0; i < rows; i++) {
sum += result[i][j];
}
if (sum > 0) {
for (int i = 0; i < rows; i++) {
result[i][j] /= sum;
}
}
}
return result;
}
bool _matrixConverged(
List<List<double>> a,
List<List<double>> b,
double tolerance,
) {
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < a[0].length; j++) {
if ((a[i][j] - b[i][j]).abs() > tolerance) {
return false;
}
}
}
return true;
}
List<List<VektagrafId>> _extractClusters(
List<List<double>> matrix,
List<VektagrafId> nodeIds,
) {
final clusters = <List<VektagrafId>>[];
final processed = <int>{};
for (int i = 0; i < matrix.length; i++) {
if (processed.contains(i)) continue;
final cluster = <VektagrafId>[];
// Find all nodes that belong to this cluster
for (int j = 0; j < matrix[0].length; j++) {
if (matrix[i][j] > 0.001) { // Threshold for cluster membership
cluster.add(nodeIds[j]);
processed.add(j);
}
}
if (cluster.isNotEmpty) {
clusters.add(cluster);
}
}
return clusters;
}
}
Reference
Graph API
Core Graph Interface
abstract class VektagrafGraph {
// Node operations
Future<VektagrafId> addNode(Map<String, dynamic> properties, {Set<String>? labels});
Future<VektagrafNode?> getNode(VektagrafId id);
Future<void> removeNode(VektagrafId id);
Future<List<VektagrafNode>> getNodesByLabel(String label);
// Edge operations
Future<VektagrafId> addEdge(VektagrafId fromId, VektagrafId toId,
{String? type, Map<String, dynamic>? properties});
Future<VektagrafEdge?> getEdge(VektagrafId id);
Future<void> removeEdge(VektagrafId id);
Future<List<VektagrafEdge>> getEdgesByType(String type);
// Traversal operations
Stream<VektagrafNode> traverse(VektagrafId startId);
Future<List<VektagrafNode>?> shortestPath(VektagrafId fromId, VektagrafId toId);
}
Node and Edge Interfaces
abstract class VektagrafNode {
VektagrafId get id;
Set<String> get labels;
Map<String, dynamic> get properties;
bool hasLabel(String label);
T? getProperty<T>(String key);
Future<void> setProperty(String key, dynamic value);
Future<List<VektagrafEdge>> get incoming;
Future<List<VektagrafEdge>> get outgoing;
Future<List<VektagrafEdge>> outgoingOfType(String type);
Future<List<VektagrafEdge>> incomingOfType(String type);
Stream<VektagrafNode> connectedNodes();
Stream<VektagrafNode> connectedNodesOfType(String type);
}
abstract class VektagrafEdge {
VektagrafId get id;
VektagrafId get fromId;
VektagrafId get toId;
String? get type;
Map<String, dynamic> get properties;
Future<VektagrafNode?> get fromNode;
Future<VektagrafNode?> get toNode;
T? getProperty<T>(String key);
Future<void> setProperty(String key, dynamic value);
}
Performance Characteristics
Time Complexity
| Operation | Memory Graph | Optimized Graph |
|---|---|---|
| Add Node | O(1) | O(1) |
| Add Edge | O(1) | O(1) |
| Get Node | O(1) | O(1) |
| Get Edge | O(1) | O(1) |
| Traverse (BFS) | O(V + E) | O(V + E) |
| Shortest Path | O(V + E) | O(V log V + E) |
| Remove Node | O(degree) | O(degree) |
Space Complexity
| Component | Memory Usage |
|---|---|
| Node | ~200 bytes + properties |
| Edge | ~150 bytes + properties |
| Index (per label) | ~50 bytes per node |
| Index (per type) | ~50 bytes per edge |
Configuration Options
class GraphConfig {
// Performance settings
final int maxNodesInMemory;
final int maxEdgesInMemory;
final bool enableIndexing;
final bool enableCaching;
// Security settings
final bool enableAccessControl;
final bool auditTraversal;
final bool encryptProperties;
// Algorithm settings
final int maxTraversalDepth;
final Duration traversalTimeout;
final bool enableParallelTraversal;
}
Summary
This chapter covered Vektagraf's comprehensive graph operations and relationship modeling capabilities. Key takeaways include:
- Object-Graph Integration: Automatic graph node creation for objects with seamless relationship modeling
- Flexible Relationships: Support for various relationship patterns with properties and types
- Advanced Traversal: Efficient graph traversal with security controls and performance optimization
- Social Features: Complete social network implementation with friend suggestions and analytics
- Recommendation Systems: Content-based and collaborative filtering using graph relationships
- Security: Relationship-level access control with privacy-preserving analytics
- Advanced Algorithms: PageRank, community detection, and clustering implementations
Next Steps
- Chapter 7: Learn about query optimization and performance tuning techniques
- Chapter 21: Build complete social network applications using graph operations
- Chapter 19: Implement recommendation systems with graph-based algorithms
No Comments