Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
parameters.go7.79 kB
// Parameter substitution for NornicDB Cypher. // // This file contains functions for handling Cypher query parameters. // Parameters allow passing values into queries without string interpolation, // providing both security (SQL injection prevention) and performance benefits. // // # Parameter Syntax // // Parameters are specified with $ prefix: // // $name - Simple parameter // $userId - Camel case parameter // $count123 - Parameter with numbers // // # Usage Example // // params := map[string]interface{}{ // "name": "Alice", // "age": 30, // } // result, err := exec.Execute(ctx, "MATCH (p:Person {name: $name, age: $age}) RETURN p", params) // // # Supported Types // // - string: Escaped and quoted ('value') // - int, int64, etc.: Numeric literals (42) // - float32, float64: Decimal literals (3.14) // - bool: true/false // - []interface{}: List literals ([1, 2, 3]) // - map[string]interface{}: Map literals ({key: value}) // // # ELI12 // // Parameters are like fill-in-the-blanks in a story: // // "Hello, my name is _____" + {name: "Alice"} = "Hello, my name is Alice" // // Instead of building the query string yourself (which can cause problems), // you leave blanks ($name) and provide the values separately. The database // fills in the blanks safely! // // # Neo4j Compatibility // // Parameter handling matches Neo4j behavior exactly: // - $param syntax // - Type conversion rules // - Escaping behavior package cypher import ( "context" "fmt" "strconv" "strings" ) // NOTE: parameterPattern is defined in regex_patterns.go // getParamKeys returns the keys from a parameter map as a slice. // // # Parameters // // - params: Map of parameter names to values // // # Returns // // - A slice of parameter names // // # Example // // params := map[string]interface{}{"name": "Alice", "age": 30} // keys := getParamKeys(params) // ["name", "age"] (order may vary) func getParamKeys(params map[string]interface{}) []string { keys := make([]string, 0, len(params)) for k := range params { keys = append(keys, k) } return keys } // paramsKeyType is the key type for storing params in context. // Using a custom type prevents collisions with other context values. type paramsKeyType struct{} // paramsKey is the context key for query parameters. var paramsKey = paramsKeyType{} // getParamsFromContext extracts parameters from context if present. // // Parameters are stored in context using paramsKey to avoid collisions. // This is used internally by the query executor. // // # Parameters // // - ctx: Context that may contain parameters // // # Returns // // - Parameter map from context, or nil if not present // // # Example // // ctx := context.WithValue(context.Background(), paramsKey, map[string]interface{}{"id": 123}) // params := getParamsFromContext(ctx) // {"id": 123} func getParamsFromContext(ctx context.Context) map[string]interface{} { if params, ok := ctx.Value(paramsKey).(map[string]interface{}); ok { return params } return nil } // substituteParams replaces $paramName placeholders with actual values. // // This implements Neo4j-style parameter substitution with proper escaping // and type handling. Parameters that are not provided in the map are left as-is. // // # Parameters // // - cypher: The query string with $param placeholders // - params: Map of parameter names to values // // # Returns // // - The query string with parameters substituted // // # Example // // query := "MATCH (p:Person {name: $name}) RETURN p" // params := map[string]interface{}{"name": "Alice"} // result := exec.substituteParams(query, params) // // result = "MATCH (p:Person {name: 'Alice'}) RETURN p" // // # Security Note // // Values are properly escaped to prevent injection attacks. // String values have single quotes escaped by doubling them. func (e *StorageExecutor) substituteParams(cypher string, params map[string]interface{}) string { if params == nil || len(params) == 0 { return cypher } // Use pre-compiled regex to find all parameter references // Parameters are: $name or $name123 (alphanumeric starting with letter) result := parameterPattern.ReplaceAllStringFunc(cypher, func(match string) string { // Extract parameter name (without $) paramName := match[1:] // Look up the value value, exists := params[paramName] if !exists { // Parameter not provided, leave as-is (might be handled elsewhere or is an error) return match } return e.valueToLiteral(value) }) return result } // valueToLiteral converts a Go value to a Cypher literal string. // // This function handles all Go types that can be passed as Cypher parameters, // converting them to their appropriate Cypher literal representation. // // # Parameters // // - v: The Go value to convert // // # Returns // // - A string representation valid in Cypher syntax // // # Type Conversions // // nil → "null" // "hello" → "'hello'" // 42 → "42" // 3.14 → "3.14" // true → "true" // [1, 2] → "[1, 2]" // {a: 1} → "{a: 1}" // // # Example // // e.valueToLiteral("Alice") // "'Alice'" // e.valueToLiteral(42) // "42" // e.valueToLiteral([]int{1, 2, 3}) // "[1, 2, 3]" // e.valueToLiteral(map[string]interface{}{"name": "Bob"}) // "{name: 'Bob'}" func (e *StorageExecutor) valueToLiteral(v interface{}) string { if v == nil { return "null" } switch val := v.(type) { case string: // Escape single quotes by doubling them (Cypher standard) escaped := strings.ReplaceAll(val, "'", "''") // Also escape backslashes escaped = strings.ReplaceAll(escaped, "\\", "\\\\") return fmt.Sprintf("'%s'", escaped) case int: return strconv.FormatInt(int64(val), 10) case int8: return strconv.FormatInt(int64(val), 10) case int16: return strconv.FormatInt(int64(val), 10) case int32: return strconv.FormatInt(int64(val), 10) case int64: return strconv.FormatInt(val, 10) case uint: return strconv.FormatUint(uint64(val), 10) case uint8: return strconv.FormatUint(uint64(val), 10) case uint16: return strconv.FormatUint(uint64(val), 10) case uint32: return strconv.FormatUint(uint64(val), 10) case uint64: return strconv.FormatUint(val, 10) case float32: return strconv.FormatFloat(float64(val), 'f', -1, 32) case float64: return strconv.FormatFloat(val, 'f', -1, 64) case bool: if val { return "true" } return "false" case []interface{}: // Convert array to Cypher list literal: [val1, val2, ...] parts := make([]string, len(val)) for i, item := range val { parts[i] = e.valueToLiteral(item) } return "[" + strings.Join(parts, ", ") + "]" case []string: // String array parts := make([]string, len(val)) for i, item := range val { parts[i] = e.valueToLiteral(item) } return "[" + strings.Join(parts, ", ") + "]" case []int: parts := make([]string, len(val)) for i, item := range val { parts[i] = strconv.Itoa(item) } return "[" + strings.Join(parts, ", ") + "]" case []int64: parts := make([]string, len(val)) for i, item := range val { parts[i] = strconv.FormatInt(item, 10) } return "[" + strings.Join(parts, ", ") + "]" case []float64: parts := make([]string, len(val)) for i, item := range val { parts[i] = strconv.FormatFloat(item, 'f', -1, 64) } return "[" + strings.Join(parts, ", ") + "]" case map[string]interface{}: // Convert map to Cypher map literal: {key1: val1, key2: val2} parts := make([]string, 0, len(val)) for k, v := range val { parts = append(parts, fmt.Sprintf("%s: %s", k, e.valueToLiteral(v))) } return "{" + strings.Join(parts, ", ") + "}" default: // Fallback: convert to string return fmt.Sprintf("'%v'", v) } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/orneryd/Mimir'

If you have feedback or need assistance with the MCP directory API, please join our Discord server