Applies to VAST Cluster 5.5.
Functions › List, struct and map functions › Combine and change lists. Returns a list cut, or padded, to a given number of elements. array_resize is the same function.
Syntax
list_resize(list, size)
list_resize(list, size, fill)
array_resize(list, size)
array_resize(list, size, fill)
| Argument | Type | Description |
|---|---|---|
list |
A list of any type | The list to cut or pad. |
size |
A number (or a string that holds one) | The number of elements in the result. Not negative. |
fill |
A value of the list's element type | The value to pad with. Optional; NULL if you leave it out. |
Returns: a list of the same type as list.
Aliases: array_resize.
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_lists_list_resize. 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 players, each with a list of scores (scores), a list of badges (badges) and a default score (base). Player 2 has a NULL score, player 3 has empty lists, and player 4 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_lists_list_resize"'
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}.players"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, scores BIGINT[], badges VARCHAR[], base BIGINT)")
run(f"INSERT INTO {TABLE} VALUES "
"(1, [1, 2, 3], ['gold', 'silver', 'bronze'], 2), "
"(2, [3, NULL], ['silver'], -1), "
"(3, [], [], 0), "
"(4, NULL, NULL, NULL)")
[]
[]
[(4,)]
What it does
list_resize returns a list of exactly size elements:
- A longer list is cut to its first
sizeelements:list_resize([1, 2, 3], 2)is[1, 2].fillisn't used. - A shorter list is padded at the end with
fill, or with NULLs if there's nofill:list_resize([1, 2, 3], 4)is[1, 2, 3, NULL], andlist_resize([1, 2, 3], 4, 42)is[1, 2, 3, 42]. - Only new positions are filled. A NULL element already in the list stays NULL:
list_resize(scores, 3, 0)on[3, NULL]gives[3, NULL, 0].
fill is converted to the list's element type: list_resize([1], 3, 2.5) is the INTEGER[] list [1, 3, 3], with 2.5 rounded to 3. size is converted to a whole number in the same way, so a size of 2.5 gives three elements. On a list of lists, fill is a list: list_resize([[1]], 2, [0]) is [[1], [0]].
NULLs and edge cases
| Input | Result |
|---|---|
list_resize(NULL, 2) or list_resize(NULL, 2, 0) |
NULL |
list_resize([1, 2], NULL) |
[] |
list_resize([1], 3, NULL) |
[1, NULL, NULL] |
list_resize([1, 2, 3], 0) |
[] |
list_resize([], 3) |
[NULL, NULL, NULL] |
list_resize([1, 2], 2.5), list_resize([1, 2], '3') |
[1, 2, NULL] |
list_resize([1], 3, 2.5) |
[1, 3, 3], an INTEGER[] |
list_resize(['a'], 3, 'x') |
['a', 'x', 'x'] |
len(list_resize([1], 100000)) |
100000 |
list_resize([1, 2], -1) |
Error: Type INT32 with value -1 can't be cast because the value is out of range for the destination type UINT64 |
list_resize([1], 3, 'x') |
Error: Could not convert string 'x' to INT32 |
list_resize([], 3, 'x') (an empty list with no type) |
Error: Unimplemented type for cast (VARCHAR -> "NULL") |
A number, string, date, boolean or BLOB as list |
Error: No function matches the given name and argument types, for example 'list_resize(INTEGER_LITERAL, INTEGER_LITERAL)' |
The same cases, as the cluster returns them. They use the helpers from Set up the examples:
run("SELECT list_resize(NULL, 2), list_resize(NULL, 2, 0), list_resize([1, 2], NULL), list_resize([1], 3, NULL), "
"list_resize([1, 2, 3], 0), list_resize([], 3)")
run("SELECT list_resize([1, 2], 2.5), list_resize([1, 2], '3'), list_resize([1], 3, 2.5), "
"typeof(list_resize([1], 3, 2.5)), list_resize(['a'], 3, 'x'), len(list_resize([1], 100000))")
run("SELECT list_resize([1, 2], -1)")
run("SELECT list_resize([1], 3, 'x')")
run("SELECT list_resize([], 3, 'x')")
run("SELECT list_resize(123, 2)")
[(None, None, [], [1, None, None], [], [None, None, None])]
[([1, 2, None], [1, 2, None], [1, 3, 3], 'INTEGER[]', ['a', 'x', 'x'], 100000)]
OSError: Type INT32 with value -1 can't be cast because the value is out of range for the destination type UINT64
OSError: Could not convert string 'x' to INT32
OSError: Unimplemented type for cast (VARCHAR -> "NULL")
InternalError: Binder Error: No function matches the given name and argument types 'list_resize(INTEGER_LITERAL, INTEGER_LITERAL)'. You might need to add explicit type casts.
Examples
Keep each player's first two scores, and pad every list of scores to four with 0. Player 3's empty list is padded too; player 4's NULL list stays NULL:
run(f"SELECT id, list_resize(scores, 2), list_resize(scores, 4, 0) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, [1, 2], [1, 2, 3, 0]), (2, [3, None], [3, None, 0, 0]), (3, [None, None], [0, 0, 0, 0]), (4, None, None)]
Pad to three with a value from another column, and pad the badges with 'none'. array_resize gives the same lists:
run(f"SELECT id, list_resize(scores, 3, base), array_resize(scores, 3, base), list_resize(badges, 3, 'none') "
f"FROM {TABLE} ORDER BY id LIMIT 10")
[(1, [1, 2, 3], [1, 2, 3], ['gold', 'silver', 'bronze']), (2, [3, None, -1], [3, None, -1], ['silver', 'none', 'none']), (3, [0, 0, 0], [0, 0, 0], ['none', 'none', 'none']), (4, None, None, None)]
Keep the players whose top two badges include 'silver':
run(f"SELECT id FROM {TABLE} WHERE list_contains(list_resize(badges, 2), 'silver') ORDER BY id LIMIT 10")
[(1,), (2,)]
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). The scan reads a list column whole, and VAST DataBase doesn't evaluate list functions, so list_resize() is always computed by the Query Engine:
- In the
SELECTlist, the scan reads the list column and the Query Engine computeslist_resize()for each row it returns. - In
WHERE, the scan sends the list for every row, and aFILTERstep in the Query Engine computes the condition 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 list_contains(list_resize(badges, 2), 'silver')")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ list_contains(list_resize │
│ (badges, 2), 'silver') │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ badges │
│ id │
│ │
└───────────────────────────┘
Make it faster
- Pair a list condition with a pushed-down condition. In
base >= 0 AND list_contains(list_resize(badges, 2), 'silver'),base >= 0is evaluated in VAST DataBase, so the list condition runs only on the rows that pass it. TheFILTERstep in the plan holds only the list condition:
plan(f"SELECT id FROM {TABLE} WHERE base >= 0 AND list_contains(list_resize(badges, 2), 'silver')")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ list_contains(list_resize │
│ (badges, 2), 'silver') │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ badges │
│ id │
│ │
└───────────────────────────┘
- Pad when you write, if every reader pads. If every query pads the lists to the same length, store them padded, so queries read them as they are.
Limitations and errors
sizecan't be negative.list_resize([1, 2], -1)fails withType INT32 with value -1 can't be cast because the value is out of range for the destination type UINT64. Ifsizecomes from a calculation, clamp it withgreatest(n, 0).fillmust convert to the element type. A string that isn't a number, used to pad anINTEGER[], fails withCould not convert string 'x' to INT32. A fill that does convert is changed silently:2.5becomes3in anINTEGER[].- An empty list literal has no element type.
list_resize([], 3, 'x')fails withUnimplemented type for cast (VARCHAR -> "NULL"). Give the list a type when it's a literal; a list column always has one. - A NULL
sizegives an empty list, not NULL.
run("SELECT list_resize([1, 2], greatest(-1, 0)), list_resize([]::VARCHAR[], 3, 'x')")
[([], ['x', 'x', 'x'])]
Similar functions
| Function | Result for [3, 1, NULL, 3, 10] |
|---|---|
list_resize(l, 2) |
[3, 1] |
list_slice: list_slice(l, 2, 3), elements 2 to 3 |
[1, NULL] |
l[2:3]: the same slice |
[1, NULL] |
| array_pop_back: all but the last element | [3, 1, NULL, 3] |
list_append: list_append(l, 0), one more element |
[3, 1, NULL, 3, 10, 0] |
run("SELECT list_resize(l, 2), list_slice(l, 2, 3), l[2:3], array_pop_back(l), list_append(l, 0) "
"FROM (SELECT [3, 1, NULL, 3, 10] AS l)")
[([3, 1], [1, None], [1, None], [3, 1, None, 3], [3, 1, None, 3, 10, 0])]
Clean up:
run(f"DROP TABLE IF EXISTS {TABLE}")
run(f"DROP SCHEMA IF EXISTS {SCHEMA}")
conn.close()
[]
[]
See also
- list_slice: take a range of elements.
- List, struct and map functions: all the list functions.
- Where filters run and pushdowns.
- EXPLAIN: how to read a plan.