Array Broadcasting
Hamelin automatically broadcasts operations across arrays, letting you use
familiar syntax without explicit iteration. When you write array.field,
Hamelin extracts that field from every element in the array. When you write
func(array), the function applies to each array element individually.
How broadcasting works
Broadcasting works for any operation or function call, for example arithmetic, comparisons, function calls, and field access. Instead of needing special array functions, you use familiar syntax and Hamelin handles the iteration.
Examples
Let's work through some examples assuming that failed_attempts has the following data:
SET failed_attempts = [
{timestamp: '2024-01-15T14:25:00Z', reason: 'invalid_password', ip: '192.168.1.100'},
{timestamp: '2024-01-15T14:26:15Z', reason: 'account_locked', ip: '192.168.1.101'},
{timestamp: '2024-01-15T14:27:30Z', reason: 'invalid_password', ip: '192.168.1.102'}
]
Field access broadcasting
Access fields across all array elements using dot notation:
// Extract all unique reasons from the failed attempts array
| SET all_reasons = array_distinct(failed_attempts.reason)
// Result: ['invalid_password', 'account_locked']
// Extract all IP addresses
| SET all_ips = failed_attempts.ip
// Result: ['192.168.1.100', '192.168.1.101', '192.168.1.102']
The operation applies to each element in the array, extracting the specified field from every object.
Comparison broadcasting
Apply comparison operations across array elements:
// Check which attempts were due to invalid passwords
| SET password_failures = failed_attempts.reason == 'invalid_password'
// Result: [true, false, true]
// Find attempts from suspicious IP ranges
| SET suspicious_ips = failed_attempts.ip > '192.168.1.100'
// Result: [false, true, true]
Each comparison operation returns an array of boolean values, maintaining the same structure as the original array.
Function call broadcasting
Named functions automatically broadcast over arrays:
// Convert all reasons to uppercase
| SET uppercase_reasons = upper(failed_attempts.reason)
// Result: ['INVALID_PASSWORD', 'ACCOUNT_LOCKED', 'INVALID_PASSWORD']
// Extract hour from all timestamps
| SET attempt_hours = hour(failed_attempts.timestamp)
// Result: [14, 14, 14]
Functions apply to each element in the array automatically.
Arithmetic broadcasting
Mathematical operations work across array elements:
// Extract numeric seconds from timestamp strings for calculations
| SET timestamp_seconds = second(failed_attempts.timestamp)
// Result: [0, 15, 30]
// Add 10 seconds to each timestamp second value
| SET adjusted_seconds = timestamp_seconds + 10
// Result: [10, 25, 40]
// Calculate minutes elapsed since the first attempt
| SET minutes_elapsed = (timestamp_seconds - timestamp_seconds[0]) / 60
// Result: [0, 0.25, 0.5]
Combining with array functions
Broadcasting pairs naturally with functions that take arrays as arguments like any() and all():
// Check if any login attempt was due to invalid password
| WHERE any(failed_attempts.reason == 'invalid_password')
// Verify all attempts came from internal network
| WHERE all(failed_attempts.ip < '192.168.2.0')
// Count how many attempts were password-related
| AGG password_attempt_count = count_if(any(failed_attempts.reason == 'invalid_password'))
The broadcasting happens first (creating boolean arrays), then the array functions operate on those arrays.
Nested structure broadcasting
Broadcasting works with the nested structure in our example data:
// Extract just the hour from each timestamp
| SET attempt_hours = hour(failed_attempts.timestamp)
// Result: [14, 14, 14]
// Check if any IP is in a specific subnet
| SET internal_network = failed_attempts.ip > '192.168.1.100'
// Result: [false, true, true]
// Create readable timestamp strings
| SET readable_times = format_timestamp(failed_attempts.timestamp, 'HH:mm:ss')
// Result: ['14:25:00', '14:26:15', '14:27:30']
Broadcasting navigates through multiple levels of nesting.
Broadcasting vs. transform and filter
For simple operations like field access or applying a single function,
broadcasting is the most concise approach. For more complex per-element logic,
use transform() and filter() with lambda expressions:
// Broadcasting — concise for simple operations
SET tag_names = upper(tags.name)
// transform() — equivalent using explicit lambda
SET tag_names = transform(tags, t -> upper(t.name))
// filter() — select elements by a predicate
SET admin_tags = filter(tags, t -> t.role == 'admin')
// Combining both — filter then transform
SET admin_names = transform(filter(tags, t -> t.role == 'admin'), t -> t.name)
Broadcasting is syntactic sugar that covers the most common cases automatically.
transform() and filter() give you full control when you need custom
per-element logic, predicate-based filtering, or when you want to convert
array structures (e.g., map(transform(arr, x -> x.key : x.val)) to turn
a key-value array into a map).