FSQL for SPL Users
Introduction
If you're a security analyst who's comfortable with Splunk's Search Processing Language (SPL), this guide will help you transition to Query's Federated Search Query Language (FSQL). FSQL allows you to search across multiple data sources using a unified language that provides a concise syntax for sophisticated data retrieval and filtering operations.
This tutorial will help you leverage your existing SPL knowledge while highlighting the unique capabilities of FSQL for security analysis tasks.
Why FSQL?
Before diving into the syntax, let's understand why FSQL matters for security analysts:
- Federated search: Query across multiple data sources with a single language
- Text-based queries: Easy to share, copy, paste, version, and include in documentation
- OCSF-based data model: All data sources represented as unified OCSF events
- Powerful selection syntax: Target specific data with a concise, flexible syntax
- Familiar feel: If you know SPL, many concepts will be easy to grasp
FSQL Basics: Transitioning from SPL
Basic Query Structure
In SPL, you typically start with a search term followed by pipes for additional operations:
index=security sourcetype=firewall src_ip="10.0.0.1" | table src_ip, dest_ip, actionIn FSQL, the structure is more declarative:
QUERY network_activity.src_endpoint.ip, network_activity.dst_endpoint.ip, network_activity.status_id
WITH network_activity.src_endpoint.ip = '10.0.0.1'The basic structure is:
QUERY <fields> WITH <filter> [SINCE <time>] [UNTIL <time>] [FROM <connectors>]You may find it more comfortable to write your filter first and your field selection second:
QUERY <filter> [SHOW <fields>] ...QUERY network_activity.src_endpoint.ip = '10.0.0.1'Specifying Time Ranges
SPL:
index=security sourcetype=firewall earliest=-24h latest=-12hFSQL:
QUERY network_activity.* SINCE 24hrs UNTIL 12hrsSPL's earliest maps to SINCE, and latest maps to UNTIL.
Selecting Data Sources
SPL:
index=security (sourcetype=cisco_asa OR sourcetype=palo_alto)FSQL:
QUERY network_activity.* FROM 'Cisco ASA', 'Palo Alto'Connectors can also be referenced by alias (an immutable identifier — the recommended choice for saved searches) or by tag, which expands to every connector carrying it. If you're used to grouping index= values in SPL, a tag is the closest equivalent:
QUERY network_activity.* FROM #firewallsSee Data Sources (FROM) for all connector reference types.
Ordering Results
SPL:
index=security sourcetype=firewall | sort - _timeFSQL:
QUERY network_activity.**
ORDER BY network_activity.time DESCMultiple sort keys are comma-separated, each with an optional ASC (default) or DESC:
ORDER BY network_activity.dst_endpoint.port ASC, network_activity.time DESCField Selection: From SPL to FSQL
Basic Field Selection
SPL:
... | fields src_ip, dest_ip, actionFSQL:
QUERY network_activity.src_endpoint.ip, network_activity.dst_endpoint.ip, network_activity.status_idWildcard Field Selection
SPL:
... | fields src_*, actionFSQL:
QUERY network_activity.src_endpoint.*, network_activity.status_idFor all fields in an event:
SPL:
... | fields *(The above is implicit in most SPL queries)
FSQL:
QUERY network_activity.*Excluding Fields
SPL:
... | fields - start_time, end_timeFSQL:
QUERY network_activity.(* - (start_time + end_time))Filtering: SPL vs FSQL
Equality Filters
SPL:
index=myindex src_ip="10.0.0.1"FSQL:
WITH network_activity.src_endpoint.ip = '10.0.0.1'Multiple Conditions with AND/OR
SPL:
index=myindex (src_ip="10.0.0.1" OR src_ip="10.0.0.2") AND action="blocked"FSQL:
WITH (network_activity.src_endpoint.ip = '10.0.0.1' OR network_activity.src_endpoint.ip = '10.0.0.2')
AND network_activity.status_id = FAILUREString Operations
SPL:
index=web user_agent="*Mozilla*"FSQL:
WITH http_activity.user_agent CONTAINS 'Mozilla'Case-insensitive in SPL:
index=web | where like(lower(user_agent), "%mozilla%")Case-insensitive in FSQL:
WITH http_activity.user_agent ICONTAINS 'mozilla'Regular Expressions
SPL:
index=security sourcetype=windows_event_log
| regex command_line=".*\.(ps1|bat|vbs)$"FSQL:
WITH process_activity.process.cmd_line MATCHES '.*\.(ps1|bat|vbs)$'For case-insensitive matching, SPL can use the (?i) regex flag. FSQL provides IMATCHES:
index=security sourcetype=windows_event_log
| regex command_line="(?i).*\.(ps1|bat|vbs)$"WITH process_activity.process.cmd_line IMATCHES '.*\.(ps1|bat|vbs)$'Note: Regex support varies between data source platforms. Some sources may not support regex natively, which can result in over-fetching.
CIDR Notation
SPL:
index=security src_ip="10.0.0.0/8"
| where cidrmatch("10.0.0.0/8", src_ip)FSQL:
WITH network_activity.src_endpoint.ip CIDR '10.0.0.0/8'Or using entity searches to match across all IP fields:
WITH %ip CIDR '172.16.0.0/12'Note: Many platforms do not support CIDR filtering natively. This operator may result in over-fetching.
IN Operator
SPL:
index=myindex status IN ("success", "failure")FSQL:
WITH network_activity.status_id IN SUCCESS, FAILUREFSQL Power Features for Security Analysts
Event Category Searches
In Splunk, you might be using tag or datamodel:
SPL:
tag=network
datamodel=Network_TrafficThe powerful feature in FSQL is the ability to search across all events in a category using the # selector:
QUERY #network.*
WITH #network.status_id = FAILUREThis searches across all network-related events (http_activity, dns_activity, network_activity, etc.) for failed connections - very useful for security investigations!
Observable Searches (Entity Searches)
In SPL, you might search for an IP across multiple known IP fields or via a raw text search, as below:
index=security (src_ip="10.0.0.1" OR dest_ip="10.0.0.1" OR translated_ip="10.0.0.1")
index=security "10.0.0.1"In FSQL, use the % entity operator to search across all IP fields:
QUERY #network.*
WITH %ip = '10.0.0.1'Type-Based Searches
Search for all user information:
QUERY @userEquivalent long-form selector:
QUERY **:type(user)Or all device information with a specific IP:
QUERY @device
WITH %ip = '10.0.0.1'Common Security Use Cases: SPL to FSQL Translation
1. Failed Authentication Attempts
SPL:
index=security sourcetype=windows_event_log EventCode=4625
| stats count by user, src_ip
| where count > 5FSQL:
SUMMARIZE COUNT authentication
WITH authentication.status_id = FAILURE
GROUP BY authentication.user.username, authentication.src_endpoint.ip
ORDER BY count DESCNote: If you need an explicit threshold (for example, count > 5), apply it in downstream filtering after aggregation.
2. Suspicious Process Execution
SPL:
index=security sourcetype=windows_event_log EventCode=4688 process_name IN ("powershell.exe", "cmd.exe") (command_line="*bypass*" OR command_line="*hidden*")
| table _time, user, process_name, command_lineFSQL:
QUERY process_activity.time, process_activity.user.username,
process_activity.process.name, process_activity.process.cmd_line
WITH process_activity.process.name IN 'powershell.exe', 'cmd.exe'
AND (process_activity.process.cmd_line CONTAINS 'bypass' OR
process_activity.process.cmd_line CONTAINS 'hidden')3. Suspicious Network Connections
SPL:
index=security sourcetype=firewall (dest_port=4444 OR dest_port=8080)
| table _time, src_ip, dest_ip, dest_port, bytes_out, bytes_inFSQL:
QUERY network_activity.time, network_activity.src_endpoint.ip,
network_activity.dst_endpoint.ip, network_activity.dst_endpoint.port,
network_activity.src_endpoint.bytes, network_activity.dst_endpoint.bytes
WITH network_activity.dst_endpoint.port IN 4444, 80804. Searching Across Multiple Data Sources
SPL:
(index=security sourcetype=firewall src_ip="10.0.0.1") OR
(index=security sourcetype=windows_event_log user="jsmith")
| stats count by sourcetypeFSQL:
SUMMARIZE COUNT (network_activity + authentication)
WITH network_activity.src_endpoint.ip = '10.0.0.1'
OR authentication.user.username = 'jsmith'
FROM 'Firewall Logs', 'Windows Event Logs'
GROUP BY __event
ORDER BY count DESCFSQL Quick Reference
Operators
| Operation | SPL | FSQL |
|---|---|---|
| Equals | = | = |
| Not equals | != | != |
| Case insensitive equals | LOWER(field)="value" | == |
| Contains | ="*value*" | CONTAINS or ~ |
| Starts with | ="value*" | STARTSWITH or ^= |
| Ends with | ="*value" | ENDSWITH or $= |
| In list | IN ("val1", "val2") | IN val1, val2 |
| Less than | < | < |
| Is null | isnull(field) | <field> empty |
| Regex match | ` | regex field="pat"` |
| Regex (case-insensitive) | ` | regex field="(?i)pat"` |
| CIDR range | N/A (use cidrmatch) | CIDR '10.0.0.0/8' |
| Sort | | sort - field | ORDER BY field DESC |
Selectors
| Selector | Description | Example |
|---|---|---|
. | Direct path | authentication.user.username |
* | All fields | authentication.* |
** | All fields recursively | authentication.** |
# | Category | #network.* |
% | Entity (observable) | %ip, %hash |
@ | Type | @ip_t, @user |
Tips for SPL Users
-
Think in terms of events: SPL is pipe-based and can transform data significantly. FSQL is more about selecting and filtering OCSF events.
-
Learn the data model: Take time to understand the OCSF schema and how your sources map to it.
-
Use entity shortcuts: The
%ipnotation is much easier than remembering all the places IPs might appear. -
Leverage wildcards: Use
*and type selectors to get what you need without knowing the exact field paths. -
Start simple: Begin with basic queries and expand as you get comfortable.
Conclusion
As you transition from SPL to FSQL, you'll find that your experience with SPL gives you a solid foundation. FSQL's concise syntax and powerful selectors can make your security investigations even more efficient once you get comfortable with them.
Remember that FSQL is optimized for querying across multiple data sources with a unified schema, which is perfect for modern security operations where data lives in many different systems.
Use these patterns as a starting point, then adapt them to your own environment and detections.
Updated 3 days ago