Applies to VAST Cluster 5.5.
Functions › Text basics › Length. Returns the number of bits in a string's UTF-8 encoding (eight per byte), or the number of bits in a BIT value. It has no aliases.
Syntax
bit_length(s)
bit_length(bits)
| Argument | Type | Description |
|---|---|---|
s |
VARCHAR |
The string to measure. |
bits |
BIT |
The bit string to measure. |
Returns: BIGINT.
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_bit_length. 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 events. Each event has a label and a set of flags, stored as a string of 0 and 1 characters. 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_bit_length"'
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}.events"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, label VARCHAR, flags VARCHAR)")
run(f"INSERT INTO {TABLE} VALUES "
"(1, 'login', '101'), "
"(2, 'café order', '11110000'), "
"(3, '', '1'), "
"(4, NULL, NULL)")
[]
[]
[(4,)]
What it does
bit_length measures a value in bits:
| Argument type | What it counts | Example | Result |
|---|---|---|---|
VARCHAR |
bits in the UTF-8 encoding: 8 times strlen | bit_length('abc') |
24 |
VARCHAR with a two-byte letter |
8 × 2 bytes | bit_length('é') (U+00E9) |
16 |
BIT |
bits in the bit string, the same as length on BIT |
bit_length('101'::BIT) |
3 |
On a string, bit_length follows the byte count, not the character count: 'José' has 4 characters, 5 bytes and 40 bits, and 'e\u0301' (e and a combining accent, displayed as é) has 2 code points, 3 bytes and 24 bits. On a BIT value it counts the bits as written, so '101'::BIT gives 3, not a multiple of 8.
A table column can't have the BIT type: CREATE TABLE with a BIT column fails with BIT type is not supported in table definitions. Store bit strings as VARCHAR and cast them in the query, as the examples do with CAST(flags AS BIT).
NULLs and edge cases
| Input | Result |
|---|---|
NULL |
NULL |
'' (empty string) |
0 |
'é' (U+00E9) |
16 |
'e\u0301' (e and a combining accent) |
24 |
'101'::BIT |
3 |
A BLOB |
Error: No function matches the given name and argument types 'bit_length(BLOB)'; use 8 * octet_length(b) |
A list, such as ['a', 'b'] |
Error: No function matches the given name and argument types 'bit_length(VARCHAR[])' |
| A number, date or boolean | Error: No function matches the given name and argument types, for example 'bit_length(INTEGER_LITERAL)' |
The same cases, as the cluster returns them. They use the table and helpers from Set up the examples:
run("SELECT bit_length(NULL), bit_length(''), bit_length('é'), bit_length('e\u0301'), bit_length('101'::BIT)")
run("SELECT bit_length('\\xAA'::BLOB)")
run("SELECT bit_length(['a', 'b'])")
run("SELECT bit_length(123)")
[(None, 0, 16, 24, 3)]
InternalError: Binder Error: No function matches the given name and argument types 'bit_length(BLOB)'. You might need to add explicit type casts.
InternalError: Binder Error: No function matches the given name and argument types 'bit_length(VARCHAR[])'. You might need to add explicit type casts.
InternalError: Binder Error: No function matches the given name and argument types 'bit_length(INTEGER_LITERAL)'. You might need to add explicit type casts.
Examples
The bits of each label, next to its bytes: bit_length is always 8 times strlen on a string. The flags are cast to BIT, so bit_length counts the flags themselves:
run(f"SELECT id, bit_length(label), strlen(label), bit_length(CAST(flags AS BIT)) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, 40, 5, 3), (2, 88, 11, 8), (3, 0, 0, 1), (4, None, None, None)]
Keep only the labels that take more than 64 bits (8 bytes). café order has 10 characters and 11 bytes:
run(f"SELECT id, label FROM {TABLE} WHERE bit_length(label) > 64 ORDER BY id LIMIT 10")
[(2, 'café order')]
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 bit_length() is always computed by the Query Engine:
- In the
SELECTlist, the scan reads the column and the Query Engine computesbit_length()for each row it returns. - In
WHERE, the scan sends the column for every row, and aFILTERstep in the Query Engine computesbit_length()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 bit_length(label) > 64")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ (bit_length(label) > 64) │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ label │
│ id │
│ │
└───────────────────────────┘
Make it faster
- Store the size if you filter on it often.
bit_length(s) > 64andstrlen(s) > 8select the same rows, and both are computed in aFILTERstep. Compute the size when you write the rows and keep it in its own column:label_bytes > 8is pushed down to VAST DataBase,bit_length(label) > 64isn't. - Test for empty strings with
= '', notbit_length(s) = 0. A comparison of the raw column with a constant is pushed down, so rows that don't match never leave storage. The plan forlabel = ''has noFILTERstep:
plan(f"SELECT id FROM {TABLE} WHERE label = ''")
┌───────────────────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ Projections: id │
│ │
└───────────────────────────┘
- Combine it with a pushed-down condition. In
id > 1 AND bit_length(label) > 64,id > 1is still evaluated in VAST DataBase, sobit_length()runs only on the rows that pass it.
Limitations and errors
- Strings and bit strings only. A number, a date, a boolean or a list fails with
No function matches the given name and argument types; the error listsbit_length(VARCHAR)andbit_length(BIT)as the forms it accepts. - No
BITcolumns.CREATE TABLEwith aBITcolumn fails withBIT type is not supported in table definitions. Store the bits as aVARCHARof0and1characters and usebit_length(CAST(col AS BIT)). - Not for
BLOB.bit_lengthof aBLOBfails the same way. Multiply the byte count instead:8 * octet_length(b).
run("SELECT bit_length('\\xAA\\xBB'::BLOB)")
run("SELECT 8 * octet_length('\\xAA\\xBB'::BLOB)")
InternalError: Binder Error: No function matches the given name and argument types 'bit_length(BLOB)'. You might need to add explicit type casts.
[(16,)]
Similar functions
| Function | Counts | Result for 'José' (bit_length gives 40) |
Result for '101'::BIT (bit_length gives 3) |
|---|---|---|---|
| strlen | bytes in the UTF-8 encoding | 5 |
(VARCHAR only) |
| length | code points (for a BIT value, bits) |
4 |
3 |
| length_grapheme | grapheme clusters: what a reader sees as characters | 4 |
(VARCHAR only) |
| octet_length | bytes of a BLOB; also takes a BIT value |
(not on VARCHAR) |
see its page |
run("SELECT bit_length('José'), strlen('José'), length('José'), length_grapheme('José')")
run("SELECT bit_length('101'::BIT), length('101'::BIT)")
[(40, 5, 4, 4)]
[(3, 3)]
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.
- Bit strings:
bit_lengthand the other functions onBITvalues. - Where filters run and pushdowns.
- EXPLAIN: how to read a plan.