Applies to VAST Cluster 5.5.
Functions › Text basics › Changing text. Returns a string, a BLOB or a list repeated a number of times, joined end to end. repeat has no aliases.
Syntax
repeat(s, count)
| Argument | Type | Description |
|---|---|---|
s |
VARCHAR, BLOB or a list |
The value to repeat. |
count |
BIGINT |
How many times to repeat it. An integer literal such as 2, or an INTEGER column, works. 0 or less gives an empty value of the type of s. |
Returns: the type of s: VARCHAR, BLOB, or a list of the same element type.
Aliases: none.
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_repeat. 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 four tasks in an outline: each has a depth, a list of tags and a marker byte string. 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_repeat"'
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}.tasks"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, title VARCHAR, depth BIGINT, tags VARCHAR[], marker BLOB)")
run(f"INSERT INTO {TABLE} VALUES "
"(1, 'Plan the release', 0, ['planning'], '\\xAA'::BLOB), "
"(2, 'Write the notes', 1, ['docs', 'release'], '\\x00\\xFF'::BLOB), "
"(3, 'Review the notes', 2, [], ''::BLOB), "
"(4, NULL, NULL, NULL, NULL)")
[]
[]
[(4,)]
What it does
repeat joins count copies of s end to end. It has one form for each type of s:
Type of s |
Example | Result |
|---|---|---|
VARCHAR |
repeat('Hello', 2) |
'HelloHello' |
BLOB |
repeat('\xAA'::BLOB, 2) |
b'\xaa\xaa' (two bytes) |
| List | repeat([1, 2], 2) |
[1, 2, 1, 2] |
| List | repeat(['a', 'b'], 2) |
['a', 'b', 'a', 'b'] |
On a list, repeat repeats the elements, not the list: the result is one flat list, not a list of lists. A count of 0 or less returns an empty value of the same type: '', an empty BLOB or an empty list (repeat([1, 2], 0) and repeat([1], -1) both return []). An empty list or an empty BLOB stays empty. The result can be large: repeat('ab', 1000000) returns a string of 2,000,000 characters.
repeat used in a FROM clause is a different function with the same name: a table function that returns a value in a number of rows. See Table functions.
NULLs and edge cases
| Input | Call | Result |
|---|---|---|
s is NULL |
repeat(NULL, 2) |
NULL |
count is NULL |
repeat('ab', NULL) |
NULL |
'' (empty string) |
repeat('', 2), repeat('', 5) |
'', whatever the count |
count is 0 |
repeat('ab', 0) |
'' |
count is negative |
repeat('ab', -1) |
'' |
Type of an untyped NULL |
typeof(repeat(NULL, 2)) |
'BLOB': an untyped NULL picks the BLOB form |
Type of a VARCHAR NULL |
typeof(repeat(CAST(NULL AS VARCHAR), 2)) |
'VARCHAR' |
A BLOB |
repeat('\xAA'::BLOB, 2) |
b'\xaa\xaa' |
| A list of integers | repeat([1, 2], 2) |
[1, 2, 1, 2] |
| A list of strings | repeat(['a', 'b'], 2) |
['a', 'b', 'a', 'b'] |
| A large count | length(repeat('ab', 1000000)) |
2000000 |
count is a decimal, such as 2.6 |
repeat('ab', 2.6) |
Error: No function matches the given name and argument types |
A number as s |
repeat(123, 2) |
Error: No function matches the given name and argument types |
The same cases, as the cluster returns them. They use the table and helpers from Set up the examples:
run("SELECT repeat(NULL, 2), repeat('ab', NULL), repeat('', 2), repeat('', 5), repeat('ab', 0), repeat('ab', -1)")
run("SELECT typeof(repeat(NULL, 2)), typeof(repeat(CAST(NULL AS VARCHAR), 2))")
run("SELECT repeat('\\xAA'::BLOB, 2), repeat([1, 2], 2), repeat(['a', 'b'], 2), length(repeat('ab', 1000000))")
run("SELECT repeat('ab', 2.6)")
run("SELECT repeat(123, 2)")
[(None, None, '', '', '', '')]
[('BLOB', 'VARCHAR')]
[(b'\xaa\xaa', [1, 2, 1, 2], ['a', 'b', 'a', 'b'], 2000000)]
InternalError: Binder Error: No function matches the given name and argument types 'repeat(STRING_LITERAL, DECIMAL(2,1))'. You might need to add explicit type casts.
InternalError: Binder Error: No function matches the given name and argument types 'repeat(INTEGER_LITERAL, INTEGER_LITERAL)'. You might need to add explicit type casts.
Examples
Indent each title by its depth, two dots per level. A depth of 0 gives an empty indent, and the row with no values gives NULL:
run(f"SELECT id, repeat('..', depth) || title FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 'Plan the release'), (2, '..Write the notes'), (3, '....Review the notes'), (4, None)]
Repeat a list and a BLOB column. Each result keeps the type of its column: a list of VARCHAR and a BLOB:
run(f"SELECT id, repeat(tags, 2), repeat(marker, 3) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, ['planning', 'planning'], b'\xaa\xaa\xaa'), (2, ['docs', 'release', 'docs', 'release'], b'\x00\xff\x00\xff\x00\xff'), (3, [], b''), (4, None, None)]
Keep the tasks whose indent is two levels deep. Make it faster shows a cheaper way to write this filter:
run(f"SELECT id, title FROM {TABLE} WHERE repeat('..', depth) = '....' ORDER BY id LIMIT 10")
[(3, 'Review the notes')]
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). VAST DataBase can evaluate a comparison of a column with a constant, but not a function of a column, so repeat() is always computed by the Query Engine:
- In the
SELECTlist, the scan reads the columns and the Query Engine computesrepeat()for each row it returns. - In
WHERE, the scan sends the columns for every row, and aFILTERstep in the Query Engine computesrepeat()and keeps the rows that match. No rows are skipped in storage.
The plan of the WHERE example shows the FILTER step above the scan:
plan(f"SELECT id FROM {TABLE} WHERE repeat('..', depth) = '....'")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ (repeat('..', depth) = '..│
│ ..') │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ depth │
│ id │
│ │
└───────────────────────────┘
Make it faster
- Filter on the count, not on the repeated value.
repeat('..', depth) = '....'keeps the same rows asdepth = 2, but only the second is a comparison of a raw column with a constant, which is pushed down to VAST DataBase, so rows that don't match never leave storage. Its plan has noFILTERstep:
plan(f"SELECT id FROM {TABLE} WHERE depth = 2")
┌───────────────────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ Projections: id │
│ │
└───────────────────────────┘
- Mind the size of the result. The result is
counttimes the size ofs, for every row. A large count on a long column makes every returned row that much larger.
Limitations and errors
- Strings,
BLOBvalues and lists only. A number, a date or a boolean fails withNo function matches the given name and argument types. Cast it toVARCHARfirst. - An integer count. A decimal count, such as
2.6, fails withNo function matches the given name and argument types. Cast it toBIGINTfirst; the cast rounds, soCAST(2.6 AS BIGINT)repeats 3 times. - An untyped
NULLgives aBLOB.repeat(NULL, 2)uses theBLOBform. Cast theNULLif the type matters:repeat(CAST(NULL AS VARCHAR), 2). spaceisn't available.space(n)fails withScalar Function with name space does not exist!. Userepeat(' ', n).
run("SELECT repeat('ab', CAST(2.6 AS BIGINT)), repeat(' ', 3) || '|'")
run("SELECT space(3)")
[('ababab', ' |')]
InternalError: Catalog Error: Scalar Function with name space does not exist!
Similar functions
| Function | What it repeats | Call | Result |
|---|---|---|---|
repeat on a string |
the string | repeat('ab', 2) |
'abab' |
repeat on a list |
the elements of the list, in one flat list | repeat([1, 2], 2) |
[1, 2, 1, 2] |
repeat on a BLOB |
the bytes | repeat('\xAA'::BLOB, 2) |
b'\xaa\xaa' |
| repeat (table function) | a value, in rows | SELECT * FROM repeat(42, 2) |
rows that hold 42 |
The list and BLOB forms are also described in List functions and Binary functions. To pad a string to a given length, use lpad and rpad. replace, translate and reverse are the other functions that change text.
run("SELECT repeat('ab', 2), repeat([1, 2], 2), repeat('\\xAA'::BLOB, 2)")
run("SELECT * FROM repeat(42, 2)")
[('abab', [1, 2, 1, 2], b'\xaa\xaa')]
[(42,), (42,)]
Clean up:
run(f"DROP TABLE IF EXISTS {TABLE}")
run(f"DROP SCHEMA IF EXISTS {SCHEMA}")
conn.close()
[]
[]
See also
- Text basics: all the text functions.
- Table functions:
repeatinFROM. - Where filters run and pushdowns.
- EXPLAIN: how to read a plan.