Skip to content
Loglinepre-release
Menu

Not affiliated with, endorsed by or sponsored by Splunk. “SPL-compatible” describes the search syntax the engine accepts, the way “S3-compatible” describes a storage API. The behavior notes below say where this engine differs from Splunk; they are written from public documentation and the project’s own reasoning, never from Splunk software. Affiliation notice.

Compatibility

What the engine runs, and how.

Phase zero. "Pushed" means the command compiles into the ClickHouse query; "in-process" means it runs in the engine on the query result. Everything not listed parses as a generic command and fails with "not supported yet".

pushed
compiled into the ClickHouse query
pivot in-process
the query does the work, the engine reshapes the result
parsed only
understood by the parser, rejected by the planner
not yet
not built

Source: docs/COMPATIBILITY.md at 71d2c54. 19 command rows pushed, 1 not yet.

Search expressions

FeatureStatusNotes
implicit AND, OR, NOT, parenthesespushedboolean keywords must be upper case, as in Splunk
free-text termspushedcase-insensitive; single tokens use hasTokenCaseInsensitive, phrases and punctuation use substring search
wildcards in terms and valuespushed* only; compiled to ILIKE
field=value, field!=valuepushedvalue compare is case-insensitive like Splunk; index= is exact
field<n, <=, >, >=pushednumeric when the value parses as a number, string otherwise
field IN (a, b)pushed
earliest=, latest=pushedrelative modifiers with snap, epoch, MM/DD/YYYY:HH:MM:SS, ISO 8601; read in the search's time zone (tz in POST /api/search, --tz on the CLI, UTC by default): snaps land on local midnight across daylight-saving changes and a timestamp without an offset is local, as in Splunk
subsearch [ ... ]parsed onlyplanner rejects it
search later in the pipelinepushedfree-text terms need _raw to still be present

Commands

