Applies to VAST Cluster 5.5.
Functions › Binary, hash and UUID functions › Bit strings. A BIT value is a string of 0s and 1s of any length, such as '1011'::BIT. You build one with a cast, from a VARCHAR of 0s and 1s or from an integer, and cast it to VARCHAR to read it. BIT isn't a table column type: store bit strings as VARCHAR and cast them in the query.
Syntax
'bits'::BIT
CAST(value AS BIT)
TRY_CAST(value AS BIT)
CAST(bit_value AS type)
| Cast | From or to | Result |
|---|---|---|
To BIT |
VARCHAR of 0s and 1s |
the same bits |
To BIT |
TINYINT, SMALLINT, INTEGER, BIGINT, HUGEINT |
the two's-complement bits: 8, 16, 32, 64 or 128 of them |
To BIT |
DOUBLE |
the 64 bits of the floating-point value |
To BIT |
BOOLEAN |
8 bits: true is '00000001' |
To BIT |
BLOB |
a BIT value built from the bytes |
From BIT |
VARCHAR |
the 0s and 1s as text |
From BIT |
INTEGER, BIGINT, BOOLEAN, BLOB |
the bits read as that type: '101'::BIT is 5 |
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_bit_values. 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_bit_values"'
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, mask VARCHAR)")
run(f"INSERT INTO {TABLE} VALUES "
"(1, '1011', '1100'), "
"(2, '0', '1'), "
"(3, NULL, NULL), "
"(4, '10000000', '11111111'), "
"(5, '0110', '0011')")
[]
[]
[(5,)]
What it does
A cast to BIT reads its input as a sequence of bits:
- A
VARCHARmust hold only the characters0and1. Spaces, a0bprefix or any other character fail the cast, and so does an empty string.TRY_CASTreturnsNULLinstead. - An integer gives its two's-complement bits, as many as the type has:
CAST(42 AS BIT)is 32 bits (42is anINTEGER),CAST(42::TINYINT AS BIT)is'00101010'. - A
DOUBLEgives the 64 bits of its floating-point form, and aBOOLEANgives 8 bits.
The client receives a BIT value as bytes in an internal encoding ('1011'::BIT arrives as b'\x04\xfb'), so cast it to VARCHAR to read it, as the examples on this page do. Casting back the other way, '101'::BIT is 5 as an INTEGER or BIGINT, and '1'::BIT is true as a BOOLEAN.
Two BIT values are equal when they have the same bits and the same length: '1011'::BIT = '01011'::BIT is false. A string literal compared with a BIT value is cast to BIT.
NULLs and edge cases
| Input | Result |
|---|---|
CAST(NULL AS BIT) |
NULL |
''::BIT |
Error: Cannot cast empty string to BIT |
'1021'::BIT, ' 1011'::BIT, '0b1011'::BIT |
Error: Invalid character encountered in string -> bit conversion: '2' (the first bad character) |
TRY_CAST('1021' AS BIT), TRY_CAST('' AS BIT) |
NULL |
CAST(CAST(-1::SMALLINT AS BIT) AS VARCHAR) |
'1111111111111111' |
CAST(CAST(true AS BIT) AS VARCHAR) |
'00000001' |
'1011'::BIT = '01011'::BIT |
false |
| A bit string of 100,000 bits | works |
The same cases, as the cluster returns them. They use the table and helpers from Set up the examples:
run("SELECT CAST(NULL AS BIT), TRY_CAST('1021' AS BIT), TRY_CAST('' AS BIT)")
run("SELECT ''::BIT")
run("SELECT '1021'::BIT")
run("SELECT ' 1011'::BIT")
run("SELECT CAST(CAST(-1::SMALLINT AS BIT) AS VARCHAR), CAST(CAST(true AS BIT) AS VARCHAR), "
"'1011'::BIT = '01011'::BIT, length(CAST(repeat('10', 50000) AS BIT))")
[(None, None, None)]
OSError: Cannot cast empty string to BIT
OSError: Invalid character encountered in string -> bit conversion: '2'
OSError: Invalid character encountered in string -> bit conversion: ' '
[('1111111111111111', '00000001', False, 100000)]
Examples
Cast each stored string to BIT, and show what the client receives with and without a cast back to VARCHAR:
run(f"SELECT id, CAST(bits AS BIT), CAST(CAST(bits AS BIT) AS VARCHAR) FROM {TABLE} ORDER BY id LIMIT 10")
[(1, b'\x04\xfb', '1011'), (2, b'\x07\xfe', '0'), (3, None, None), (4, b'\x00\x80', '10000000'), (5, b'\x04\xf6', '0110')]
The bits of integers of each size, of a DOUBLE and of a BOOLEAN:
run("SELECT CAST(CAST(42::TINYINT AS BIT) AS VARCHAR), CAST(CAST(42 AS BIT) AS VARCHAR), "
"CAST(CAST(2.5::DOUBLE AS BIT) AS VARCHAR)")
[('00101010', '00000000000000000000000000101010', '0100000000000100000000000000000000000000000000000000000000000000')]
Read bits back as numbers, as a boolean and as bytes:
run("SELECT CAST('101'::BIT AS INTEGER), CAST('00000000000000000000000000101010'::BIT AS INTEGER), "
"CAST('101'::BIT AS BIGINT), CAST('1'::BIT AS BOOLEAN), CAST('101'::BIT AS BLOB)")
[(5, 42, 5, True, b'\x05')]
Compare bit strings, and join two of them with ||. The result of || is a VARCHAR, not a BIT value:
run("SELECT '1011'::BIT = '1011'::BIT, '1011'::BIT = '1011', '1011'::BIT < '1100'::BIT, "
"CAST('1011' AS BIT) || CAST('01' AS BIT), typeof(CAST('1011' AS BIT) || CAST('01' AS BIT))")
[(True, True, True, '101101', 'VARCHAR')]
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 table holds VARCHAR strings, so a cast to BIT is always computed by the Query Engine:
- In the
SELECTlist, the scan reads the column and the Query Engine casts it for each row it returns. - In
WHERE, the scan sends the column for every row, and aFILTERstep in the Query Engine casts it and keeps the rows that match. No rows are skipped in storage.
The plan of a filter on the cast shows the FILTER step above the scan:
plan(f"SELECT id FROM {TABLE} WHERE CAST(bits AS BIT) = '1011'::BIT")
┌───────────────────────────┐
│ PROJECTION │
│ ──────────────────── │
│ #1 │
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ FILTER │
│ ──────────────────── │
│ (CAST(bits AS BIT) = 1011)│
│ │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ │
│ Projections: │
│ bits │
│ id │
│ │
└───────────────────────────┘
Make it faster
- Compare the stored string.
bits = '1011'compares the rawVARCHARcolumn with a constant, so it's pushed down to VAST DataBase and rows that don't match never leave storage. It finds the same rows asCAST(bits AS BIT) = '1011'::BITas long as the strings are stored without leading spaces or other characters. The plan has noFILTERstep:
plan(f"SELECT id FROM {TABLE} WHERE bits = '1011'")
run(f"SELECT id FROM {TABLE} WHERE bits = '1011' ORDER BY id LIMIT 10")
┌───────────────────────────┐
│ VAST_SCAN │
│ ──────────────────── │
│ Function: VAST_SCAN │
│ Projections: id │
│ │
└───────────────────────────┘
[(1,)]
- Cast only the rows you need. Combine the cast with a condition on a raw column, such as
id > 100: that condition is still evaluated in VAST DataBase, so the cast runs only on the rows that pass it.
Limitations and errors
- No
BITcolumns.CREATE TABLEwith aBITorBIT[]column, andCREATE TABLE ... AS SELECTof aBITvalue, fail withBIT type is not supported in table definitions. Store aVARCHARof 0s and 1s, or an integer, and cast it in the query.
run(f"CREATE TABLE {SCHEMA}.bits_tbl (id BIGINT, b BIT)")
InternalError: Binder Error: BIT type is not supported in table definitions. Column "b" has type: BIT
- One bad string fails the query.
CAST(col AS BIT)on a column with a value that isn't 0s and 1s fails withInvalid character encountered in string -> bit conversion. UseTRY_CAST(col AS BIT)to getNULLfor those rows. - The client receives bytes. Without a cast to
VARCHAR, aBITvalue arrives as bytes in an internal encoding. Cast it toVARCHARbefore you return it. - Sorting doesn't follow text order.
ORDER BY CAST(bits AS BIT)and<between bit strings of different lengths follow the internal encoding, not the order of the text:'011'::BIT < '1'::BITis true, and so is'10000000'::BIT < '0'::BIT. To sort in text order, sort by theVARCHARcolumn.
run(f"SELECT id, bits FROM {TABLE} WHERE bits IS NOT NULL ORDER BY CAST(bits AS BIT) LIMIT 10")
run("SELECT '011'::BIT < '1'::BIT, '10000000'::BIT < '0'::BIT")
[(4, '10000000'), (5, '0110'), (1, '1011'), (2, '0')]
[(True, True)]
Similar functions
| Function | What it gives for 5 | Result |
|---|---|---|
CAST('101' AS BIT) |
the bits you write | '101' |
| bitstring | bits padded on the left to a length | bitstring('101', 8) is '00000101' |
CAST(5 AS BIT) |
all 32 bits of an INTEGER |
'00000000000000000000000000000101' |
| set_bit | a copy with one bit changed | set_bit('000'::BIT, 0, 1) is '100' |
| bin | a VARCHAR of the binary digits, without leading zeros |
'101' |
run("SELECT CAST('101'::BIT AS VARCHAR), CAST(bitstring('101', 8) AS VARCHAR), CAST(CAST(5 AS BIT) AS VARCHAR), "
"CAST(set_bit('000'::BIT, 0, 1) AS VARCHAR), bin(5)")
[('101', '00000101', '00000000000000000000000000000101', '100', '101')]
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.
- CAST and :: and TRY_CAST.
- Bitwise operators on BIT values.
- Where filters run and pushdowns.