Welcome to the new DOCS.vastdata.com. We've added new menus and a version selector, plus new persona and resource pages to help customers get started. Looking for a previous article? Use the docs search.

regexp_matches

Prev Next

Applies to VAST Cluster 5.5.

Functions › Regular expressions › Regular expressions. regexp_matches(s, pattern) is true if the regular expression pattern matches any part of s. Anchor the pattern with ^ and $ to test the whole string, or use regexp_full_match.

Syntax

regexp_matches(s, pattern)
regexp_matches(s, pattern, options)
Argument Type Description
s VARCHAR The string to test.
pattern VARCHAR The regular expression (syntax). A constant or a column.
options VARCHAR Optional, a constant. Flags such as 'i' (ignore case), 's' (. matches a line break) or 'l' (literal text); see options.

Returns: BOOLEAN.

Set up the examples

The examples connect with the ADBC driver, as in VAST Query Engine (ADBC), and work in a schema called docs_sql_fn_string_regexp_matches. Set QE_ENDPOINT, AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and VASTDB_BUCKET (SQL reference), and run the blocks in order in one Python session. Below each block is the output you should see.

This block connects, defines the run() and plan() helpers, and creates a table of five log lines. Line 4 is empty, and the last row has no values:

import os
import re

import adbc_driver_manager
import adbc_driver_manager.dbapi as dbapi
import adbc_driver_vastdb

BUCKET = os.environ["VASTDB_BUCKET"]
SCHEMA = f'"{BUCKET}/docs_sql_fn_string_regexp_matches"'

conn = dbapi.connect(
    driver=adbc_driver_vastdb.get_driver_path(),
    db_kwargs={
        "vast.db.endpoint": os.environ["QE_ENDPOINT"],
        "vast.db.access_key": os.environ["AWS_ACCESS_KEY_ID"],
        "vast.db.secret_key": os.environ["AWS_SECRET_ACCESS_KEY"],
    },
    autocommit=True,
)

def server_message(e):
    """The server's message in a driver error, without the request ID or the echoed statement."""
    text = re.sub(r"\[req=0x[0-9a-f]+\]: ", "", str(e))
    found = (re.search(r'message: "(.*?)", request_id', text, re.S)
             or re.search(r"description: (.*)\)", text, re.S))
    message = found.group(1) if found else text.strip().splitlines()[-1]
    message = re.sub(r"^Plan query: '.*?' failed with: ", "", message, flags=re.S)
    message = re.split(r"\\n|\n", message)[0].replace('\\"', '"')
    return message.replace(BUCKET, "<bucket>")

def run(sql):
    """Run one statement. Print the rows it returns, or the error."""
    try:
        with conn.cursor() as cur:
            cur.execute(sql)
            rows = cur.fetchall() if cur.description else []
    except (adbc_driver_manager.Error, OSError) as e:
        print(f"{type(e).__name__}: {server_message(e)}")
    else:
        print(rows)

def plan(sql):
    """Print the plan without its ~N Rows lines: the table's row count, which can still be 0 just after an insert."""
    with conn.cursor() as cur:
        cur.execute("EXPLAIN " + sql)
        steps = cur.fetchall()[0][1]
    print("\n".join(line for line in steps.splitlines() if "Rows" not in line))

TABLE = f"{SCHEMA}.logs"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, line VARCHAR)")
run(f"INSERT INTO {TABLE} VALUES "
    "(1, 'ERROR 2026-10-16 disk full'), "
    "(2, 'warn: Disk 93% full'), "
    "(3, 'INFO 2026-10-17 ok'), "
    "(4, ''), "
    "(5, NULL)")
[]
[]
[(5,)]

What it does

regexp_matches searches s for the first place where pattern matches, and returns true if there is one:

Call Result Why
regexp_matches('abc', 'b') true b matches in the middle
regexp_matches('abc', '^b') false ^ anchors the pattern at the start
regexp_matches('abc', '^abc$') true anchored at both ends: the whole string
regexp_matches('ABC', 'b', 'i') true option i ignores case
regexp_matches('axb', 'a.b', 'l') false option l: the pattern is literal text