CommandStatusNotes
statspushedby-fields with null values are dropped, output sorted by by-fields, as Splunk does
evalpushedsee functions below; each assignment sees earlier ones
wherepushed
fields +/-pushedfields + keeps internal _ fields like Splunk; fields + wildcards match materialized columns only, fields - wildcards also hide search-time fields
renamepushedwildcards with the same number of * on both sides (rename client* AS src_*, rename avg(*) AS *); pairs apply in order; columns are renamed in SQL, search-time fields through the rename (in event output too)
tablepushed
sortpusheddefault limit 10000 like Splunk; sort 0 for unlimited; mixed numeric/string ordering, which survives table and fields turning a search-time field into a string
headpushedhead N, head limit=N, head (expr) with keeplast and null; an expression without a count has no default limit, and with a count the stricter one wins, as in Splunk; head (expr) cannot follow a timechart pivot yet
deduppusheddedup N fields and sortby (+|-)field...; sortby decides which results are kept and orders the output, ties keep the incoming order; no keepevents, keepempty, consecutive yet
top, rarepushedlimit (limit=0 for every value), showcount, showperc, countfield, percentfield, useother, otherstr, by; the OTHER row sums the count and percent of the values past the limit and comes last in its group
timechartpushed, pivot in-processspan= or automatic; empty buckets filled; by yields one column per series; limit=N, limit=topN, limit=bottomN (default 10, 0 for all) keep the series with the largest (smallest) sum of the first aggregation, and useother (default true) with otherstr sums the rest into an OTHER series, as Splunk does; usenull (default true) and nullstr (default NULL) count events without the split field as a series of their own; cont=false leaves out the buckets no event fell in
bin, bucketpushedtime spans and numeric spans
rexpushednamed groups; max_match=N (0 for every match) makes multivalue fields; mode=sed with s/regex/replacement/flags (flags g, i; \1 to \9 in the replacement) and y/from/to/, any delimiter; RE2 syntax rather than PCRE; no s///N for the Nth match other than the first
chartpushed, pivot in-processchart agg... over x by y, by x y, by x, or no split; options before or after the split fields. Without a column field it is stats by the row field. With one, one column per value of it, rows sorted by the row field, and cells no result filled stay null (chart ... | fillnull value=0 fills them); limit=N, topN, bottomN (default 10, 0 for all), useother, otherstr as in timechart; usenull (default true) and nullstr (default NULL) count results without the column field under a column of their own. Results without the row field are dropped. Several aggregations give agg: value columns. span= buckets the row field first, as bin does (a time span on _time, a number on a number field). A where clause (where max in top3, where sum notin bottom2, where count > 100; count, sum, avg, min or max of the first aggregation's cells) keeps the columns that pass, in place of limit and without an OTHER column, as in Splunk. No bins, cont, format, sep or agg= yet
eventstatspushedevery stats function, with by: window aggregates over the whole result or per group, added to every result in its order; a result without a BY field gets no values, as in Splunk; a field of the same name is replaced. No allnum yet
streamstatspushedevery stats function, with by, current (default true), window=N (0 for every result so far) and global (default true: with by, the group's results among the last N of any group; false: the group's own last N), as window aggregates in result order (newest first for events, as Splunk streams them). A result without a BY field, or whose window is empty (the first with current=false), gets no values. reset_on_change=true (with by) starts the counts over where the BY values change from one result to the next; reset_before="(expr)" starts over at a result where the expression holds, reset_after="(expr)" after it; time_window=<span> (seconds to weeks, not months; with current=true and no window=) counts the results within that much time before each one, in the stream's time order. No allnum yet
mvexpandpushedmvexpand field [limit=N]: one result per value, in the multivalue field's order, the rest of the result copied (ARRAY JOIN); limit keeps the first N values. A multivalue field with no values keeps its result, with the field null; a single value stays as it is. A search-time field holding a JSON array expands into its elements
spathpushed; in-process without a path once the events' fields are goneinput= (default _raw), output=, path= or a bare path: keys joined with ., {} for every element of an array (a multivalue field). A JSON string comes out unquoted, any other value as its JSON text, null or a missing path as no value. Without a path the input's JSON object joins the search-time fields, so later commands (SQL included) see its keys as a.b.c; after table, fields or a transforming command that runs in-process instead, with Splunk's names (a{}, a{}.b). {N} is the element N of an array, counting from 0 as mvindex does (a{0}.b, c{1}{0}), and no value past its end. JSON only, no XML
fillnullpushed; in-process without a field list on eventsvalue= (default 0) and a field list, whose fields are created where they do not exist, as in Splunk; without a field list every field is filled. A number column filled with a number stays a number, anything else becomes a string; a multivalue field with no values counts as null. Without a field list on events the fields to fill are only known per event, so it fills the flattened events in-process, and what follows runs in-process too (see below); after a pivot it runs in-process as well. No wildcards in the field list yet
transaction, join, append, lookupnot yetparse as generic, rejected by the planner

In-process execution

Once a command has to run in the engine (the timechart and chart pivot, fillnull without a field list on events, spath without a path once the events' fields are gone), everything after it runs in the engine too, on the rows the query returned. These commands have an in-process form, with the semantics of their SQL form: search (terms against _raw, field comparisons), where, eval (every function below), fields, table, rename, head (with an expression too), sort, dedup, bin, rex (extraction and mode=sed), stats, eventstats, streamstats (with its resets and time_window), top, rare, timechart, chart, fillnull, spath and mvexpand. A transforming command there turns the output into a results table. The rest (transaction, lookup, ...) are not built at all yet.

The in-process steps see only the rows the query returned: after fillnull or spath on events that is at most the event cap (see SEARCH-LIMITS.md), so an in-process stats over more events than that counts only the newest ones, and the result says limited. After a pivot the rows are the chart's, which is the usual case.

Known deviations in commands

  • timechart, bin and the timeline bucket time in UTC even when the search has another time zone, so span=1d buckets start at UTC midnight; Splunk uses the user's zone.
  • timechart's and chart's OTHER series is the cell-by-cell sum of the series it replaces, whatever the aggregation, which is how Splunk filters series after aggregating; for avg, max or dc that is a sum of averages, maxima or distinct counts.
  • chart's NULL column (usenull=true) is ranked for limit like any other column, and so is timechart's NULL series.
  • chart span= on a number field labels each bucket with its lower bound (0, 10000), as bin does; Splunk labels it as a range (0-10000).
  • chart's where clause takes in topN, notin topN, in bottomN, notin bottomN, or a comparison to a number; Splunk's limit inside it is not accepted.
  • streamstats time_window follows the time order the results stream in (newest first for events, oldest first after sort _time) and uses the _time field, so it cannot follow a command that drops _time; reset_on_change compares the BY values as the previous result had them, so a result without the BY field (which gets no values) still ends a run.
  • spath's array index is {N} counting from 0, and {-1} is rejected; Splunk documents {N} without saying where it counts from.
  • A rex with max_match other than 1 that matches nothing gives an empty multivalue field rather than no field, because ClickHouse arrays cannot be null; mvcount of it is 0, not null.
  • fillnull without a field list on events fills the fields of the events returned (at most the event cap), so a field that only appears in events past the cap is not created in the ones shown.
  • spath without a path, while the events' search-time fields are still there, keeps a JSON array under its own key (tags, multivalue) rather than Splunk's tags{}, and does not flatten objects inside arrays (Splunk's items{}.id); the in-process form, after table or fields, uses Splunk's names.
  • spath without a path does not overwrite a field the search already has as a column (host and the other default fields, or one made by eval or rex): the column wins. Splunk overwrites it. A fields - or wildcard rename of search-time fields before spath also applies to the fields spath extracts.
  • spath with a path gives numbers as ClickHouse prints them (1.50 as 1.5, 1e3 as 1000); in-process spath keeps the JSON text.
  • In-process min and max compare numerically when every value has a number and as strings otherwise, where SQL goes by the column's type; values sorts as strings in both. In-process stdev and var of a single value are null, where SQL gives nan.
  • In-process stats, chart and top keep a search-time field's stored type in the BY values (a status ingested as a number comes out as a number), where SQL groups by the field's text.
  • In-process search matches a free-text term against _raw the way the query would (a bare word as a token, a phrase as a substring, * as a wildcard, without regard to case), and field=value with the same case and wildcard rules; earliest= and latest= are rejected there as they are in a later search in SQL.
  • A wildcard rename that moves a search-time field onto the name of a column the query already has (rename data.* AS * with a JSON key data.host) keeps the column's value; Splunk overwrites it.

Stats functions

29 functions.

  • count
  • count(field)
  • count(eval(cond))
  • dc
  • distinct_count
  • estdc
  • sum
  • sumsq
  • avg
  • mean
  • min
  • max
  • median
  • perc<N>
  • p<N>
  • exactperc<N>
  • stdev
  • stdevp
  • var
  • varp
  • range
  • mode
  • first
  • last
  • earliest
  • latest
  • values
  • list
  • rate

Known deviations

  • min/max on search-time fields compare numerically; Splunk falls back to lexicographic order when values are not numbers.
  • first/last use _time order, which is what Splunk does for a time-ordered search but not after a sort; the same goes for eventstats and streamstats.
  • list keeps the first 100 values, Splunk's default list_maxsize; values has no cap.
  • rate(x) is (latest x − earliest x) / (latest − earliest time) over the events that have x, null when those times are equal.

Eval functions

58 functions.

  • if
  • case
  • coalesce
  • nullif
  • null
  • isnull
  • isnotnull
  • isnum
  • isint
  • isstr
  • true
  • false
  • exact
  • len
  • lower
  • upper
  • trim
  • ltrim
  • rtrim
  • substr
  • replace
  • split
  • mvcount
  • mvindex
  • mvjoin
  • mvfilter
  • round
  • abs
  • ceil
  • ceiling
  • floor
  • sqrt
  • exp
  • ln
  • log
  • pow
  • max
  • min
  • random
  • tonumber
  • tostring
  • "hex"
  • "commas"
  • "duration"
  • now
  • relative_time
  • strftime
  • strptime
  • match
  • like
  • cidrmatch
  • md5
  • sha1
  • sha256
  • sha512
  • urldecode
  • json_extract
  • spath

Known deviations

  • String concatenation with . treats null as an empty string.
  • _time inside eval is epoch seconds, as in Splunk; strftime and strptime take Splunk's directives: %M, %h and %r, which ClickHouse reads differently, are translated, and the rest of the common set (%Y %y %m %d %e %H %I %S %p %j %a %b %T) means the same in both; Splunk's subsecond %N is not supported.
  • round(x, n) needs a literal n.
  • relative_time(x, modifier), the base of tonumber and the format of tostring need literals; relative_time, strftime and strptime work in the search's time zone.
  • tostring(x, "commas") rounds to two decimals and drops trailing zeros (1,234.5); tostring(x, "duration") ignores fractions of a second and writes D+HH:MM:SS past a day.
  • mvfilter reads one field; with nothing left it gives an empty multivalue rather than null, as rex does.
  • Regular expressions in match, replace and rex are RE2; PCRE-only syntax such as lookbehind will fail.