Applies to VAST Cluster 5.5.
Functions › Binary, hash and UUID functions › Bit strings. Returns the bit at a given index of a bit string, as the INTEGER 0 or 1. Index 0 is the leftmost bit: get_bit('101101'::BIT, 1) is 0.
Syntax
get_bit(bits, index)
| Argument | Type | Description |
|---|---|---|
bits |
BIT |
The bit string to read. |
index |
INTEGER |
The index of the bit, from 0 for the leftmost bit to length(bits) - 1. |
Returns: INTEGER (0 or 1).
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_binary_hash_uuid_get_bit. 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 feature-flag sets, stored as VARCHAR strings of 0s and 1s. The third 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_binary_hash_uuid_get_bit"'
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}.flags"
run(f"CREATE SCHEMA IF NOT EXISTS {SCHEMA}")
run(f"CREATE TABLE IF NOT EXISTS {TABLE} (id BIGINT, bits VARCHAR)")
run(f"INSERT INTO {TABLE} VALUES "
"(1, '1011'), "
"(2, '0'), "
"(3, NULL), "
"(4, '10000000'), "
"(5, '0110')")
[]
[]
[(5,)]
What it does
get_bit returns one bit of a bit string. Bits are indexed from 0 at the left, so for an integer cast to BIT, index 0 is the most significant bit and the last index is the least significant one: get_bit(CAST(42 AS BIT), 31) is 0, because 42 is even. An index outside 0 to length(bits) - 1 is an error.
A string literal of 0s and 1s is cast to BIT, so get_bit('101101', 0) works. A VARCHAR column must be cast: CAST(bits AS BIT).
NULLs and edge cases
| Input | Result |
|---|---|
get_bit(NULL, 0), get_bit('101'::BIT, NULL) |
NULL |
get_bit('101101'::BIT, 0), get_bit('101101'::BIT, 5) |
1, 1 |
get_bit('101101'::BIT, '1') (index as a string literal) |
0 |
get_bit('101101'::BIT, 6) |
Error: bit index 6 out of valid range (0..5) |
get_bit('101101'::BIT, -1) |
Error: bit index -1 out of valid range (0..5) |
get_bit('101101'::BIT, 1.6) |
Error: No function matches the given name and argument types 'get_bit(BIT, DECIMAL(2,1))' |
The same cases, as the cluster returns them. They use the table and helpers from Set up the examples:
run("SELECT get_bit(NULL, 0), get_bit('101'::BIT, NULL), get_bit('101101'::BIT, 0), get_bit('101101'::BIT, 5), "
"get_bit('101101'::BIT, '1'), get_bit(CAST(42 AS BIT), 31)")
run("SELECT get_bit('101101'::BIT, 6)")
run("SELECT get_bit('101101'::BIT, -1)")
run("SELECT get_bit('101101'::BIT, 1.6)")
[(None, None, 1, 1, 0, 0)]
OSError: bit index 6 out of valid range (0..5)
OSError: bit index -1 out of valid range (0..5)
InternalError: Binder Error: No function matches the given name and argument types 'get_bit(BIT, DECIMAL(2,1))'. You might need to add explicit type casts.
Examples
The first flag of each row:
run(f"SELECT id, bits, get_bit(CAST(bits AS BIT), 0) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, '1011', 1), (2, '0', 0), (3, None, None), (4, '10000000', 1), (5, '0110', 0)]
Keep the rows whose first flag is set:
run(f"SELECT id, bits FROM {TABLE} WHERE get_bit(CAST(bits AS BIT), 0) = 1 ORDER BY id LIMIT 10")
[(1, '1011'), (4, '10000000')]
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 get_bit() is always computed by the Query Engine:
- In the
SELECTlist, the scan reads the column and the Query Engine computesget_bit()for each row it returns. - In
WHERE, the scan sends the column for every row, and aFILTERstep in the Query Engine computesget_bit()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 get_bit(CAST(bits AS BIT), 0) = 1")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ (get_bit(CAST(bits AS BIT)│
│ , 0) = 1) │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ bits │
│ id │
│ │
└───────────────────────────┘
Make it faster
- A prefix test isn't faster.
bits LIKE '1%'finds the same rows from theVARCHARcolumn, but it runs in aFILTERstep too:
plan(f"SELECT id FROM {TABLE} WHERE bits LIKE '1%'")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ prefix(bits, '1') │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ bits │
│ id │
│ │
└───────────────────────────┘
- Store a flag you filter on often in its own column. A
BOOLEANor integer column compared with a constant is pushed down to VAST DataBase, so rows that don't match never leave storage. - Combine it with a pushed-down condition. In
id > 100 AND get_bit(CAST(bits AS BIT), 0) = 1,id > 100is still evaluated in VAST DataBase, soget_bit()runs only on the rows that pass it.
Limitations and errors
- One short row fails the query. An index past the end of any row's bit string fails the whole query with
bit index ... out of valid range. Row 2 holds a single bit, so index 1 fails:
run(f"SELECT id, get_bit(CAST(bits AS BIT), 1) FROM {TABLE} ORDER BY id LIMIT 10")
OSError: bit index 1 out of valid range (0..0)
Bring the values to one length first, with bitstring, or store bit strings of one length.
- The index must be an
INTEGER. An index computed withlength(), which returns aBIGINT, fails withNo function matches the given name and argument types 'get_bit(BIT, BIGINT)'. Cast it:get_bit(b, CAST(length(b) - 1 AS INTEGER)).
run("SELECT get_bit('1011'::BIT, length('1011'::BIT) - 1)")
run("SELECT get_bit('1011'::BIT, CAST(length('1011'::BIT) - 1 AS INTEGER))")
InternalError: Binder Error: No function matches the given name and argument types 'get_bit(BIT, BIGINT)'. You might need to add explicit type casts.
[(1,)]
- Bit strings only. A number,
BLOB, date, boolean, list orVARCHARcolumn fails withNo function matches the given name and argument types. The error lists the one formget_bitaccepts:get_bit(BIT, INTEGER).
Similar functions
| Function | Result for '101101'::BIT |
|---|---|
get_bit(bits, 1): one bit, indexed from 0 |
0 |
| set_bit: a copy with one bit changed, same indexes | set_bit(bits, 1, 1) is '111101' |
| bit_position: where a pattern starts, counted from 1 | bit_position('1'::BIT, bits) is 1 |
| bit_count: how many 1s | 4 |
run("SELECT get_bit('101101'::BIT, 1), CAST(set_bit('101101'::BIT, 1, 1) AS VARCHAR), "
"bit_position('1'::BIT, '101101'::BIT), bit_count('101101'::BIT)")
[(0, '111101', 1, 4)]
Clean up:
run(f"DROP TABLE IF EXISTS {TABLE}")
run(f"DROP SCHEMA IF EXISTS {SCHEMA}")
conn.close()
[]
[]
See also
- Binary, hash and UUID functions: all the bit string functions.
- BIT values and casts.
- Where filters run and pushdowns.