The pattern syntax and the options are described on Regular-expression syntax and options. A . matches one code point, so e followed by a combining accent (U+0301) needs .., and 'cafe\u0301' doesn't match 'é$'.

NULLs and edge cases

Input Result
A NULL string or pattern NULL
A NULL options string Error: Regex options field must not be NULL
An empty pattern: regexp_matches('abc', ''), regexp_matches('', '') true
regexp_matches('', '.') false
regexp_matches('ABC', 'b', 'ic') (the last of i and c wins) false
regexp_matches('abc', 'b', 'g') Error: Option 'g' (global replace) is only valid for regexp_replace
An invalid constant pattern: regexp_matches('abc', '(') Error: missing ): (
A number, date, boolean, BLOB or list Error: No function matches the given name and argument types, for example 'regexp_matches(INTEGER_LITERAL, STRING_LITERAL)'

The same cases, as the cluster returns them. They use the table and helpers from Set up the examples:

run("SELECT regexp_matches(NULL, 'a'), regexp_matches('abc', NULL), regexp_matches('abc', ''), "
    "regexp_matches('', ''), regexp_matches('', '.'), regexp_matches('ABC', 'b', 'ic')")
run("SELECT regexp_matches('abc', 'b', NULL)")
run("SELECT regexp_matches('abc', 'b', 'g')")
run("SELECT regexp_matches('abc', '(')")
run("SELECT regexp_matches(123, '1')")
[(None, None, True, True, False, False)]
InternalError: Invalid Input Error: Regex options field must not be NULL
InternalError: Invalid Input Error: Option 'g' (global replace) is only valid for regexp_replace
InternalError: Invalid Input Error: missing ): (
InternalError: Binder Error: No function matches the given name and argument types 'regexp_matches(INTEGER_LITERAL, STRING_LITERAL)'. You might need to add explicit type casts.

Examples

Which lines hold a date, which mention a disk in any case, and which start with an upper-case word followed by a space:

run(f"SELECT id, regexp_matches(line, '\\d{{4}}-\\d{{2}}-\\d{{2}}'), regexp_matches(line, 'disk', 'i'), "
    f"regexp_matches(line, '^[A-Z]+ ') FROM {TABLE} ORDER BY id LIMIT 10")
[(1, True, True, True), (2, False, True, False), (3, True, False, True), (4, False, False, False), (5, None, None, None)]

Keep only the error and warning lines, in any case:

run(f"SELECT id, line FROM {TABLE} WHERE regexp_matches(line, '^(error|warn)', 'i') ORDER BY id LIMIT 10")
[(1, 'ERROR 2026-10-16 disk full'), (2, 'warn: Disk 93% full')]

How it runs in VAST

The Query Engine runs on the cluster's CNodes, and a query on a table starts with a scan in VAST DataBase (VAST_SCAN). In WHERE, the Query Engine first rewrites simple patterns into cheaper tests:

Pattern Becomes Runs in
'^literal$' (the whole string, no special characters) line = 'literal' VAST DataBase (pushed down; no FILTER step)
'^literal' prefix(line, 'literal') a FILTER step in the Query Engine
'literal$' suffix(line, 'literal') a FILTER step
'literal' contains(line, 'literal') a FILTER step
anything else, with option i, or from a column regexp_matches(...) a FILTER step

In a FILTER step, the scan sends the column for every row and the Query Engine keeps the rows that match. In the SELECT list, regexp_matches is computed for each row the scan returns. The plans of a whole-string pattern, a prefix and a general pattern:

plan(f"SELECT id FROM {TABLE} WHERE regexp_matches(line, '^INFO 2026-10-17 ok$')")
plan(f"SELECT id FROM {TABLE} WHERE regexp_matches(line, '^ERROR')")
plan(f"SELECT id FROM {TABLE} WHERE regexp_matches(line, '\\d{{4}}-\\d{{2}}')")
┌───────────────────────────┐
│         VAST_SCAN         │
│    ────────────────────   │
│    Function: VAST_SCAN    │
│      Projections: id      │
│                           │
└───────────────────────────┘
┌───────────────────────────┐
│         PROJECTION        │
│    ────────────────────   │
│             #1            │
│                           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│           FILTER          │
│    ────────────────────   │
│   prefix(line, 'ERROR')   │
│                           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│         VAST_SCAN         │
│    ────────────────────   │
│    Function: VAST_SCAN    │
│                           │
│        Projections:       │
│            line           │
│             id            │
│                           │
└───────────────────────────┘
┌───────────────────────────┐
│         PROJECTION        │
│    ────────────────────   │
│             #1            │
│                           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│           FILTER          │
│    ────────────────────   │
│   regexp_matches(line, '  │
│       \d{4}-\d{2}')       │
│                           │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│         VAST_SCAN         │
│    ────────────────────   │
│    Function: VAST_SCAN    │
│                           │
│        Projections:       │
│            line           │
│             id            │
│                           │
└───────────────────────────┘

Make it faster

  • Prefer a literal or anchored pattern. '^ERROR' runs as prefix() and 'disk' as contains(), which are cheaper than a general pattern. A whole-string literal is pushed down.
  • Use a range instead of a prefix. line >= 'ERROR' AND line < 'ERROS' selects the lines that start with ERROR, and it's pushed down to VAST DataBase; regexp_matches(line, '^ERROR') isn't. The plan has no FILTER step:
plan(f"SELECT id FROM {TABLE} WHERE line >= 'ERROR' AND line < 'ERROS'")
┌───────────────────────────┐
│         VAST_SCAN         │
│    ────────────────────   │
│    Function: VAST_SCAN    │
│      Projections: id      │
│                           │
└───────────────────────────┘
  • Combine it with a pushed-down condition. In id > 100 AND regexp_matches(line, 'disk'), id > 100 is still evaluated in VAST DataBase, so the pattern runs only on the rows that pass it.
  • Extract at load time. If you filter on a part of the line often, such as the level, store it in its own column with regexp_extract when you write the data.

Limitations and errors

  • Strings only. A number, date, boolean, BLOB or list fails with No function matches the given name and argument types. Cast the value to VARCHAR first.
  • The options string is a constant and isn't NULL. From a column it fails with Regex options field must be a constant; NULL fails with Regex options field must not be NULL.
  • A pattern from a column is checked row by row. One invalid value fails the query with the reason, such as missing ): l+( or trailing \.
  • Some pattern features aren't available, such as backreferences and lookahead: see syntax and options.
  • Other dialects' names aren't available. regexp_like and rlike fail with Scalar Function with name ... does not exist!; s REGEXP p and s RLIKE p fail with a syntax error. Use regexp_matches.
run(f"SELECT regexp_matches(line, 'a', CAST(id AS VARCHAR)) FROM {TABLE} LIMIT 1")
run("SELECT regexp_like('abc', 'b')")
run("SELECT 'abc' REGEXP 'b'")
InternalError: Invalid Input Error: Regex options field must be a constant
InternalError: Catalog Error: Scalar Function with name regexp_like does not exist!
InternalError: Parse query: 'SELECT 'abc' REGEXP 'b'' failed with: Parser Error: syntax error at or near "'b'"

Similar functions

Function Tests Result for 'xabcx' and abc
regexp_matches('xabcx', 'abc') a regular expression, anywhere true
regexp_full_match('xabcx', 'abc'), ~ a regular expression, the whole string false
SIMILAR TO 'abc' a regular expression, the whole string false
LIKE '%abc%' % and _ wildcards, the whole string true
contains('xabcx', 'abc') plain text, anywhere true
run("SELECT regexp_matches('xabcx', 'abc'), regexp_full_match('xabcx', 'abc'), 'xabcx' ~ 'abc', "
    "'xabcx' SIMILAR TO 'abc', 'xabcx' LIKE '%abc%', contains('xabcx', 'abc')")
[(True, False, False, False, True, True)]

Clean up:

run(f"DROP TABLE IF EXISTS {TABLE}")
run(f"DROP SCHEMA IF EXISTS {SCHEMA}")
conn.close()
[]
[]

See also