API Reference¶
questdb¶
- questdb.connect(conf_str: str | None = None, *, host: str | None = None, port: int | str | None = None, tls: bool | None = None, connection_listener: Callable[[ConnectionEvent], None] | None = None, connection_event_inbox_capacity: int = 0, error_handler: Callable[[SenderError], None] | None = None, error_event_inbox_capacity: int = 0, **params) QuestDB[source]¶
Connect to a QuestDB deployment and return a
QuestDBhandle.This is the deployment-level entry point and the default for new code. The handle owns connection pools for both ingestion and queries: borrow row-building senders with
QuestDB.sender(), bulk-load DataFrames withQuestDB.dataframe(), run SQL withQuestDB.query(), and borrow reader leases withQuestDB.reader(). The standaloneSenderis the complementary connection-level API (one sender = one connection) for point-to-point needs: ILP/HTTP transactions, ILP/TCP, QWP/UDP datagrams, and manually driven single ws connections.Pass either a QWP/WebSocket configuration string (
ws::addr=host:port;orwss::...) or the equivalent keywords:host,port(default 9000) andtls(defaultFalse) select the endpoint. Either way, any further keyword arguments are added as configuration-string settings (username='u',sender_pool_max=4, …; booleans normally map toon/off, whiletls_verify=Falsemaps tounsafe_off); a setting present both in the string and as a keyword raisesValueError, matchingSender.from_conf(). One configuration addresses the whole deployment; list every cluster node in a singleaddrserver list.By default
connect()opens the warm minimums up front and fails fast when the server is unreachable or rejects the credentials. Setlazy_connect=True(orlazy_connect=truein the string) to tolerate a down server at startup: nothing connects here, sender leases buffer locally and connect in the background, and readers connect on the first query.Pooled row senders auto-flush by default at 1,000 rows, 100 milliseconds, or a cap-derived byte threshold. Set
auto_flush_bytes='off'to disable only the byte trigger, orauto_flush='off'to disable all automatic publishing. Auto-triggered publishes do not wait for server acknowledgement; use an explicitflush(wait=True)orwait()as an acknowledgement barrier.connection_listenerreceives oneConnectionEventper connection-state transition;error_handlerreceives oneSenderErrorper server rejection (without it rejections are logged through thequestdblogger). Each runs on its own dispatcher thread; seeQuestDB.from_conf()for details. When a handler or listener closes over the returned handle, callQuestDB.close()explicitly (or usewith) rather than leaving the handle to the garbage collector.import questdb with questdb.connect('ws::addr=localhost:9000;') as db: with db.sender() as sender: sender.row('trades', symbols={'sym': 'BTC-USD'}, columns={'price': 62000.0}, at=ts) db.dataframe(df, table_name='trades', at='ts') result = db.query('SELECT * FROM trades LIMIT 10') with questdb.connect(host='localhost', port=9000) as db: ...
- class questdb.QuestDB¶
Bases:
objectHandle to a QuestDB deployment over QWP/WebSocket.
Owns the connection pool; lends row-building senders via
sender(), bulk-loads DataFrames viadataframe(), runs queries viaquery(), and lends reader leases viareader(). Construct withquestdb.connect(). Instances are safe to share across threads.- __enter__()¶
- __exit__(exc_type, exc_val, exc_tb)¶
Close the handle at the end of a
withblock.When the most recent
close()on the handle was given a number of seconds, this close waits only for what is left of that budget; otherwise it closes with the limits of aclose()with no arguments.Leaving the block on an exception still closes, but a close that cannot finish is reported through the
questdblogger instead of raised: the frames unwinding here are often the ones holding the leases the close is waiting for, so raising would replace the exception being reported with a complaint that follows from it. Leaving the block normally raises asclose()does – a lease left open there is a leak worth hearing about.
- close(timeout=<default>)¶
Close the client and its connection pool.
Closing is one-way. Once a
close()proceeds – rather than being refused outright, as from inside one of the handle’s own calls or callbacks – the handle refuses new work:sender(),reader(),dataframe(),query()and the rest raise. Work already in flight drains: a call in progress runs to completion, and an outstanding lease keeps working until its holder closes it. The one exception isPooledSender.dataframe(), which forwards todataframe()over a connection of its own from the pool, so a load started through a lease afterclose()is new work and is refused like any other. The native pool is torn down by the firstclose()that finds nothing using the handle, or when the handle itself is collected.Calls on the handle and on its leases are counted per OS thread. With greenlets – gevent, eventlet, or
greenletused directly – all greenlets on one thread count as one caller, soclose()is also refused while another greenlet on the same thread is suspended inside a call on the handle or on one of its leases. See Threading Considerations.timeoutsets how long this call waits for that drain. Calls are the handle’s own methods in progress, such asdataframe()andquery(); anything done through a lease counts as that lease, including a longlease.dataframe()already running whenclose()starts.Left out: a call in progress is waited for until it returns, and an outstanding lease for up to a minute. A long load through a lease needs a larger
timeout, orNone.A number of seconds: calls and leases alike are waited for up to that long.
0closes only if nothing is in flight.None: calls and leases alike are waited for until they are done.
close()cannot interrupt a call in progress. Adataframe()reading a stream that never ends, or one waiting on the very thread that callsclose(), keeps it waiting untiltimeoutruns out – and, with no limit on calls, for ever. A lease comes back only when its holder closes it, so one held by the calling thread, or by a thread that has since finished, never will: close every lease before closing the handle.When the limit runs out, this call raises
QuestDBErrorwithcodeset toQuestDBErrorCode.InvalidApiCall, naming how many leases and calls are still outstanding. The handle stays closing – it never goes back to open – and a laterclose(), with anytimeout, resumes the wait and finishes the teardown once the last of them is done. Waiting for a concurrentclose()on another thread to finish its teardown is limited too: a number of seconds covers that wait and the drain together; left out, that wait gets a minute of its own;Noneputs no limit on it.timeoutcovers only this waiting. The teardown then spends up toclose_flush_timeout_millis(5 seconds by default) draining each sender’s queue. An in-memory queue drops whatever it has not delivered by then; a disk-backed queue (sf_dir) keeps it for replay on restart. The drop is not reported to Python: it raises nothing and logs nothing through thequestdblogger. To be sure rows have arrived, close each lease withclose(wait=True)before closing the handle.While it waits, it reports what it is still waiting for through the
questdblogger atWARNING: every five seconds for the first minute, then once a minute.A number of seconds is a budget for the whole close, including the close a
withblock makes on its way out: when the most recentclose()on the handle was given one, leaving the block waits only for what is left of it, and not at all once it has run out. To bound leaving awithblock, callclose(timeout=...)as the block’s last statement. Otherwise, leaving the block closes with the limits of aclose()with no arguments.Idempotent: closing a closed handle returns without doing anything, and no
close()returns success unless the teardown has run – when several race, one of them runs it and the others return once it is done.When called from inside one of this handle’s own
error_handler/connection_listenercallbacks, it never waits – while the callback runs, that thread delivers nothing, including whatever a wait would need. It closes immediately when nothing is using the handle; it refuses when leases or calls are outstanding (close from another thread to wait for the drain); and when a concurrentclose()is already running it returns without waiting, possibly before that close finishes – the handle only moves toward closed, never back to open.
- connection_events_delivered¶
Total connection events delivered to the listener.
0when no listener is registered.
- connection_events_dropped¶
Total connection events discarded by the listener inbox’s drop-oldest policy.
0when no listener is registered.
- dataframe(df, *, table_name: str | None = None, table_name_col: None | int | str = None, symbols: str | bool | List[int] | List[str] = 'auto', at: ServerTimestampType | int | str | TimestampNanos | datetime.datetime, max_rows_per_batch: int = 16384, schema_overrides: Dict[str, object] | None = None)¶
Ingest a dataframe through the pooled columnar QWP path.
Ingestion always uses the direct (non-store-and-forward) column sender, independent of
sf_dir. On success, the call returns only after every DataFrame batch has been committed. The first successful batch on a fresh direct connection is already a commit boundary; subsequent batches are pipelined until the final commit. Large Arrow inputs add a checkpoint about every 100 batches to bound the uncommitted tail. The client may checkpoint earlier if deferred capacity fills or a batch must be split to fit.On a transient connection failure, the client re-sends the original, replayable DataFrame only when no batch was successfully published and the failed operation is not
in_doubt. Otherwise it raises rather than replaying from row zero. If a batch from this call may have committed, the raised error hasin_doubt=Trueeven when the final native write alone was provably not delivered. This includes local validation and Arrow stream errors after publication. Internal checkpoints do not reset the call’s delivery status. The load did not finish, but any already committed prefix remains; an application-level retry of the whole DataFrame can duplicate it unless the destination table uses suitableDEDUP UPSERT KEYS. A consumed one-shot stream may instead be non-replayable within_doubt=Falsewhen no rows could have landed; that error asks for a fresh reader. Server-side rejections (e.g. a schema mismatch) surface as a plainQuestDBError; the structuredsender_errordiagnostic is attached only by the store-and-forward senders.dfaccepts any of:pandas
pandas.DataFrame. NumPy-backed columns route through the legacy planner; pyarrow-backed columns route through the Arrow C Stream capsule path below.polars
polars.DataFrameandpolars.LazyFrame(polars 1.32 or later).LazyFrameis materialised via.collect(engine='streaming').pyarrow
pa.Table,pa.RecordBatch, andpa.RecordBatchReader.Any object exposing the Arrow C Data Interface — i.e. with
__arrow_c_stream__(duckdb / cudf / pyarrow-backed pandas 2.2+) or__arrow_c_array__(single Arrow array exporters, wrapped into a one-batchpa.Table).
atnames the designated timestamp column (by name or index). Alternatively pass a fixedTimestampNanos/datetimeshared by every row (encoded as a repeated constant; resubmission stays idempotent underDEDUP UPSERT KEYS), or the explicitServerTimestampsentinel to let the server assign each row’s timestamp on arrival — an opt-in mirroring the row API, since server-assigned timestamps defeatDEDUP UPSERT KEYSon resubmission.The columnar path loads one table per call: name it via
table_name— or, for NumPy-backed pandas input, the dataframe’s index name (df.index.name); Arrow-native input (polars, pyarrow, pyarrow-backed pandas) requires an explicittable_name.table_name_colraisesUnsupportedDataFrameShapeError— split multi-table frames (e.g.df.groupby(col)) and load each group.Supports a column-QWP v1 subset: a single per-call table name, non-null designated timestamp column, and the following per-column dtypes:
Numeric: NumPy
bool/int{8,16,32,64}/uint{8..64}/float{32,64}. Arrowpa.int{8,16,32,64},pa.float{16,32,64}, andpa.uint{8,16,32,64}are accepted by the Rust Arrow batch route when the frame uses a fixed table name and a designated timestamp column name. Unsigned Arrow values follow the Rust Arrow policy:UInt8andUInt16widen toINT,UInt32toLONG, andUInt64values up toi64::MAXare accepted asLONG. LargerUInt64values are rejected because QuestDB QWP-WS encodes integers as signedi64. Signedint8/int16land as QuestDBINT; the row-orientedSender.dataframe()instead widens every integer toLONG. Likewisefloat32lands here asFLOATbut is widened toDOUBLEby the row path — ingest a given table through a single path to avoid a first-write column-type mismatch.String / Symbol: object-dtype
str,pa.string(),pa.large_string(),pd.CategoricalDtypeof strings.Timestamp: NumPy
datetime64units accepted by pandas andpa.timestampwith units,us, orns(tz-aware accepted on Arrow-backed columns in the Rust Arrow route). An Arrowmstimestamp field lands as DATE instead — see DATE below — while a NumPydatetime64[ms]field is widened to a microsecond TIMESTAMP unless adf.attrs['questdb']claim names it DATE. Timestamp field columns accept null timestamps (NaT/None) and values before the Unix epoch; adatetime64[ns]field carryingNaTis re-exported through Arrow so itsINT64_MINnull sentinel is preserved (this needs pyarrow). The designated timestamp column (at=) must be non-null and at or after the epoch.Decimal: Arrow-backed
pa.decimal{32,64,128,256}columns (pa.decimal32/pa.decimal64require pyarrow >= 19), or object-dtype columns ofdecimal.Decimal(re-exported through Arrow; the decimal width and scale are inferred from the values).Array: Arrow
pa.list_/pa.large_list/pa.fixed_size_listcolumns with afloat64leaf (nested for multi-dimensional), and object-dtype columns offloat64numpy.ndarraycells (any rank; requires pyarrow). Both land as QuestDBARRAY(DOUBLE). Null rows are allowed; null elements inside an array are not.UUID: object-dtype columns of
uuid.UUID, thearrow.uuidextension type overpa.fixed_size_binary(16), or any 16-byte binary column claimed withschema_overrides={'col': 'uuid'}. Bytes are canonical RFC 4122 big-endian — exactlyuuid.UUID.bytes— and the client byte-swaps them into QWP wire order. Round-trip throughqueryis byte-identity.The
arrow.uuidroute requires pyarrow >= 18, which is where that canonical extension type is registered, and the column must carry the extension type — build it frompa.uuid(). WritingARROW:extension:nameas plain field metadata is not equivalent: pyarrow leaves such a key on the field, and a pandasArrowDtypecarries a type and no field, so a frame built that way holds a barepa.fixed_size_binary(16)and is refused as unlabelled — see Binary below. Frames imported over the C data interface or Arrow IPC do get the extension type rebuilt from that key, which is why the same metadata can route two ways. The other two routes —uuid.UUIDcells andschema_overrides— are free of both conditions, and a{'kind': 'uuid'}entry indf.attrs['questdb']claims an object-dtype column of 16-bytebytes, the shapeto_pandas(dtype_backend='numpy_nullable')returns. On pyarrow < 18, where no column can carry the label at all, a pandas frame’s barepa.fixed_size_binary(16)column is still refused rather than quietly landing as BINARY.LONG256: 32-byte binary columns. Bytes are little-endian limbs, least-significant limb first, forwarded verbatim. Three routes claim the type, and they need different inputs.
schema_overrides={'col': 'long256'}needs a fully Arrow-backed frame — every column an ArrowDtype, e.g.df.convert_dtypes(dtype_backend='pyarrow').questdb.column_type=long256field metadata needs apa.Tableorpa.RecordBatchhanded straight to this method: field metadata lives on the field, a pandasArrowDtypecarries a type and no field, so a column that passed through pandas has already lost the claim. A{'kind': 'long256'}entry indf.attrs['questdb']claims an object-dtype column of Python ints or of 32-bytebytes— the two shapesQueryResult.to_pandas()hands a LONG256 column back in, plain anddtype_backend='numpy_nullable'respectively. Ints are encoded as 32 unsigned little-endian bytes, so every non-null value must satisfy0 <= value < 2**256;bytescells go out verbatim and must be exactly 32 bytes. LONG256 has no Arrow extension type, so apa.fixed_size_binary(32)column in a pandas frame claims nothing by itself and is refused rather than sent as opaque bytes — see Binary below for why pandas frames refuse where native Arrow input writes BINARY.Binary: object-dtype columns of
bytes,bytearray, or C-contiguous one-byte-itemmemoryviewcells land as BINARY, the same value typesBuffer.rowaccepts. Arrowpa.binary(),pa.large_binary(), andpa.fixed_size_binary(n)columns also land as BINARY, with the exception and the condition below.The exception is
pa.fixed_size_binary(8), which both planners refuse withArrowUnsupportedColumnKind, labelled or not. polars exports itsObjectdtype in exactly that shape, as handles to objects in its own process rather than data, and once exported nothing else marks it. To store genuine 8-byte values as BINARY, make the column variable-length binary:pa.binary(),pl.Binary, or an object column ofbytes.The condition is on widths 16 and 32. A width on its own claims nothing: such a column is a UUID or a LONG256 only when something labels it —
pa.uuid(),questdb.column_typefield metadata ofuuidorlong256,schema_overrides, or adf.attrs['questdb']claim. Any other extension type, or any otherquestdb.column_typevalue, leaves the column unlabelled — exceptbinary, which, likeschema_overrides={'col': 'binary'}, labels it as plain bytes. What happens to an unlabelled one depends on where it comes from, not on which planner writes it. Native Arrow input — apa.Table,pa.RecordBatch,pa.RecordBatchReader, or any other__arrow_c_stream__producer — keeps its field metadata, so an unlabelled column there is written as BINARY, and once the write is done thequestdblogger records one WARNING per such column naming the remedies. A pandas frame refuses it withBadDataFrame, fully Arrow-backed or not: anArrowDtypecarries a type and no field, so the column may be a UUID or LONG256 column whose label was dropped on the way into pandas, and guessing would auto-create a table with the wrong column type. The refusal does not depend on the pyarrow version — which remedies the message can name does, sincepa.uuid()needs pyarrow 18, but which columns are accepted does not. To write either width as BINARY without the warning or the refusal, label it as plain bytes —schema_overrides={'col': 'binary'}, or{"questdb.column_type": "binary"}field metadata on native Arrow input — which sends it as it is, with no copy. Making the column variable-length binary (pa.binary(),pd.ArrowDtype(pa.binary())) or, in pandas, an object-dtype column ofbytesworks too, at the cost of a copy. Requires QuestDB 10 or newer.DATE: Arrow
pa.timestamp('ms')(tz-aware or naive),pa.date32(), andpa.date64()columns land as DATE. The Arrow type is itself the claim, so there is no DATE cell type and no'date'kind forschema_overrides. Such a column lands as DATE whether the frame is fully Arrow-backed or mixed with NumPy columns. What has no route of its own to DATE is the NumPydatetime64[ms]dtype, which widens to a microsecond TIMESTAMP, and its tz-awaredatetime64[ms, tz]form, which is rejected outright. A millisecond column named byat=becomes the table’s designated TIMESTAMP, like any otheratcolumn. Round-tripping keeps the type:queryreturns a DATE column aspa.timestamp('ms', 'UTC'), whichto_arrow()andto_pandas(dtype_backend='pyarrow')feed straight back as DATE. Plainto_pandas()gives a NumPydatetime64[ms]column, and thedf.attrs['questdb']claim it comes with names the column DATE, so this method puts the Arrow type back on before writing and that frame lands as DATE too. Row-at-a-time, DATE is written withDateMillisthroughBuffer.row.
Server-side coercion handles cross-type writes (e.g.
pa.string()UUIDs landing in a UUID column are parsed server-side; narrow ints landing in a wider column are widened). Failures surface asQuestDBErrorfrom theflush().schema_overridesreclassifies columns by name, mapping each to'symbol','ipv4','char','uuid','long256','binary', or('geohash', bits)(e.g.{'venue': 'symbol', 'src_ip': 'ipv4'}). Unknown column names are rejected. An override wins over any Arrow field metadata on its column, except'binary', which is refused on a column whose own type or field metadata labels it UUID or LONG256.'uuid'and'long256'apply to fixed-size and variable-length binary columns alike — the route polars frames take, since polars has no fixed-size binary dtype — and every non-null value must be exactly 16 or 32 bytes respectively.'binary'applies to binary columns and says a 16- or 32-byte column holds plain bytes (see Binary above); it cannot override apa.uuid()type or auuid/long256field label, and is refused on such a column. It requires the Arrow columnar path (fully Arrow-backed input withouttable_name_col; pandas input needs pandas 2.2 or later); on input that falls back to the NumPy planner it raisesUnsupportedDataFrameShapeError.A pandas frame that came out of
QueryResult.to_pandas()carries its source column types indf.attrs['questdb'], and this method reads them back. That is what keeps UUID, LONG256, IPV4, CHAR, GEOHASH, and DATE columns their own type on a read-modify-write round trip: their claim lives in Arrow field metadata, which a pandas dtype — an Arrow type and no field — cannot hold. The metadata recalls what the frame held when it was read, so a column since dropped, renamed, or retyped simply loses its claim, andsymbols/schema_overridesoutrank it. BYTE, SHORT, and INT columns still widen one step on the way back in; the Arrow ingest API has no override that pins them.The claim is honoured on every shape a
to_pandasbackend produces, so plainto_pandas(),dtype_backend='pyarrow'anddtype_backend='numpy_nullable'round-trip the same way. Besides the Arrow-backed and plain NumPy columns, that covers the object columns the other backends leave behind: Python ints, which is what a pandas masked column becomes and what plainto_pandas()returns a LONG256 as, andbytes, which is howdtype_backend='numpy_nullable'returns UUID and LONG256. Frames saved from version 5.0, whose plainto_pandas()returned a GEOHASH column as an unsigned integer, round-trip too: such a column is written as the signed integer of the same width, bit for bit. A frame whose columns were retyped past that set — which a customtypes_mappercan do — keeps the claim inattrsand cannot use it: a claimed column that arrives as a float or a string dtype lands as that dtype implies, and the claim it could not use is reported as a warning-level record on thequestdblogger. An object column states no width or range of its own, so a claimed value the type cannot hold — an integer past2**32-1underipv4, a cell that is not exactly 16 or 32 bytes underuuidorlong256— is refused rather than written into a column of the claimed type. GEOHASH is the deliberate exception, described next.Bulk GEOHASH values are raw bit patterns, whatever the column is stored as: NumPy, Arrow, or an object column of Python ints, which is also what a pandas masked column becomes. This is a deliberate contract, the same for every shape, so that whether a frame is written does not depend on whether a GEOHASH column holds a null; plain
QueryResult.to_pandas()returns a column that does as a masked column. The declared precision must be in1..=60and fit the signed integer carrier, but no per-value check verifies that the pattern has no bits set above that precision. For example,32underGEOHASH(5b)is accepted even though it needs six bits. If the declared precision is wrong for the data, the write can succeed and store a different GEOHASH location orNULL. Only the lowceil(precision / 8)bytes are encoded, and the exact result of an inconsistent value is unspecified. This is a semantic data-integrity risk, not a memory-safety risk for otherwise valid NumPy/Arrow buffers. The caller must validate bulk values. The scalarGeohashconstructor remains strict; malformed custom Arrow C Data structures remain invalid input and are outside this guarantee.A column of nothing but nulls names no type of its own, and without a claim it is left out of the write and out of the table the write auto-creates. A claim names one, so a claimed all-null column is written as the claimed type — which is what keeps a batch that happens to hold no value for one column from deciding the shape of the table.
The claim a query result carries reads as an ordinary mapping but cannot be edited in place, because every copy of the frame shares it; assign a new mapping to
df.attrs['questdb']to change it. A plain hand-writtendictis read here just the same, in this shape:df.attrs['questdb'] = { 'version': 1, 'columns': { 'src_ip': {'kind': 'ipv4'}, 'pos': {'kind': 'geohash', 'precision_bits': 20}, 'traded_on': {'kind': 'date'}, }, }
versionmust be1when present, and a mapping without it reads as version 1. The claim’s vocabulary can gain kinds, and a client applying a version it does not understand would write the wrong column type, which is the outcome the claim exists to prevent, so a mapping carrying any other version is not read and every column in it is ignored. A claim, or a column’s entry in it, that is not read is reported through thequestdblogger; the write goes ahead either way.kindis one of'uuid','long256','ipv4','char','geohash', or'date'— the types whose claim cannot always ride on a pandas dtype.precision_bitsaccompanies'geohash'alone. A'date'claim restores the DATE type after plain pandas has represented it as NumPydatetime64[ms]. Naming a column that is not in the frame is not an error; the entry is skipped.A
pa.Tableorpa.RecordBatchfromQueryResult.to_arrow()orQueryResult.iter_arrow()carries the same claims in Arrow field metadata, as aquestdb.column_typekey on every field, and this method passes that metadata to the native client unchanged. A field claim the field’s Arrow type cannot carry —'short'onpa.int32(),'ipv4'onpa.int64()— makes this method raiseQuestDBError, withcodeset toQuestDBErrorCode.ArrowIngest, before anything is sent. This stricter rule is deliberate. Field metadata is the input contract of the native Arrow ingest, which refuses such a field in every language that uses it. A field claim also goes stale only when the old field is carried onto a new type:Table.castwith a schema built fromfield.with_type(...), orpa.concat_tables(..., promote_options='permissive')with the claimed table first. Casting to a schema built from scratch,set_columnby name, and a polars or pandas round trip all build fields without the key.df.attrsbelongs to the whole frame and survivesastypeand arithmetic, so a stale claim there is the normal case and is logged instead. To write a retyped column, drop its metadata:i = table.schema.get_field_index('col') table = table.cast(table.schema.set( i, table.schema.field(i).remove_metadata()))
max_rows_per_batchsets the pipelining granularity, not a safety limit: any batch exceeding the negotiated per-batch byte cap is split regardless of it, and a single row is never bounded by it. Each batch is one unit of client memory and server-side apply. The first successful batch on a fresh connection is a commit boundary. Sliceable Arrow inputs add another checkpoint about every 100 batches, somax_rows_per_batch * 100rows approximates the maximum periodic uncommitted tail, not a whole-source replay window. The NumPy planner pipelines the remaining batches until its final commit. Either path may checkpoint earlier when deferred capacity fills. Raisemax_rows_per_batchfor narrow numeric rows; lower it for very wide rows or tight memory. Streaming Arrow input (pa.RecordBatchReader) is not re-batched — the producer’s batch size governs its checkpoint window.
- error_events_delivered¶
Total server rejections delivered to the
error_handler(or to the default logging handler when none was registered).
- error_events_dropped¶
Total server rejections discarded by the handler inbox’s drop-oldest policy.
- execute(sql, binds=None, *, timeout=None)¶
Run a statement and discard whatever it returns.
Executes
sqllikequery(), drains the result to its clean end and returns the pooled connection — the pattern DDL and DML otherwise need spelled out aswith db.query(sql) as r: r.to_pandas(). Statement output (aCOPYstatus row, admin-function rows, a straySELECT) is discarded; usequery()when you want the result. The connection’s SYMBOL dictionary is left untouched.bindsandtimeoutbehave exactly as onquery().Returns the statement’s
EXEC_DONEreport as anExecDonenamed tuple(op_type, rows_affected), orNonefor a statement that streamed a result set instead (aSELECT). SeeQueryResult.exec_donefor the caveats onrows_affected.
- static from_conf(conf_str, *, connection_listener=None, connection_event_inbox_capacity=0, error_handler=None, error_event_inbox_capacity=0)¶
Construct a handle from a QWP/WebSocket configuration string.
Prefer the
questdb.connect()module-level factory.By default the handle connects eagerly: construction pre-opens the warm minimums (
sender_pool_minsender andquery_pool_minreader connections, one of each by default), so an unreachable server or bad credentials fail here, fast. Withlazy_connect=trueconstruction opens no connection: sender leases buffer locally and connect in the background, readers connect on first use (query_pool_mindefaults to 0), and errors surface from the first operation instead. Combininglazy_connect=truewith a blockinginitial_connect_retryor a positivequery_pool_minis a configuration conflict.Pooled row auto-flush is enabled by default at 1,000 rows, 100 milliseconds, or an estimated encoded size at 90% of the effective frame cap. Before a server cap is known, or when the server omits it, the byte threshold is 90% of the lower of QuestDB’s default cap, 2 MiB less 14 bytes, and the local store-and-forward frame cap. Set
auto_flush_bytes=offto disable only the byte trigger, orauto_flush=offto disable all automatic publishing. The mode is immutable and shared by every lease; the interval starts when the first row enters an empty lease buffer. Auto-triggered publishes do not wait for server acknowledgement.The underlying connection pool is opened by questdb_db_connect_with_handlers. Dataframe ingestion always uses the direct (non-store-and-forward) QWP/WebSocket column sender, independent of
sf_dir. On a transient connection failure the original DataFrame is re-sent only when no batch was successfully published and the failed operation is provably not delivered. Once a prefix may have committed, the call raises instead of replaying from row zero. A delivery-unknown failure surfaces asQuestDBErrorwithin_doubtset.request_timeoutbounds each commit’s no-progress ack wait;request_timeout=0disables that deadline, so a stalled but connected server can blockdataframe()indefinitely.connection_listener, when set, is a callable receiving oneConnectionEventper connection-state transition of the pool’s ingress connections (initial connect, per-endpoint attempt failures, failover, terminal auth rejection). It runs on a dedicated dispatcher thread fed by a bounded inbox (connection_event_inbox_capacity;0selects the default of 64) with a drop-oldest overflow policy, so a slow listener cannot stall ingest or reconnects. Exceptions it raises are logged and swallowed. Dropped/delivered totals are available viaconnection_events_dropped/connection_events_delivered. Successful events are queued only after negotiated state, including the server frame cap, is committed; they do not acknowledge data.error_handler, when set, is a callable receiving oneSenderErrorper server rejection recorded by any of the pool’s store-and-forward connections — including rejections for rows published through aPooledSenderthat was already closed. It runs on its own dedicated dispatcher thread fed by a bounded inbox (error_event_inbox_capacity;0selects the default of 64, overflow drops the oldest event). Exceptions it raises are logged and swallowed. Without a handler every rejection is logged through thequestdblogger instead —ERRORfor terminal rejections,WARNINGfor retriable ones (the affected rows are replayed, not lost) — so rejections are never silent. A terminal rejection is queued only after the connection’s terminal latch and pollable diagnostic have been committed. Delivered/dropped totals are available viaerror_events_delivered/error_events_dropped. Use the handler for dead-lettering, alerting, and metrics; terminal failures also surface asQuestDBErrorfrom the sender calls themselves.When a handler or listener closes over the returned handle, call
close()explicitly (or usewith): until closed, such a handle is pinned alive rather than collected, so an abandoned one leaks instead of crashing. The pin is dropped at interpreter shutdown, so an unclosed self-referential handle can still crash during finalization.
- query(sql, binds=None, *, reset_symbol_dict=True, timeout=None)¶
Execute a SQL query and return a
QueryResult.Egress goes through the QuestDB Wire Protocol (QWP/WebSocket)
/read/v1endpoint. The reader is borrowed from the same connection pool that hosts the ingress writers and is returned to the pool when the returnedQueryResultis consumed or closed (a poisoned connection is dropped instead). Auth / TLS settings apply to both directions.To run several queries on one pooled connection, lease a
PooledReaderwithreader()instead.- Parameters:
sql – SQL text to execute. Forwarded verbatim to QuestDB.
binds –
Positional bind parameters matching the
$1..``$N`` placeholders insql, as a list or tuple. Always prefer binds over interpolating values into the SQL text — they take no escaping and keep types exact. Supported Python types and their QuestDB bind types:None→ SQL NULL (bound as a VARCHAR null)bool→ BOOLEANint→ LONG (must fit signed 64-bit)float→ DOUBLEstr→ VARCHARdatetime.datetime→ TIMESTAMP (microseconds; a naive value is interpreted as UTC, the same rule as everywhere else in the API)TimestampMicros→ TIMESTAMPTimestampNanos→ TIMESTAMP_NSdatetime.date→ DATEdecimal.Decimal→ the narrowest DECIMAL that holds it, with its fractional digits as the scale (up to 76 digits and scale 76; NaN and infinities raiseValueError)uuid.UUID→ UUID
Any other type raises
TypeErrornaming the placeholder.res = db.query( 'SELECT * FROM trades WHERE ts > $1 AND sym = $2', [datetime.datetime(2026, 7, 1), 'BTC-USD'])
reset_symbol_dict – When
True(the default), the server resets the connection’s SYMBOL dictionary before this query so it never inherits symbols from earlier queries on the pooled connection (query-scoped dict), avoiding cross-query dictionary bloat into_polars()/to_pandas(). SetFalseto keep the dictionary warm across repeated identical queries. No-op against servers that predate the capability.timeout – Run this query under its own budget instead of the server-wide
query.timeout, overriding the connect string’squery_timeout_ms. Anintis milliseconds; adatetime.timedeltaworks too, with a positive value below one millisecond rounded up to 1 ms.None(the default) keeps the connect string’squery_timeout_ms;0clears it. Requires a server advertisingCAP_QUERY_TIMEOUT: against an older one a non-zero timeout is refused before anything is sent, withQuestDBErrorCode.UnsupportedServer, rather than silently running under the server default; the connection is untouched, so passtimeout=0or raise the server’squery.timeoutinstead. On expiry the error carriesQuestDBErrorCode.QueryTimeoutand the connection stays usable — but do not retry a write on it: a statement that outlives its timeout is reported as done instead, and a DDL / INSERT / UPDATE that timed out waiting for the table writer may still be applied.
- Returns:
A
QueryResult. Materialise it viaiter_rows(),to_pandas(),to_arrow(),iter_arrow(),iter_pandas(), or the__arrow_c_stream__PyCapsule protocol.
Sentinel-value collisions in the result frame round-trip QuestDB’s contract:
INT64_MINin a LONG column, NaN in DOUBLE / FLOAT, and the sentinel values for INT / DATE / TIMESTAMP / TIMESTAMP_NS / CHAR / UUID / LONG256 / IPV4 are all interpreted as NULL by QuestDB and cannot be distinguished from legitimate occurrences of those values. GEOHASH has no such sentinel, so a NULL GEOHASH stays distinct from every value.
- reader()¶
Borrow a context-managed
PooledReaderlease from the pool.The read-side twin of
sender(): the lease holds one pooled reader connection for its lifetime and runs queries on it sequentially viaPooledReader.query().with db.reader() as r: r1 = r.query('SELECT * FROM t1').to_pandas() r2 = r.query( 'SELECT * FROM t2', reset_symbol_dict=False).to_pandas()
The lease counts as an active use of the handle until it is closed:
QuestDB.close()waits for outstanding leases for as long as itstimeoutallows (a minute by default – seeclose), and a lease keeps working while the handle drains.
- reap_idle()¶
Manually reap idle above-pool-size connections.
- sender()¶
Borrow a context-managed row-building sender from the pool.
The lease counts as an active use of the handle until it is closed:
QuestDB.close()waits for outstanding leases for as long as itstimeoutallows (a minute by default – seeclose), and a lease keeps working while the handle drains – apart fromPooledSender.dataframe(), which is new work on the handle and is refused onceclose()has started.
- server_info() ServerInfo¶
Return a
ServerInfosnapshot of the server’sSERVER_INFOhandshake: cluster role, failover epoch, negotiated capabilities, handshake wall-clock, and cluster/node identifiers.A reader is borrowed from the connection pool (opening one on first use, exactly like
query()), sampled, and returned to the pool. The snapshot describes that connection at its last handshake; a later failover is reflected only in snapshots taken after it.
- class questdb.PooledSender¶
Bases:
objectA row-building sender borrowed from a
QuestDBpool.Obtain a lease with
QuestDB.sender();close()returns the native sender to the pool. Rows publish into an ordered, store-and-forward-covered QWP stream over one pooled connection.Rows go through
row(),dataframe(),flush(),wait()andclose();len(sender)is the number of buffered rows. Frame-level delivery tracking is available throughflush_and_get_fsn(),flush_and_keep_and_get_fsn(),published_fsn(),acked_fsn()andawait_acked_fsn(), and per-lease server diagnostics throughpoll_error()anderror_events_dropped(). PreferQuestDB.dataframe()for bulk loads; the lease’s owndataframe()is a convenience that routes to the same direct columnar path. Row auto-flush is enabled by default at 1,000 rows, 100 milliseconds, or a cap-derived byte threshold, and can be configured through the parent handle’s connection settings.A lease is used by one thread at a time. It may move to another thread – borrowed on one and used on a worker, say – provided the hand-off is synchronized and no two threads call into it at once. To ingest from several threads, share the
QuestDBhandle and borrow one lease per thread. Every method holds the lease’s lock for its whole call,dataframe()included, so a call from a second thread waits until the running one returns. Code that runs inside a call – a column value’s conversion, the Arrow streamdataframe()reads, or a handler on thequestdblogger, whichdataframe()logs its warnings to – therefore must not wait on another thread that uses the same lease.Close every lease, or use it in a
withblock. A lease collected withoutclose()returns its sender to the pool without sending the rows still buffered in it, and reports how many it discarded through thequestdblogger atWARNING.- __enter__()¶
- __exit__(exc_type, exc_val, exc_tb)¶
- acked_fsn()¶
Highest FSN completed on the lease’s pooled connection by ACK or drop-and-continue rejection, or
Noneif no frame has completed yet. A completed frame is not necessarily applied: a rejected frame the queue drops to make progress also advances this watermark.
- await_acked_fsn(fsn, timeout_millis=0)¶
Wait until the acknowledgement watermark reaches
fsn(as returned byflush_and_get_fsn()on this lease).Returns
Trueoncefsnis acknowledged, orFalseif the no-progress timeout elapsed beforefsnwas acknowledged (0waits indefinitely). Only a terminal connection failure raises.
- close(flush: bool = True, wait: bool = False)¶
Return this sender to its pool. Idempotent.
Pending rows are published by default; delivery is owned by the store-and-forward queue, which keeps delivering after the sender is returned.
wait=Trueadditionally waits for an OK ack before returning the sender to the pool; without it, a later server rejection of this lease’s rows is reported through the pool’serror_handler(default: thequestdblogger).
- dataframe(df, *, table_name: str | None = None, table_name_col: None | int | str = None, symbols: str | bool | List[int] | List[str] = 'auto', at: ServerTimestampType | int | str | TimestampNanos | datetime.datetime, max_rows_per_batch: int = 16384, schema_overrides: Dict[str, object] | None = None)¶
Bulk-load a whole DataFrame over a direct columnar connection borrowed from the pool for the duration of this call.
Prefer
QuestDB.dataframe()— this convenience forwards to the same path and is not part of this sender’s row stream: the frame is committed over its own connection and becomes visible to SQL immediately, without waiting for rows appended to this sender withrow()to drain. There is therefore no ordering relationship betweendataframe()and buffered rows —dataframe()does not flush them; publish those withflush().Being the handle’s own path, it is refused once
QuestDB.close()has started, asQuestDB.dataframe()is, even though this lease’s other methods keep working until it is closed. A load already running whenclose()starts finishes.Arguments mirror
QuestDB.dataframe().
- error_events_dropped()¶
Diagnostics dropped from the lease’s connection ring (
error_inbox_capacity).
- flush(*, wait=False)¶
Publish and clear buffered rows.
By default this returns after local store-and-forward acceptance. Pass
wait=Trueto wait for the server’s OK acknowledgement of everything published through this lease. The wait is a pure ack barrier: only a terminal connection failure raises. Server rejections are delivered to the pool’serror_handler(default: thequestdblogger) instead; retriable ones are replayed by the store-and-forward queue.
- flush_and_get_fsn()¶
Publish and clear buffered rows, returning the frame sequence number (FSN) of the published frame, or
Noneif the buffer was empty.FSNs are watermarks of the lease’s pooled connection: use them for progress tracking while this lease is held (see
await_acked_fsn()); they are not portable receipts across leases, which may borrow different connections. Configureauto_flush=offwhen this call must publish and identify one whole application batch; auto-flush may already have published and cleared some or all buffered rows.
- flush_and_keep_and_get_fsn()¶
Publish buffered rows without clearing the buffer, returning the published frame’s FSN, or
Noneif the buffer was empty.
- poll_error()¶
Poll the next server-rejection diagnostic recorded on the lease’s connection since this lease was borrowed, as a
SenderError, orNonewhen none is pending.The pool’s
error_handlerindependently receives every rejection the moment it is recorded; polling here is a per-lease pull alternative for code that wants diagnostics inline.
- published_fsn()¶
Highest FSN published locally on the lease’s pooled connection, or
Noneif nothing has been published on it yet.
- row(table_name: str, *, symbols: Dict[str, str | None] | None = None, columns: Dict[str, None | bool | int | float | str | TimestampMicros | TimestampNanos | datetime.datetime | numpy.ndarray | Decimal | uuid.UUID | ipaddress.IPv4Address | bytes | bytearray | memoryview | Char | DateMillis | Long256 | Geohash] | None = None, at: ServerTimestampType | TimestampNanos | datetime.datetime)¶
Append one row to this sender’s QWP buffer.
When pooled auto-flush is enabled on the
QuestDBhandle, completing a row that breaches a configured row, byte, or interval threshold publishes the buffer without waiting for an acknowledgement. A buffer whose size estimate reaches the connection’s frame cap is published too, even withauto_flush_bytes=off. Every published frame fits the frame cap: when the whole buffer does not, or its size estimate is past the cap, the rows buffered before this one go out as a frame of their own and this row starts the next batch, so one call publishes at most two frames. Re-adding the row converts its values a second time.Whatever this method raises, the row was not added.
QuestDBErrorCode.BatchTooLargemeans either it does not fit in a frame on its own, or the rows buffered before it do not fit even without it, which can happen after the frame cap shrinks, for example on reconnect; those rows are then discarded, and the error says how many. Any other error raised by a publish propagates from this method and leaves the rows buffered before it in place.See
Buffer.rowfor supported column types, protocol restrictions, server requirements, andNULL-sentinel behavior.
- wait(timeout_millis=0)¶
Wait for everything published through this lease to receive an OK ack; returns immediately if this lease published nothing.
The wait is a pure ack barrier: only a terminal connection failure raises. Server rejections are delivered to the pool’s
error_handler(default: thequestdblogger) instead.timeout_millisis a no-progress timeout;0waits indefinitely.
- class questdb.PooledReader¶
Bases:
objectA reader lease borrowed from a
QuestDBpool.The read-side twin of
QuestDB.sender(): obtain a lease withQuestDB.reader(); it holds one pooled reader connection for its lifetime and runs queries on it sequentially viaquery().close()(or leaving thewithblock) releases the connection: back to the pool if the last query reached its terminal on a live connection (a clean drain, a successfulcancel(), or a server-side error such as a parse error or a query timeout), dropped otherwise.Queries are strictly sequential — one result at a time. Fully drain (or
close()) eachQueryResultbefore callingquery()again; running the next query while the previous result is still open raisesQuestDBError. Closing an undrained result terminates the lease; callresult.cancel()beforeresult.close()to preserve it.Because every query shares one connection, passing
reset_symbol_dict=Falseto follow-up queries keeps the connection’s SYMBOL dictionary warm across them instead of resetting it per query.The lease counts as an active use of the
QuestDBhandle (QuestDB.close()waits for it) and has thread affinity: use one lease per thread, on the thread that created it. AQueryResultreturned by the lease may be handed to a worker under that result’s thread hand-off rules; wait for it to finish before using the lease again.- __enter__()¶
- __exit__(exc_type, exc_val, exc_tb)¶
- close()¶
Release the lease’s reader connection. Idempotent.
A still-open (undrained) last result is freed first, which tears down the connection; a connection whose last query reached its terminal (a clean drain, a successful
cancel(), or a server-side error) is returned to the pool, any other is dropped and the pool refills on demand.
- execute(sql, binds=None, *, timeout=None)¶
Run a statement on the lease’s connection and discard whatever it returns.
Mirrors
QuestDB.execute(), including theExecDone(op_type, rows_affected)return value. The result is drained to its clean end, so the lease stays usable for the next call, and the connection’s SYMBOL dictionary is left untouched — an interleaved statement does not invalidate a warm dictionary built withreset_symbol_dict=False.
- query(sql, binds=None, *, reset_symbol_dict=True, timeout=None) QueryResult¶
Execute a SQL query on the lease’s connection and return a
QueryResult.sql,binds,reset_symbol_dictandtimeoutbehave exactly as onQuestDB.query(), except the query runs on the reader this lease holds instead of a per-call pool borrow. The previous query’s result must be fully drained (or closed) first;reset_symbol_dict=Falsereuses the connection’s SYMBOL dictionary built up by the lease’s earlier queries.
- class questdb.Sender¶
Bases:
objectIngest data into QuestDB over a single connection.
This is the connection-level API: one sender drives exactly one connection (ILP/HTTP, ILP/TCP, QWP/UDP, or a single QWP/WebSocket connection) and carries the point-to-point capabilities the deployment-level handle does not: HTTP transactions, UDP datagrams, and manual ws progress and buffer control. For pooled ingestion and queries, prefer
questdb.connect().See the Sending Data documentation for more information.
- __enter__() Sender¶
Call
Sender.establish()at the start of awithblock.
- __exit__(exc_type, exc_val, exc_tb)¶
Flush pending and disconnect at the end of a
withblock.If the
withblock raises an exception, any pending data will NOT be flushed.This is implemented by calling
Sender.close().
- __init__(*args, **kwargs)¶
- acked_fsn()¶
Highest QWP/WebSocket frame sequence number completed by ACK or drop-and-continue rejection.
- auto_flush¶
Auto-flushing is enabled.
Consult the .auto_flush_rows, .auto_flush_bytes and .auto_flush_interval properties for the current active thresholds.
Over
wsandwssthe byte threshold is on by default, so auto-flush stays on withauto_flush_rowsandauto_flush_intervalboth off. Passauto_flush=offto batch by hand.
- auto_flush_bytes¶
Byte-count threshold for the auto-flush logic, or None if disabled or not yet known.
Over
wsandwssthe threshold is at most 90% of the connection’s frame cap, and by default it is 90% of that cap. The cap is known onceSender.establish()has connected, so before that the default reportsNone. A server that does not advertise a cap counts as having QuestDB’s default, 2 MiB less 14 bytes, unless the local limit is lower. An explicit threshold reads as configured beforeSender.establish()and afterSender.close(); the cap lowers it only between the two.Raises
QuestDBError(InvalidApiCall) when read overwsorwssfrom this sender’s ownerror_handlerorconnection_listener.
- auto_flush_interval¶
Time interval threshold for the auto-flush logic, or None if disabled.
- auto_flush_rows¶
Row count threshold for the auto-flush logic, or None if disabled.
- await_acked_fsn(fsn, timeout_millis=0)¶
Wait until the QWP/WebSocket completion watermark reaches
fsn.Returns
Trueonce every frame published so far (which includesfsn) has been acknowledged, orFalseif the no-progress timeout elapsed before the acknowledgement watermark reachedfsn.
- close(flush=True)¶
Disconnect.
This method is idempotent and can be called repeatedly.
Once a sender is closed, it can’t be re-used.
- Parameters:
flush (bool) – If
True, flush the internal buffer before closing. For QWP/WebSocket, this also drains already-published frames before closing.
Refused while a row is part-way through the sender’s internal buffer, which is the buffer this call flushes. A buffer of your own from
Sender.new_buffer()is neither flushed nor checked here: the sender is never told which buffers exist, so it cannot refuse on their behalf. Such a buffer keeps every byte written into it, but a sender closed out from under it can no longer send it — finish the row and flush it before closing.
- close_drain()¶
Stop accepting new QWP/WebSocket publications and wait for already published frames to resolve.
- connection_events_delivered¶
Total connection events delivered to the listener.
0when no listener is registered.
- connection_events_dropped¶
Total connection events discarded by the listener inbox’s drop-oldest policy.
0when no listener is registered.
- dataframe(df, *, table_name: str | None = None, table_name_col: None | int | str = None, symbols: str | bool | List[int] | List[str] = 'auto', at: ServerTimestampType | int | str | TimestampNanos | datetime.datetime, max_rows_per_batch: int = 16384, schema_overrides: Dict[str, object] | None = None)¶
Write a Pandas DataFrame to QuestDB.
Over ILP/HTTP and ILP/TCP the frame is serialized into the internal row buffer; over QWP/UDP into fire-and-forget datagrams, with the same delivery caveats as
row(). Over QWP/WebSocket the frame is bulk-loaded through a poolless direct columnar connection opened from this sender’s configuration for the call (the same direct path asQuestDB.dataframe(), carrying the sender’s auth/TLS regardless of how it was constructed);max_rows_per_batchapplies only on that path, and passingschema_overrideson any other protocol raises. The direct load has no ordering relationship with rows buffered viarow()and does not flush them.table_name_colfollows the same split. The row-serializing protocols accept it, because they can change table between rows. QWP/WebSocket writes one table per call and rejects it withUnsupportedDataFrameShapeError; split the frame and make one call per table, or send those rows withrow().Example:
import pandas as pd import questdb as qi df = pd.DataFrame({ 'car': pd.Categorical(['Nic 42', 'Eddi', 'Nic 42', 'Eddi']), 'position': [1, 2, 1, 2], 'speed': [89.3, 98.2, 3, 4], 'lat_gforce': [0.1, -0.2, -0.6, 0.4], 'accelleration': [0.1, -0.2, 0.6, 4.4], 'tyre_pressure': [2.6, 2.5, 2.6, 2.5], 'ts': [ pd.Timestamp('2022-08-09 13:56:00'), pd.Timestamp('2022-08-09 13:56:01'), pd.Timestamp('2022-08-09 13:56:02'), pd.Timestamp('2022-08-09 13:56:03')]}) with qi.Sender.from_env() as sender: sender.dataframe(df, table_name='race_metrics', at='ts')
Over QWP/WebSocket, supported column types and arguments mirror
QuestDB.dataframe, including UUID, IPV4, BINARY, GEOHASH, LONG256, CHAR, DATE,schema_overridesand round-trip claims indf.attrs['questdb']. Over ILP and QWP/UDP, seeBuffer.dataframefor the row-serializing mappings;max_rows_per_batchandschema_overridesdo not apply on those protocols.Additionally, this method also supports auto-flushing the buffer as specified in the
Sender’sauto_flushconstructor argument. Auto-flushing is implemented incrementally, meanting that when callingsender.dataframe(df)with a largedf, the sender may have sent some of the rows to the server already whist the rest of the rows are going to be sent at the next auto-flush or next explicit call toSender.flush().In case of data errors with auto-flushing enabled, some of the rows may have been transmitted to the server already.
- drive_once()¶
Drive one QWP/WebSocket progress step for manual progress senders.
- error_events_dropped()¶
Number of QWP/WebSocket diagnostics dropped from the bounded ring.
- establish()¶
Prepare the sender for use.
If using ILP/HTTP this will initialize the HTTP connection pool.
If using ILP/TCP this will cause connection to the server and block until the connection is established.
If the TCP connection is set up with authentication and/or TLS, this method will return only after the handshake(s) is/are complete.
- flush(buffer=None, clear=True, transactional=False)¶
If called with no arguments, immediately flushes the internal buffer.
Alternatively you can flush a buffer that was constructed explicitly by passing
buffer.The buffer will be cleared by default, unless
clearis set toFalse.This method does nothing if the provided or internal buffer is empty.
- Parameters:
buffer – The buffer to flush. If
None, the internal buffer is flushed.
With QWP/WebSocket, this publishes the buffer into the local sender queue and returns before the server necessarily ACKs the frame. Later terminal diagnostics fail subsequent sender calls and are available as
QuestDBError.sender_error. Server diagnostics are also available throughSender.poll_error().- Parameters:
clear – If
True, the flushed buffer is cleared (default). IfFalse, the flushed buffer is left in the internal buffer. Note thatclear=Falseis only supported ifbufferis also specified.transactional – If
Trueensures that the flushed buffer contains row for a single table, ensuring all data can be written transactionally. This feature requires ILP/HTTP and is not available when connecting over TCP. Default: False.
If the flush fails, the internal buffer is cleared, rows and all; a buffer passed as
bufferis left as it is. Over QWP/WebSocket a buffer is published as one frame, and one whose encoded frame exceeds the connection’s frame cap raisesQuestDBError(BatchTooLarge) without publishing anything.The Python GIL is released during the network IO operation.
- flush_and_get_fsn(buffer=None)¶
Publish a QWP/WebSocket buffer locally, clear it on success, and return the assigned frame sequence number.
- flush_and_keep_and_get_fsn(buffer=None)¶
Publish a QWP/WebSocket buffer locally without clearing it and return the assigned frame sequence number.
- static from_conf(conf_str, *, bind_interface=None, username=None, password=None, token=None, token_x=None, token_y=None, auth_timeout=None, tls_verify=None, tls_ca=None, tls_roots=None, tls_roots_password=None, max_buf_size=None, retry_timeout=None, retry_max_backoff=None, request_min_throughput=None, request_timeout=None, auto_flush=None, auto_flush_rows=None, auto_flush_bytes=None, auto_flush_interval=None, max_datagram_size=None, multicast_ttl=None, qwp_ws_progress=None, error_handler=None, connection_listener=None, connection_event_inbox_capacity=0, protocol_version=None, init_buf_size=None, max_name_len=None)¶
Construct a sender from a configuration string.
The additional arguments are used to specify additional parameters which are not present in the configuration string.
Note that any parameters already present in the configuration string cannot be overridden.
- static from_env(*, bind_interface=None, username=None, password=None, token=None, token_x=None, token_y=None, auth_timeout=None, tls_verify=None, tls_ca=None, tls_roots=None, tls_roots_password=None, max_buf_size=None, retry_timeout=None, retry_max_backoff=None, request_min_throughput=None, request_timeout=None, auto_flush=None, auto_flush_rows=None, auto_flush_bytes=None, auto_flush_interval=None, max_datagram_size=None, multicast_ttl=None, qwp_ws_progress=None, error_handler=None, connection_listener=None, connection_event_inbox_capacity=0, protocol_version=None, init_buf_size=None, max_name_len=None)¶
Construct a sender from the
QDB_CLIENT_CONFenvironment variable.The environment variable must be set to a valid configuration string.
The additional arguments are used to specify additional parameters which are not present in the configuration string.
Note that any parameters already present in the configuration string cannot be overridden.
- init_buf_size¶
The initial capacity of the sender’s internal buffer.
- max_name_len¶
Maximum length of a table or column name.
- new_buffer()¶
Make a new configured buffer.
The buffer is set up with the configured init_buf_size and max_name_len, and matches the sender’s protocol.
Must be called after
Sender.establish()and beforeSender.close(); otherwise raisesQuestDBError(InvalidApiCall).
- poll_error()¶
Poll the next structured QWP/WebSocket diagnostic.
- protocol_version¶
The protocol version used by the sender.
Protocol version 1 is retained for backwards compatibility with older QuestDB versions.
Protocol version 2 introduces binary floating point support and the array datatype.
- published_fsn()¶
Highest QWP/WebSocket frame sequence number published locally.
- row(table_name: str, *, symbols: Dict[str, str | None] | None = None, columns: Dict[str, None | bool | int | float | str | TimestampMicros | TimestampNanos | datetime.datetime | numpy.ndarray | Decimal | uuid.UUID | ipaddress.IPv4Address | bytes | bytearray | memoryview | Char | DateMillis | Long256 | Geohash] | None = None, at: TimestampNanos | datetime.datetime | ServerTimestampType)¶
Write a row to the internal buffer.
This may be sent automatically depending on the
auto_flushsetting in the constructor.See
Buffer.rowfor supported column types, protocol restrictions, server requirements, andNULL-sentinel behavior.Raises
QuestDBError(InvalidApiCall) if called while anotherrow()on this sender is still in progress, or adataframe()overtcp,httporudp: from a column value whose conversion runs Python code, or from a logging handler that writes thequestdblogger’s records through this sender. Such adataframe()writes the frame into this sender’s buffer, and a row written while it runs could not be flushed until it returned. Overwsandwssthe frame goes out on a connection of its own, so a row written from inside it is accepted and flushed as usual.Over
wsandwsswith auto-flush on, it also raisesInvalidApiCall, without adding the row, when called from this sender’s ownerror_handlerorconnection_listener. That includes a logging handler that writes thequestdblogger’s records through this sender while noerror_handleris set, since server rejections are then logged from inside that callback.Over
wsandwss, auto-flush also publishes a buffer whose size estimate reaches the connection’s frame cap, even withauto_flush_bytes=off. Every published frame fits the frame cap: when the whole buffer does not, or its size estimate is past the cap, the rows buffered before this one go out as a frame of their own and this row starts the next batch, so one call publishes at most two frames. Re-adding the row converts its values a second time.Over
wsandwss, whatever this method raises, the row was not added.QuestDBError(BatchTooLarge) means either it does not fit in a frame on its own, or the rows buffered before it do not fit even without it, which can happen after the frame cap shrinks, for example on reconnect; those rows are then discarded, and the error says how many. Any other error raised by a publish also clears the rows buffered before it, asSender.flush()does.
- transaction(table_name: str)¶
Start a HTTP Transactions block.
- class questdb.SenderTransaction¶
Bases:
objectA transaction for a specific table.
Transactions are only supported with ILP/HTTP.
The sender API can only operate on one transaction at a time.
To create a transaction:
with sender.transaction('table_name') as txn: txn.row(..) txn.dataframe(..)
- __enter__()¶
- __exit__(exc_type, exc_val, exc_tb)¶
- commit()¶
Commit the transaction.
A commit is also automatic at the end of a successful with block.
This will flush the buffer.
- dataframe(df, *, symbols: str | bool | List[int] | List[str] = 'auto', at: ServerTimestampType | int | str | TimestampNanos | datetime.datetime)¶
Write a dataframe for the table in the transaction.
The table name is taken from the transaction.
- rollback()¶
Roll back the transaction.
A rollback is also automatic at the end of a failed with block.
This will clear the buffer. When
rollback()is called from code that arow()ordataframe()on this sender runs while it writes, such as a column value’s conversion, it raisesQuestDBError(InvalidApiCall) and leaves the transaction as it was.
- row(*, symbols: Dict[str, str | None] | None = None, columns: Dict[str, None | bool | int | float | str | TimestampMicros | TimestampNanos | datetime.datetime | numpy.ndarray | Decimal] | None = None, at: ServerTimestampType | TimestampNanos | datetime.datetime)¶
Write a row for the table in the transaction.
The table name is taken from the transaction.
Note: Support for NumPy arrays (
numpy.array) requires QuestDB server version 9.0.0 or higher.UUID, IPV4, BINARY, CHAR, DATE, LONG256, and GEOHASH columns are QWP-only and are not supported by
SenderTransaction, which is ILP/HTTP-only by construction.
- class questdb.QueryResult(cursor_handle)¶
Bases:
objectResult of
QuestDB.query(sql).Streams query rows as Arrow record batches. Single-use: each materialisation method (
to_pandas,to_arrow,iter_arrow,iter_pandas, or the__arrow_c_stream__PyCapsule protocol) consumes the underlying cursor; the second consumption raisesQuestDBError. For the same reason it cannot be copied.Consumption rule: fully drain the result, use it as a context manager (
with db.query(...) as result:), or callclose(). Closing a partial result drops its connection. Callcancel()before closing to preserve it.Thread hand-off: a
QueryResultmay be transferred to another thread and consumed, cancelled, closed, or dropped there. Use a normal synchronisation hand-off (for example,Thread.start/joinor a thread-safe queue), and access it from only one thread at a time. Concurrent operations on one result are unsupported.__arrow_c_stream__is native — the cursor’s record batches are exposed directly through the Arrow C Data Interface, so polars (polars.DataFrame(result), polars 1.32 or later), duckdb (duckdb.from_arrow(result), duckdb 1.2 or later) and other Arrow-native consumers can read query results without pyarrow installed. duckdb reads when you fetch, so fetch before the result is closed.to_pandas/iter_pandasare also pyarrow-free by default (a native numpy path); they only need pyarrow whendtype_backend/types_mapperis passed.to_arrow/iter_arrow/to_polars/iter_polarsdo require pyarrow.SYMBOL columns:
to_polars/to_pandasbuild the Categorical directly, interning the connection dictionary once (no per-row remap).to_arrow/iter_arrow/__arrow_c_stream__emit a generic Arrow form whose per-batch SYMBOL dictionary is compacted to the values each batch uses, which a generic consumer reconciles. So when the target is a polars / pandas frame, the dedicated methods avoid the re-reconciliation thatpolars.DataFrame(result)/to_arrow().to_pandas()pay on SYMBOL-heavy results.to_arrow().to_pandas()needs pyarrow 23 or later for SYMBOL columns, which useuint32dictionary indices.Example:
with db.query('SELECT * FROM trades WHERE ts > $1', [datetime.datetime(2026, 7, 1)]) as result: df = polars.DataFrame(result) # no pyarrow # df = result.to_pandas() # no pyarrow # rows = duckdb.from_arrow(result).fetchall() # no pyarrow # table = pa.table(result) # pyarrow required
- __arrow_c_schema__()¶
Arrow schema PyCapsule protocol (no pyarrow needed).
It reads the first batch to get the schema, and keeps that batch for
__arrow_c_stream__. duckdb 1.5 calls it, so thatduckdb.from_arrow(result)opens the stream only once.pa.schema(result)andpolars.Schema(result)call it too. After this call, read the result only through__arrow_c_stream__(pa.table(result),polars.DataFrame(result), duckdb): the other readers raiseInvalidApiCallon their first read. When the schema was read first, later calls return the same schema, also after the stream was taken.
- __arrow_c_stream__(requested_schema=None)¶
Arrow C stream PyCapsule protocol (no pyarrow needed). SYMBOL columns arrive compact — each batch’s dictionary holds only the values it references — so a consumer that unifies per-batch dictionaries (e.g.
polars.DataFrame(result)on polars 1.32 or later) reconciles them. Older polars misreads such a stream, so when polars before 1.32 reads the result directly (polars.DataFrame(result),polars.from_arrow(result)), the call raisesInvalidApiCalland the result stays unread. A stream that reaches polars through another library is not checked.requested_schemais ignored, also when it does not match: the stream always has the result’s own schema, so check the schema you get.pa.table(result, schema=...)andpolars.DataFrame(result, schema=...)cast the result;pa.RecordBatchReader.from_stream(result, schema=...)(pyarrow 15 or later) returns the result’s own schema.The stream releases the result’s connection when the consumer reads the last batch, not when it releases the stream. A reader lease can run its next query as soon as the stream is drained.
The stream does not replay a result: after a batch has been delivered, a failover ends the stream with an error. The consumer raises its own exception type, and the message contains
[FailoverWouldDuplicate]; re-issue the query. polars raises aComputeError, but polars before 1.37.1 raises aPanicException, whichexcept Exceptiondoes not catch.The returned stream may be handed to a consumer worker thread. The cursor lock serialises the native cursor calls, not the stream’s own state, so the Arrow C stream must be consumed by only one thread at a time; concurrent
get_next/releasecalls are unsupported.
- cancel()¶
Cancel the query and drain to terminal.
The result remains open; call
close()before starting another query on the same reader. On failure, the result is closed. Idempotent after success. Rows already decoded but not yet handed out byiter_rows()are not yielded afterwards, and a result not yet read cannot be read afterwards:columns(),iter_rows()and the materialisation methods raiseQuestDBErrorCode.InvalidApiCall.Cancel between rows, not from a signal handler that interrupts a batch being decoded on the same thread: that call is refused with
QuestDBErrorCode.InvalidApiCall, because it would invalidate the batch under the decoder.
- close()¶
Release the cursor and reader. Idempotent.
Terminal connections return to the pool; others are dropped. This does not cancel the query. Outstanding iterators become invalid.
- columns()¶
Column names and QuestDB types as
[(name, type_name), ...].type_nameis the QuestDB DDL spelling —VARCHAR,TIMESTAMP,TIMESTAMP_NS,SYMBOL,IPv4,GEOHASH(8c),DECIMAL(18,2),DOUBLE[]— matchingColumnTypeon the server, so it can be compared againsttables()/information_schema.questdb_columns()without normalising. Useselect ... limit 0to read a schema without fetching rows; such a probe is read to its end here, so its connection goes back to the pool.Does not consume the rows —
iter_rows()continues from the same stream — but does consume the result, so it rules outto_pandas(),to_arrow()and the other materialisation methods. It decodes no rows, is cheap to call repeatedly, and once read still answers afterclose().A result that returned rows is not at its terminal until they have been read, so closing it straight after
columns()drops the connection (and on aPooledReaderlease makes the lease terminal). Iterate the rows, or callcancel()beforeclose(), to keep it; aLIMIT 0probe needs neither.Empty for a statement that ships no result set at all (a non-SELECT); see
exec_donefor that outcome.Three limits are the protocol’s, not this client’s:
A
DECIMALcolumn is reported with its storage width’s maximum precision (18, 38 or 76) because the wire carries only the scale: a column declaredDECIMAL(10,2)reads back asDECIMAL(18,2). Compare on scale when that matters.A legacy
STRINGcolumn is reported asVARCHAR: QWP has no separate STRING column kind.An array column is reported as
DOUBLE[]/LONG[]whatever its dimensionality, because the wire schema carries no dimension count; the server spells a 2-D columnDOUBLE[][].
with db.query('select * from trades limit 0') as result: for name, type_name in result.columns(): print(name, type_name)
- property exec_done¶
The statement’s terminal
EXEC_DONEreport, orNone.An
ExecDone(op_type, rows_affected)once a non-SELECT has been drained to its clean end;Nonebefore that, andNonefor a statement that streamed a result set instead (aSELECTends withEND, which carries no counts).rows_affectedis meaningful forINSERTandUPDATE, and0for a statement that carries no count — one executed at parse time (TRUNCATE,RENAME TABLE,SET,ALTER TABLE ... RESUME WAL, …).op_typeis the server’s QWP operation-type byte, carried through as an opaqueint: its values are the server’s, not part of this client’s API. Still available afterclose().with db.query('insert into t values (1)') as result: list(result.iter_rows()) # drains it print(result.exec_done.rows_affected)
- iter_arrow()¶
Iterate result batches as
pyarrow.RecordBatch.Streaming: a mid-query failover after the first batch has been yielded surfaces
QuestDBErrorCode.FailoverWouldDuplicate(the already-yielded batches cannot be discarded); re-issue the query. If the iterator is abandoned partway, cleanup runs at the next garbage-collection cycle; callclose()(or use the context- manager) for deterministic release.
- iter_pandas(*, dtype_backend=None, types_mapper=None)¶
Iterate result batches as
pandas.DataFrame.Mirrors
to_pandas(): with no arguments each batch is materialised straight into numpy (no pyarrow, sentinel-preserving).dtype_backend/types_mapperselect the pyarrow-backed path instead. Either way every batch carriesdf.attrs['questdb'], aGEOHASHcolumn gets the same signed integer type, and what a customtypes_mappercosts the round trip is the same as it is there.
- iter_polars()¶
Iterate result batches as
polars.DataFrame.Mirrors
to_polars()per batch (sameCategoricalSYMBOL handling) for streaming / low-peak-memory consumption. Every batch’s SYMBOL Categoricals share one persistentCategoriesidentity, sopolars.concatover the yielded frames stitches without a categories-mismatch error. If theCategoricalfast path fails, it issues oneRuntimeWarningand builds the remaining frames withpolars.from_arrow, cast to the sameCategories. If that cast fails too, the warning says so: those frames have a plainCategoricaldtype, andpolars.concatwith the frames before fails.Streaming: a mid-query failover after the first batch has been yielded surfaces
QuestDBErrorCode.FailoverWouldDuplicate; re-issue the query. Requires polars and pyarrow.
- iter_rows()¶
Iterate the result as tuples of Python objects.
For DB-API-shaped callers — a PEP 249 cursor, dbt, plain scripting — and for small result sets where exact QuestDB types matter more than throughput: metadata lookups, test rows,
limit 0schema probes. Needs neitherpyarrownorpandas. Large analytical results belong onto_pandas()/to_arrow(), which stay columnar; a row of Python objects costs an allocation per cell.Streams one batch at a time: the batch being decoded is the only one held in memory.
Nonemarks a SQL NULL in every column.Value mapping:
QuestDB
Python
BOOLEANboolBYTE,SHORT,INT,LONGintFLOAT,DOUBLEfloat(never NaN: the server sends NULL)VARCHAR,SYMBOLstrCHARone-character
str; code point 0 isNoneTIMESTAMPdatetime(UTC-aware, microseconds)TIMESTAMP_NSdatetime(UTC-aware), rounded down to microsecondsDATEdatetime(UTC-aware, milliseconds)UUIDuuid.UUIDLONG256int(unsigned)DECIMAL(p,s)decimal.Decimal(scale preserved)GEOHASH(nc)str(base-32, or ‘0’/’1’ bits when n is not a multiple of 5)IPv4ipaddress.IPv4AddressBINARYbytesDOUBLE[]numpy.ndarrayDatetimes are timezone-aware UTC, matching what
to_arrow()labels them.TIMESTAMP_NSis rounded down (towards the past) becausedatetimehas no nanosecond field; useto_pandas()orto_arrow()for full precision. A temporal value outsidedatetime’s years 1..9999 raisesQuestDBErrorCode.InvalidTimestampnaming the column. Like any other error raised while decoding, it closes the result before its terminal, so the connection is dropped (on aPooledReaderlease, the lease becomes terminal). A server-side error — a parse error, a timeout — ends at a terminal frame instead and keeps the connection. ACHARholding a UTF-16 surrogate code unit comes back as that lone surrogate, which cannot be encoded to UTF-8.LONG[]columns are not supported on this path.Every call returns the same iterator, so
columns()anditer_rows()share one stream and an exhausted iterator stays empty. The batch being handed out is the only one held in memory. Aftercancel()no further rows are yielded, including any already decoded; afterclose()the iterator raises. A freshiter_rows()call raises too once the stream ended with an error or its iterator was closed. A mid-query failover after iteration has begun raisesQuestDBErrorCode.FailoverWouldDuplicaterather than replaying rows the caller already holds.with db.query('select ts, symbol from trades limit 3') as result: for ts, symbol in result.iter_rows(): print(ts, symbol)
- to_arrow()¶
Read the full result into a
pyarrow.Table. Requires pyarrow.SYMBOL columns use
uint32dictionary indices, whichTable.to_pandas()accepts only from pyarrow 23. For a pandas frame, callto_pandas()instead. DECIMAL columns with a precision of 18 or less arrive asdecimal32/decimal64, which need pyarrow 20 or later.Materialise-whole: a mid-query failover replays the result transparently — the partial accumulation we hold is discarded from batch-0. The streaming path (
__arrow_c_stream__, as consumed bypolars.DataFrame(result)orpa.table(result)) does not replay: on a post-delivery failover, the consumer raises its own error, and the message contains[FailoverWouldDuplicate].
- to_pandas(*, dtype_backend=None, types_mapper=None)¶
Read the full result into a
pandas.DataFrame.The default is a native (no pyarrow) hybrid built straight from the QWP column buffers: a nullable integer column with nulls becomes a pandas nullable
Int*(pd.NA); without nulls it stays plain numpy.double/floatstay numpy withNaN;SYMBOL→Categorical;TIMESTAMP→datetime64(NaT); strings/decimal/uuid/binary →object. With no arguments or adtype_backend, aGEOHASHcolumn is a signed integer sized by its precision:int8up to 7 bits,int16to 15,int32to 31,int64to 60, asto_arrow()gives it. Analysis-safe (aggregations skippd.NA/NaN), and feeds back intoQuestDB.dataframe()for a type round-trip — the column kinds are carried indf.attrs['questdb'].dtype_backend="pyarrow"/"numpy_nullable"/types_mapperselect the pyarrow-backed path instead (pd.ArrowDtype, pandas nullable extension dtypes, or a custom mapper) — matching thepd.read_sql/pd.read_parquetconvention. Those paths attachdf.attrs['questdb']too: a pandas dtype holds an Arrow type and no field, soattrsis what carries the IPV4 / CHAR / GEOHASH / UUID / LONG256 claims that live in Arrow field metadata.QuestDB.dataframereads the claim back from either built-in backend — including the masked and object-dtype columns"numpy_nullable"produces for those five — so writing the result out again gives the same column types, and for those five the same wire bytes.A custom
types_mappergets the sameattrs, but whether the claim can be applied depends on the dtype the mapper picked. It has to be a shape that still holds the value: an Arrow-backed column, a NumPyuint32/uint16/ integer column, or an object column of Python ints orbytes. Map one of those five columns to a float or a string dtype and the claim is ignored — the column lands as that dtype implies, and re-ingestion logs aquestdbwarning about the dropped claim.df.attrs['questdb']reads as an ordinary mapping but cannot be edited in place: pandas deep-copies the whole ofattrsonce per column whenever it propagates it, so a per-column claim that copied would make slicing and exporting quadratic in the column count. Every copy of the frame shares the one claim instead. To change what a frame claims, assign a new mapping todf.attrs['questdb'], or state the type outright withschema_overrides/symbols, which outrank it.
- to_polars()¶
Read the full result into a
polars.DataFrame. Requires polars and pyarrow.Non-
SYMBOLcolumns keep their exactpolars.from_arrowdtypes (tz-awareDatetime,Decimal,Binary,List/Array, …).SYMBOLcolumns are built into a polarsCategoricaldirectly from their codes + dictionary through a persistentCategoriesregistry — the wire code is its own physical categorical code, so there is no per-rowDictionary -> Categoricalremap. Falls back topolars.from_arrowwhen polars’ (unstable)CategoriesAPI is unavailable. If that path fails, it also falls back and issues aRuntimeWarning: the values stay correct, but the read is slower. Under-W errorthat warning raises after the result was read, so the frame is lost; run the query again.Materialise-whole: a mid-query failover replays the result transparently. This accumulates batches in-library (via pyarrow) so the partial result can be discarded on failover. The pyarrow-free
polars.DataFrame(result)streams through__arrow_c_stream__and does not replay: on a post-delivery failover, polars raises its own error, and the message contains[FailoverWouldDuplicate]. polars before 1.37.1 raises aPanicException, whichexcept Exceptiondoes not catch.
- class questdb.ExecDone(op_type, rows_affected)¶
A non-SELECT statement’s terminal
EXEC_DONEreport.Returned by
QuestDB.execute()/PooledReader.execute()and carried onQueryResult.exec_done; both giveNoneinstead for a statement that streamed a result set. A named tuple:done = db.execute(sql)reads asdone.rows_affected.- op_type¶
The server’s QWP operation-type byte, carried through as an opaque
int; its values are the server’s, not part of this client’s API.
- rows_affected¶
The number of rows an
INSERTorUPDATEtouched;0for a statement that carries no count, such as one executed at parse time (TRUNCATE,RENAME TABLE,SET, …).
- class questdb.ConnectionEvent(kind: ConnectionEventKind, host: str | None, port: str | None, previous_host: str | None, previous_port: str | None, attempt_number: int | None, cause_code: QuestDBErrorCode | None, cause_msg: str | None, timestamp_millis: int)¶
Bases:
objectOne connection-state transition, delivered to the
connection_listenerregistered viaQuestDB.from_conf().Listeners run on a dedicated dispatcher thread — never on an I/O or caller thread — fed by a bounded inbox with a drop-oldest overflow policy, so a slow listener cannot stall ingest or reconnects. Successful events are queued only after negotiated connection state, including the server-advertised frame cap, is committed. They are not data-delivery or acknowledgement barriers. Exceptions raised by the listener are logged and swallowed.
- __init__(kind: ConnectionEventKind, host: str | None, port: str | None, previous_host: str | None, previous_port: str | None, attempt_number: int | None, cause_code: QuestDBErrorCode | None, cause_msg: str | None, timestamp_millis: int) None¶
- cause_code: QuestDBErrorCode | None¶
- kind: ConnectionEventKind¶
- class questdb.ConnectionEventKind(*values)¶
Bases:
TaggedEnumConnection-state transitions observed by the ingress connection pool.
- AllEndpointsUnreachable = ('all_endpoints_unreachable', 5)¶
- AuthFailed = ('auth_failed', 6)¶
- Connected = ('connected', 0)¶
- Disconnected = ('disconnected', 1)¶
- EndpointAttemptFailed = ('endpoint_attempt_failed', 4)¶
- FailedOver = ('failed_over', 3)¶
- Reconnected = ('reconnected', 2)¶
- class questdb.ServerInfo(role: ServerRole, role_byte: int, epoch: int, capabilities: int, server_wall_ns: int, cluster_id: str, node_id: str, zone_id: str | None)¶
Bases:
objectSnapshot of the server’s
SERVER_INFOhandshake, as advertised on the connectionQuestDB.server_info()sampled. Fields mirror the Rust reader’sServerInfo.- __init__(role: ServerRole, role_byte: int, epoch: int, capabilities: int, server_wall_ns: int, cluster_id: str, node_id: str, zone_id: str | None) None¶
- role: ServerRole¶
- class questdb.ServerRole(*values)¶
Bases:
TaggedEnumCluster role advertised by the server’s
SERVER_INFOhandshake.- Other = ('other', 255)¶
- Primary = ('primary', 1)¶
- PrimaryCatchup = ('primary_catchup', 3)¶
- Replica = ('replica', 2)¶
- Standalone = ('standalone', 0)¶
- class questdb.QuestDBError(code, msg, sender_error=None, *, in_doubt=False)¶
Bases:
ExceptionAn error whilst using the QuestDB client.
- __init__(code, msg, sender_error=None, *, in_doubt=False)¶
- property code: QuestDBErrorCode¶
Return the error code.
- property in_doubt: bool¶
Whether the failed ingestion operation may already have delivered its input. For QWP
dataframe(), this covers the entire DataFrame, including earlier batches from the same call, even when a later batch fails local validation. For a sender flush, it describes that flush; it does not summarize earlier independent calls on the sender.Retrying that input when this is true requires an appropriate application- or table-level deduplication guarantee. A false value says only that duplicate delivery is not known to be possible; a consumed one-shot input may still be impossible to replay.
- property sender_error¶
Return the structured QWP/WebSocket diagnostic, if this error carries one from a QWP/WebSocket sender failure.
- class questdb.QuestDBServerRejectionError(code, msg, sender_error=None, *, in_doubt=False)¶
Bases:
QuestDBErrorA terminal QWP/WebSocket server rejection.
The structured server payload is available through
QuestDBError.sender_error.
- class questdb.UnsupportedDataFrameShapeError(msg, column_failures=None)¶
Bases:
QuestDBErrorA DataFrame shape is not supported by the optimized columnar client path.
column_failurescarries structured per-column rejection details where available; each is adictwithcolumn(name orNonefor a whole-frame issue),target,source_code, andreason. The same per-column reasons are also folded intostr(exc)so a bareprint(exc)explains why the frame was rejected, and — when the designated timestamp is at fault — how to fix it onQuestDB.dataframe()(name a valid timestamp column, or passServerTimestamp).- __init__(msg, column_failures=None)¶
- class questdb.QuestDBErrorCode(*values)¶
Bases:
EnumCategory of Error.
- ArrayError = 11¶
- ArrowExport = 34¶
- ArrowIngest = 16¶
- ArrowUnsupportedColumnKind = 15¶
- AuthError = 6¶
- BadDataFrame = 65536¶
- BatchTooLarge = 35¶
- Cancelled = 30¶
- ConfigError = 10¶
- ConnectTimeout = 19¶
- CouldNotResolveAddr = 0¶
- DecimalError = 13¶
- FailoverRetry = 17¶
- FailoverWouldDuplicate = 31¶
- HandshakeError = 20¶
- HttpNotSupported = 8¶
- InvalidApiCall = 1¶
- InvalidBind = 23¶
- InvalidName = 4¶
- InvalidTimestamp = 5¶
- InvalidUtf8 = 3¶
- LimitExceeded = 28¶
- NoSchema = 33¶
- ProtocolError = 22¶
- ProtocolVersionError = 12¶
- QueryTimeout = 38¶
- RoleMismatch = 18¶
- SchemaDrift = 32¶
- ServerFlushError = 9¶
- ServerInternalError = 26¶
- ServerLimitExceeded = 29¶
- ServerParseError = 25¶
- ServerRejection = 14¶
- ServerSchemaMismatch = 24¶
- ServerSecurityError = 27¶
- SocketError = 2¶
- StoreResendRequired = 36¶
- SymbolDictFull = 37¶
- TlsError = 7¶
- UnsupportedServer = 21¶
- class questdb.SenderError(category: SenderErrorCategory, applied_policy: SenderErrorPolicy, status: int | None, message: str, message_sequence: int | None, from_fsn: int, to_fsn: int)¶
Bases:
objectStructured QWP/WebSocket server diagnostic.
- __init__(category: SenderErrorCategory, applied_policy: SenderErrorPolicy, status: int | None, message: str, message_sequence: int | None, from_fsn: int, to_fsn: int) None¶
- applied_policy: SenderErrorPolicy¶
- category: SenderErrorCategory¶
- class questdb.SenderErrorCategory(*values)¶
Bases:
TaggedEnumCategory of a structured QWP/WebSocket diagnostic.
- InternalError = ('internal_error', 2)¶
- NotWritable = ('not_writable', 5)¶
- ParseError = ('parse_error', 1)¶
- ProtocolViolation = ('protocol_violation', 6)¶
- SchemaMismatch = ('schema_mismatch', 0)¶
- SecurityError = ('security_error', 3)¶
- Unknown = ('unknown', 7)¶
- WriteError = ('write_error', 4)¶
- class questdb.SenderErrorPolicy(*values)¶
Bases:
TaggedEnumApplied policy for a structured QWP/WebSocket diagnostic.
- Retriable = ('retriable', 0)¶
- RetriableOther = ('retriable_other', 1)¶
- Terminal = ('terminal', 2)¶
- class questdb.QwpWsProgress(*values)¶
Bases:
TaggedEnumProgress mode for QWP/WebSocket senders.
- Background = ('background', 0)¶
- Manual = ('manual', 1)¶
- class questdb.Protocol(*values)¶
Bases:
TaggedEnumProtocol to use for sending data to QuestDB.
See Which protocol? for more information.
- Http = ('http', 2)¶
- Https = ('https', 3)¶
- Tcp = ('tcp', 0)¶
- Tcps = ('tcps', 1)¶
- Udp = ('udp', 4)¶
- Ws = ('ws', 5)¶
- Wss = ('wss', 6)¶
- property tls_enabled¶
- class questdb.TimestampMicros¶
Bases:
objectA timestamp in microseconds since the UNIX epoch (UTC).
You may construct a
TimestampMicrosfrom an integer or adatetime.datetime, or simply call theTimestampMicros.now()method.# Recommended way to get the current timestamp. TimestampMicros.now() # The above is equivalent to: TimestampMicros(time.time_ns() // 1000) # You can provide a numeric timestamp too. It can't be negative. TimestampMicros(1657888365426838)
TimestampMicroscan also be constructed from adatetime.datetimeobject.TimestampMicros.from_datetime( datetime.datetime.now(tz=datetime.timezone.utc))
We recommend that when using
datetimeobjects, you explicitly pass in the timezone to use. Adatetimeobject without an associated timezone is interpreted as UTC (aUserWarningis emitted once per process). Note thatdatetime.datetime.now()is your local wall clock: usedatetime.datetime.now(datetime.timezone.utc)ornow()on this class for the current instant.- classmethod from_datetime(dt: datetime.datetime)¶
Construct a
TimestampMicrosfrom adatetime.datetimeobject.
- classmethod now()¶
Construct a
TimestampMicrosfrom the current time as UTC.
- value¶
Number of microseconds (Unix epoch timestamp, UTC).
- class questdb.TimestampNanos¶
Bases:
objectA timestamp in nanoseconds since the UNIX epoch (UTC).
You may construct a
TimestampNanosfrom an integer or adatetime.datetime, or simply call theTimestampNanos.now()method.# Recommended way to get the current timestamp. TimestampNanos.now() # The above is equivalent to: TimestampNanos(time.time_ns()) # You can provide a numeric timestamp too. It can't be negative. TimestampNanos(1657888365426838016)
TimestampNanoscan also be constructed from adatetimeobject.TimestampNanos.from_datetime( datetime.datetime.now(tz=datetime.timezone.utc))
We recommend that when using
datetimeobjects, you explicitly pass in the timezone to use. Adatetimeobject without an associated timezone is interpreted as UTC (aUserWarningis emitted once per process). Note thatdatetime.datetime.now()is your local wall clock: usedatetime.datetime.now(datetime.timezone.utc)ornow()on this class for the current instant.- classmethod from_datetime(dt: datetime.datetime)¶
Construct a
TimestampNanosfrom adatetime.datetimeobject.
- classmethod now()¶
Construct a
TimestampNanosfrom the current time as UTC.
- value¶
Number of nanoseconds (Unix epoch timestamp, UTC).
- class questdb.Char¶
Bases:
objectA QuestDB CHAR value stored as one UTF-16 code unit.
'\x00'is stored as code unit 0. CHAR has no physicalNULLrepresentation: QWP/Arrow egress returns it as 0, while text output renders it as empty. Some QuestDB SQL operations treat code unit 0 as CHAR’s null/absent marker.- value¶
The single Python code point represented by this CHAR.
- class questdb.DateMillis¶
Bases:
objectA QuestDB DATE value in milliseconds since the Unix epoch (UTC).
QuestDB DATE is a millisecond timestamp, not a civil date. The full signed 64-bit range is accepted, including pre-epoch values.
INT64_MINis accepted but reads back from QuestDB asNULL.This is how
Buffer.rowwrites a DATE column. DataFrames claim DATE from the column’s Arrow type instead —pa.timestamp('ms')(naive or tz-aware),pa.date32(), orpa.date64()— so there is no DATE cell type and no'date'kind forschema_overrides. A NumPydatetime64[ms]dtype has no route of its own to DATE and widens to a microsecond TIMESTAMP, unless the frame carries adf.attrs['questdb']claim naming the column DATE, which puts the Arrow type back on it first. That claim is written as{'kind': 'date'}under the column’s entry in the version-1 claim mapping. Like the other QWP-only types, DATE needs a QWP sender; ILP senders have no DATE type, so the datetime columns they accept all land as TIMESTAMP.- classmethod from_datetime(dt: datetime.datetime)¶
Construct a
DateMillisby flooring a datetime to milliseconds.
- classmethod now()¶
Construct a
DateMillisfrom the current time as UTC.
- value¶
Milliseconds since the Unix epoch (UTC).
- class questdb.Long256¶
Bases:
objectAn unsigned 256-bit integer for a QuestDB LONG256 column.
The value whose four 64-bit limbs are all
0x8000000000000000is accepted but reads back from QuestDB asNULL.- value¶
The unsigned integer value.
- class questdb.Geohash¶
Bases:
objectA QuestDB GEOHASH value represented by bits and precision.
This scalar wrapper is strict:
precisionmust be in1..=60andbitsmust satisfy0 <= bits < 2**precision. Bulk GEOHASH columns, whether NumPy, Arrow or object, deliberately have a different, unchecked value contract; seeQuestDB.dataframe.Precision is pinned per column within one buffer’s worth of rows. The first GEOHASH cell written to a column fixes the precision for the rest of that batch, and a later cell at a different precision is rejected by
Buffer.rowitself, with the buffer rewound to what it held before that row – the bad row is not left waiting for a flush.The pin goes with the buffer: a flush clears it and the next batch starts over. A precision the server’s column does not have is therefore the server’s to reject, at flush time.
- bits¶
The packed geohash bits.
- classmethod from_string(value)¶
Construct a GEOHASH from one to twelve base32 characters.
- precision¶
The precision in bits.
- class questdb.TlsCa(*values)¶
Bases:
TaggedEnumVerification mechanism for the server’s certificate.
Here
webpkirefers to the WebPKI library andosrefers to the operating system’s certificate store.See TLS for more information.
- OsRoots = ('os_roots', 1)¶
- PemFile = ('pem_file', 3)¶
- WebpkiAndOsRoots = ('webpki_and_os_roots', 2)¶
- WebpkiRoots = ('webpki_roots', 0)¶
- class questdb.ServerTimestampType¶
Bases:
objectA placeholder value to indicate that the data should be inserted using a server-generated-timestamp.
Don’t instantiate this class directly, use the singleton
ServerTimestampinstead.This feature is mostly provided for legacy compatibility. We recommend always specifying an explicit timestamp.
Using
ServerTimestampwill prevent QuestDB’s deduplication feature from working as it would generate unique rows on resubmission.
- questdb.ServerTimestamp¶
A placeholder value to indicate that the data should be inserted using a server-generated-timestamp.
Don’t instantiate this class directly, use the singleton
ServerTimestampinstead.This feature is mostly provided for legacy compatibility. We recommend always specifying an explicit timestamp.
Using
ServerTimestampwill prevent QuestDB’s deduplication feature from working as it would generate unique rows on resubmission.
- class questdb._client.TaggedEnum(new_class_name, /, names, *, module=None, qualname=None, type=None, start=1, boundary=None)¶
Bases:
EnumBase class for tagged enums.
- property c_value¶
- classmethod parse(tag)¶
Parse from the tag name.
- property tag¶
Short name.
- questdb.WARN_HIGH_RECONNECTS¶
Returns True when the argument is true, False otherwise. The builtins True and False are the only two instances of the class bool. The class bool is a subclass of the class int, and cannot be subclassed.
questdb.ingress (legacy)¶
The deprecated 4.x compatibility shim. New code imports from questdb.
- class questdb.ingress.Buffer¶
Internal row-serialization buffer, managed by
Sender.Kept importable as
questdb.ingress.Bufferfor legacy ILP/HTTP and ILP/TCP code that constructs buffers explicitly and flushes them viasender.flush(buffer).- __init__(*args, **kwargs)¶
- clear()¶
Reset the buffer.
Note that flushing a buffer will (unless otherwise specified) also automatically clear it.
This method is designed to be called only in conjunction with
sender.flush(buffer, clear=False).Raises
QuestDBError(InvalidApiCall) if aBuffer.roworBuffer.dataframecall on this buffer is still in progress.
- dataframe(df, *, table_name: str | None = None, table_name_col: None | int | str = None, symbols: str | bool | List[int] | List[str] = 'auto', at: ServerTimestampType | int | str | TimestampNanos | datetime.datetime)¶
Add a pandas DataFrame to the buffer.
Also see
Sender.dataframeif you are not using the buffer explicitly. Over ILP and QWP/UDP it accepts the arguments shown here and adds auto-flushing. Over QWP/WebSocket it instead uses the direct columnar path, additionally acceptingmax_rows_per_batchandschema_overrides; seeQuestDB.dataframefor that path’s supported column types and arguments.Requires
pandasandnumpy.pyarrowis only needed when the frame containspd.ArrowDtype/pd.Categorical/stringdtype columns — purely NumPy / object dtypes work without it.Adding a dataframe can trigger auto-flushing behaviour, even between rows of the same dataframe. To avoid this, you can use HTTP and transactions (see
Sender.transaction).- Parameters:
df (pandas.DataFrame) – The pandas DataFrame to serialize to the buffer.
table_name (str or None) –
The name of the table to which the rows belong.
If
None, the table name is taken from thetable_name_colparameter. If bothtable_nameandtable_name_colareNone, the table name is taken from the DataFrame’s index name (df.index.nameattribute).table_name_col (str or int or None) –
The name or index of the column in the DataFrame that contains the table name.
If
None, the table name is taken from thetable_nameparameter. If bothtable_nameandtable_name_colareNone, the table name is taken from the DataFrame’s index name (df.index.nameattribute).If
table_name_colis an integer, it is interpreted as the index of the column starting from0. The index of the column can be negative, in which case it is interpreted as an offset from the end of the DataFrame. E.g.-1is the last column.symbols (str or bool or list[str] or list[int]) –
The columns to be serialized as symbols.
If
'auto'(default), all columns of dtype'categorical'are serialized as symbols. IfTrue, allstrcolumns are serialized as symbols. IfFalse, no columns are serialized as symbols.The list of symbols can also be specified explicitly as a
listof column names (str) or indices (int). Integer indices start at0and can be negative, offset from the end of the DataFrame. E.g.-1is the last column.Only columns containing strings can be serialized as symbols.
at (TimestampNanos, datetime.datetime, int or str or None) –
The designated timestamp of the rows.
You can specify a single value for all rows or column name or index. If
ServerTimestamp, timestamp is assigned by the server for all rows. To pass in a timestamp explicitly as an integer use theTimestampNanoswrapper type. To get the current timestamp, useTimestampNanos.now(). When passing adatetime.datetimeobject, the timestamp is converted to nanoseconds. A naivedatetimeobject is interpreted as UTC — never your machine’s local timezone — and aUserWarningis emitted once per process (calldatetime.datetime.now(tz=datetime.timezone.utc)for the current timestamp to avoid bugs).To specify a different timestamp for each row, pass in a column name (
str) or index (int, 0-based index, negative index supported): In this case, the column needs to be of dtypedatetime64[ns](assumed to be in the UTC timezone and not local, due to differences in Pandas and Python datetime handling) ordatetime64[ns, tz]. When a timezone is specified in the column, it is converted to UTC automatically.A timestamp column can also contain
Nonevalues. The server will assign the current timestamp to those rows.Note: All timestamps are always converted to nanoseconds and in the UTC timezone. Timezone information is dropped before sending and QuestDB will not store any timezone information.
Note: It is an error to specify both
table_nameandtable_name_col.Note: The “index” column of the DataFrame is never serialized, even if it is named.
Example:
import pandas as pd import questdb as qi buf = qi.ingress.Buffer(protocol_version=2) # ... df = pd.DataFrame({ 'location': ['London', 'Managua', 'London'], 'temperature': [24.5, 35.0, 25.5], 'humidity': [0.5, 0.6, 0.45], 'ts': pd.date_range('2021-07-01', periods=3)}) buf.dataframe( df, table_name='weather', at='ts', symbols=['location']) # ... sender.flush(buf)
Pandas to ILP datatype mappings
Pandas Mappings¶ Pandas
dtypeNulls
ILP Datatype
'bool'N
BOOLEAN'boolean'N α
BOOLEAN'object'(boolobjects)N α
BOOLEAN'uint8'N
INTEGER'int8'N
INTEGER'uint16'N
INTEGER'int16'N
INTEGER'uint32'N
INTEGER'int32'N
INTEGER'uint64'N
INTEGERβ'int64'N
INTEGER'UInt8'Y
INTEGER'Int8'Y
INTEGER'UInt16'Y
INTEGER'Int16'Y
INTEGER'UInt32'Y
INTEGER'Int32'Y
INTEGER'UInt64'Y
INTEGERβ'Int64'Y
INTEGER'object'(intobjects)Y
INTEGERβ'float32'γY (
NaN)FLOAT'float64'Y (
NaN)FLOAT'object'(floatobjects)Y (
NaN)FLOAT'string'(strobjects)Y
STRING(default),SYMBOLviasymbolsarg. δ'string[pyarrow]'Y
STRING(default),SYMBOLviasymbolsarg. δ'category'(strobjects) εY
SYMBOL(default),STRINGviasymbolsarg. δ'object'(strobjects)Y
STRING(default),SYMBOLviasymbolsarg. δ'datetime64[ns]'Y
TIMESTAMPζ'datetime64[ns, tz]'Y
TIMESTAMPζ'object'(Decimalobjects)Y (
NaN)DECIMALNote
α: Note some pandas dtypes allow nulls (e.g.
'boolean'), where the QuestDB database does not.β: The valid range for integer values is -2^63 to 2^63-1. Any
'uint64','UInt64'or pythonintobject values outside this range will raise an error during serialization.γ: Upcast to 64-bit float during serialization.
δ: Columns containing strings can also be used to specify the table name. See
table_name_col.ε: We only support categories containing strings. If the category contains non-string values, an error will be raised.
ζ: The ‘.dataframe()’ method only supports datetimes with nanosecond precision. The designated timestamp column (see
atparameter) maintains the nanosecond precision, whilst values stored as columns have their precision truncated to microseconds. All dates are sent as UTC and any additional timezone information is dropped. If no timezone is specified, we follow the pandas convention of assuming the timezone is UTC. Datetimes before 1970-01-01 00:00:00 UTC are not supported. If a datetime value is specified asNone(NaT), it is interpreted as the current QuestDB server time set on receipt of message.
Error Handling and Recovery
In case an exception is raised during dataframe serialization, the buffer is left in its previous state. The buffer remains in a valid state and can be used for further calls even after an error.
For clarification, as an example, if an invalid
Nonevalue appears at the 3rd row for aboolcolumn, neither the 3rd nor the preceding rows are added to the buffer.Note: This differs from the
Sender.dataframemethod, which modifies this guarantee due to itsauto_flushlogic.Performance Considerations
The Python GIL is released during serialization if it is not needed. If any column requires the GIL, the entire serialization is done whilst holding the GIL.
Column types that require the GIL are:
Columns of
str,floatorintorfloatPython objects.The
'string[python]'dtype.
- init_buf_size¶
The initial capacity of the buffer when first created.
This may grow over time, see
capacity().
- max_name_len¶
Maximum length of a table or column name.
- reserve(additional)¶
Ensure the buffer has at least additional bytes of future capacity.
- Parameters:
additional (int) – Additional bytes to reserve.
- row(table_name: str, *, symbols: Dict[str, str | None] | None = None, columns: Dict[str, None | bool | int | float | str | TimestampMicros | TimestampNanos | datetime.datetime | numpy.ndarray | Decimal | uuid.UUID | ipaddress.IPv4Address | bytes | bytearray | memoryview | Char | DateMillis | Long256 | Geohash] | None = None, at: ServerTimestampType | TimestampNanos | datetime.datetime)¶
Add a single row (line) to the buffer.
# All fields specified. buffer.row( 'table_name', symbols={'sym1': 'abc', 'sym2': 'def', 'sym3': None}, columns={ 'col1': True, 'col2': 123, 'col3': 3.14, 'col4': 'xyz', 'col5': TimestampMicros(123456789), 'col6': datetime(2019, 1, 1, 12, 0, 0), 'col7': numpy.array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]), 'col8': None}, at=TimestampNanos(123456789)) # Only symbols specified. Designated timestamp assigned by the db. buffer.row( 'table_name', symbols={'sym1': 'abc', 'sym2': 'def'}, at=Server.Timestamp) # Float columns and timestamp specified as `datetime.datetime`. # Pay special attention to the timezone, which if unspecified is # interpreted as UTC. buffer.row( 'sensor data', columns={ 'temperature': 24.5, 'humidity': 0.5}, at=datetime.datetime.now(tz=datetime.timezone.utc))
Python strings passed as values to
symbolsare going to be encoded as theSYMBOLtype in QuestDB, whilst Python strings passed as values tocolumnsare going to be encoded as theSTRINGtype.Refer to the QuestDB documentation to understand the difference between the
SYMBOLandSTRINGtypes (TL;DR: symbols are interned strings).Column values can be specified with Python types directly and map as so:
Python type
Serialized as ILP type
booldecimalintfloatstrnumpy.ndarraydatetime.datetimeandTimestampMicrosuuid.UUIDUUID (QWP-only)
ipaddress.IPv4AddressIPV4 (QWP-only)
bytes,bytearray, ormemoryviewBINARY (QWP-only)
CharCHAR (QWP-only)
DateMillisDATE (QWP-only)
Long256LONG256 (QWP-only)
GeohashGEOHASH (QWP-only)
NoneColumn is skipped and not serialized.
Note: Support for NumPy arrays (
numpy.array) requires QuestDB server version 9.0.0 or higher.The seven QWP-only types require protocol
udp,ws, orwssand are rejected by ILP buffers used bytcp,tcps,http, andhttps. They require QuestDB 10 or newer, the first production QWP implementation.QuestDB reserves these values as
NULLsentinels, but the client deliberately accepts them: IPV40.0.0.0, DATEINT64_MIN, UUID80000000-0000-0000-8000-000000000000, and a LONG256 whose four 64-bit limbs are all0x8000000000000000. CHAR has no physicalNULLsentinel:'\x00'is stored as code unit 0, although some SQL operations treat it as CHAR’s null/absent marker. GEOHASH has no sentinel collision. Empty BINARYb''is a real empty value and is distinct fromNULL.A bare
intis a 64-bit LONG, so a value outside-2**63 .. 2**63-1raisesOverflowError. On a QWP buffer the message namesLong256, the wrapper that sends it as a 256-bit LONG256 instead; an ILP buffer, which has no LONG256 to offer, reports the bare conversion failure.If the destination table was already created, then the columns types will be cast to the types of the existing columns whenever possible (Refer to the QuestDB documentation pages linked above).
Adding a row can trigger auto-flushing behaviour.
- Parameters:
table_name – The name of the table to which the row belongs.
symbols – A dictionary of symbol column names to
strvalues. As a convenience, you can also pass aNonevalue which will have the same effect as skipping the key: If the column already existed, it will be recorded asNULL, otherwise it will not be created.columns – A dictionary mapping column names to the supported column values listed above. As a convenience, you can also pass a
Nonevalue which will have the same effect as skipping the key: If the column already existed, it will be recorded asNULL, otherwise it will not be created.at – The timestamp of the row. This is required! If
ServerTimestamp, timestamp is assigned by QuestDB. Ifdatetime, the timestamp is converted to nanoseconds. A nanosecond unix epoch timestamp can be passed explicitly as aTimestampNanosobject.