package database import ( "context" "fmt" "time" "github.com/neo4j/neo4j-go-driver/v5/neo4j" ) // Neo4jConfig holds configuration for Neo4j connection type Neo4jConfig struct { URI string Username string Password string Database string } // NewNeo4jDriver creates a new Neo4j driver instance func NewNeo4jDriver(config Neo4jConfig) (neo4j.DriverWithContext, error) { driver, err := neo4j.NewDriverWithContext( config.URI, neo4j.BasicAuth(config.Username, config.Password, ""), func(c *neo4j.Config) { c.MaxConnectionPoolSize = 50 c.ConnectionAcquisitionTimeout = 30 * time.Second c.MaxTransactionRetryTime = 30 * time.Second }, ) if err != nil { return nil, fmt.Errorf("failed to create Neo4j driver: %w", err) } // Verify connectivity ctx := context.Background() if err := driver.VerifyConnectivity(ctx); err != nil { driver.Close(ctx) return nil, fmt.Errorf("failed to verify Neo4j connectivity: %w", err) } return driver, nil } // InitializeSchema creates constraints and indexes in Neo4j func InitializeSchema(ctx context.Context, driver neo4j.DriverWithContext, database string) error { session := driver.NewSession(ctx, neo4j.SessionConfig{ AccessMode: neo4j.AccessModeWrite, DatabaseName: database, }) defer session.Close(ctx) // Constraints for uniqueness constraints := []string{ "CREATE CONSTRAINT business_id_unique IF NOT EXISTS FOR (b:Business) REQUIRE b.id IS UNIQUE", "CREATE CONSTRAINT organization_id_unique IF NOT EXISTS FOR (o:Organization) REQUIRE o.id IS UNIQUE", "CREATE CONSTRAINT site_id_unique IF NOT EXISTS FOR (s:Site) REQUIRE s.id IS UNIQUE", "CREATE CONSTRAINT resource_flow_id_unique IF NOT EXISTS FOR (rf:ResourceFlow) REQUIRE rf.id IS UNIQUE", "CREATE CONSTRAINT match_id_unique IF NOT EXISTS FOR (m:Match) REQUIRE m.id IS UNIQUE", "CREATE CONSTRAINT shared_asset_id_unique IF NOT EXISTS FOR (sa:SharedAsset) REQUIRE sa.id IS UNIQUE", } // Indexes for performance indexes := []string{ "CREATE INDEX organization_name_index IF NOT EXISTS FOR (o:Organization) ON (o.name)", "CREATE INDEX organization_sector_index IF NOT EXISTS FOR (o:Organization) ON (o.sector)", "CREATE INDEX organization_subtype_index IF NOT EXISTS FOR (o:Organization) ON (o.subtype)", "CREATE INDEX site_location_index IF NOT EXISTS FOR (s:Site) ON (s.latitude, s.longitude)", "CREATE INDEX site_type_index IF NOT EXISTS FOR (s:Site) ON (s.site_type)", "CREATE INDEX resource_flow_type_direction_index IF NOT EXISTS FOR (rf:ResourceFlow) ON (rf.type, rf.direction)", "CREATE INDEX resource_flow_type_index IF NOT EXISTS FOR (rf:ResourceFlow) ON (rf.type)", "CREATE INDEX resource_flow_direction_index IF NOT EXISTS FOR (rf:ResourceFlow) ON (rf.direction)", "CREATE INDEX match_status_index IF NOT EXISTS FOR (m:Match) ON (m.status)", "CREATE INDEX match_score_index IF NOT EXISTS FOR (m:Match) ON (m.compatibility_score)", "CREATE INDEX shared_asset_type_index IF NOT EXISTS FOR (sa:SharedAsset) ON (sa.type)", } // Execute constraints for _, constraint := range constraints { if _, err := session.Run(ctx, constraint, nil); err != nil { return fmt.Errorf("failed to create constraint: %w", err) } } // Execute indexes for _, index := range indexes { if _, err := session.Run(ctx, index, nil); err != nil { return fmt.Errorf("failed to create index: %w", err) } } return nil } // Helper functions for extracting values from Neo4j records // GetString extracts a string value from a map func GetString(m map[string]interface{}, key string) string { if val, ok := m[key]; ok { if str, ok := val.(string); ok { return str } } return "" } // GetFloat64 extracts a float64 value from a map func GetFloat64(m map[string]interface{}, key string) float64 { if val, ok := m[key]; ok { switch v := val.(type) { case float64: return v case int64: return float64(v) case int: return float64(v) } } return 0 } // GetInt extracts an int value from a map func GetInt(m map[string]interface{}, key string) int { if val, ok := m[key]; ok { switch v := val.(type) { case int64: return int(v) case int: return v case float64: return int(v) } } return 0 } // GetBool extracts a bool value from a map func GetBool(m map[string]interface{}, key string) bool { if val, ok := m[key]; ok { if b, ok := val.(bool); ok { return b } } return false } // GetTime extracts a time.Time value from a map func GetTime(m map[string]interface{}, key string) time.Time { if val, ok := m[key]; ok { if t, ok := val.(time.Time); ok { return t } if str, ok := val.(string); ok { if parsed, err := time.Parse(time.RFC3339, str); err == nil { return parsed } } } return time.Time{} } // GetStringFromRecord extracts a string value from a Neo4j record func GetStringFromRecord(record neo4j.Record, key string) string { val, ok := record.Get(key) if !ok { return "" } if str, ok := val.(string); ok { return str } return "" } // GetFloat64FromRecord extracts a float64 value from a Neo4j record func GetFloat64FromRecord(record neo4j.Record, key string) float64 { val, ok := record.Get(key) if !ok { return 0 } switch v := val.(type) { case float64: return v case int64: return float64(v) case int: return float64(v) } return 0 } // GetIntFromRecord extracts an int value from a Neo4j record func GetIntFromRecord(record neo4j.Record, key string) int { val, ok := record.Get(key) if !ok { return 0 } switch v := val.(type) { case int64: return int(v) case int: return v case float64: return int(v) } return 0 }