Handles and Resources¶
Requires a PeachQ download with DuckDB
DuckDB features need a PeachQ package that includes DuckDB. On the download page, choose Download with DuckDB for Windows, macOS or Linux. The standard packages do not include DuckDB.
PeachQ keeps the familiar kdb+/q handle syntax while giving it a clearer mental model for new resource types.
In kdb+/q, symbols beginning with : are commonly described as file handles, process handles, or communication handles depending on how
they are used. PeachQ preserves those existing behaviours and syntax. For the extended model described here, we use the term resource
specification for a :... symbol that identifies something outside the immediate q value space.
For example:
`:data/trades.csv
`:localhost:5000
`:https://www.timestored.com/data/sample/dowjones.csv
`:pq:duckdb:prod:trade/
A resource specification says what resource is being referred to. The operation applied to it determines what PeachQ asks that resource to do.
Existing kdb+/q behaviour takes precedence where it is defined. PeachQ extends the model to additional transports, containers, formats, and providers.
The mental model¶
A resource can have one or more capabilities. It may be readable, writable, openable, callable, queryable as a table, or expose other resources.
These capabilities overlap. They are not intended to form a rigid hierarchy of mutually exclusive handle types.
| Resource | Example | Typical behaviour |
|---|---|---|
| File | :data/x |
get, set, read0, read1, file I/O |
| Splayed table | :data/trade/ |
get, select, exec, table operations |
| q IPC endpoint | :localhost:5000 |
open, synchronous call, asynchronous send |
| HTTP resource | :http://host/x |
retrieve remote content; select and get decode a CSV as a table |
| WebSocket endpoint | :ws://host/x |
connect, then persistent framed messaging |
| Alias | :pq:q:srv, :pq:duckdb:prod:trade/ |
a named connection of one kind, and its tables |
An opened handle is different from a resource specification. For example, hopen on an IPC resource returns an integer handle. The :...
value identifies the resource; the returned integer represents an opened connection to it. hopen on an alias returns the
alias symbol instead (see Aliases below): the handle says what it is, and it is the same value the coordinate forms use.
Resources as tables¶
PeachQ extends the existing q idea that some resources can be used directly as tables.
A trailing / is an important part of that model. In existing q, a splayed table is addressed by a directory-style resource ending in /. An
alias names its tables the same way, :<table>/ after the alias:
Both mean the resource is explicitly table- or collection-like. A resource without either remains file-, object-, endpoint-, or member-like unless another rule, such as a recognised tabular file format, gives it table semantics.
This gives PeachQ two clear routes into qSQL:
- An explicit table resource, signalled by a trailing
/: a splay, or an alias's table (:<table>/after the alias). - A file-like resource whose format is known to decode to a table, such as
.csv,.tsv,.json, or.parquet(see Parquet).
Existing q supports operations such as:
PeachQ generalises the idea:
select from `:trades.csv
select from `:https://www.timestored.com/data/sample/dowjones.csv
select from `:pq:duckdb:prod:trade/
The intended meaning in each case is:
Resolve this resource as a table, then apply qSQL to it.
The mechanism used to obtain the table may be completely different for each resource.
A local CSV may be read and decoded into a table. An HTTP CSV may first be downloaded and then decoded. A DuckDB provider may translate and push the qSQL query into DuckDB without materialising the full table (Storing tables in DuckDB).
The qSQL expression should not need to know which path was taken.
Transport, container, format, and provider¶
When resolving data resources, it is useful to separate four concepts.
| Concept | Examples | Responsibility |
|---|---|---|
| Transport | file, HTTP, HTTPS, S3 (and the other DuckDB-carried schemes) | Obtain underlying bytes or content; where possible support ranged read0 / read1 |
| Container | ZIP | Expose a member or child resource |
| Format / decoder | CSV, TSV, JSON, Parquet | Convert content into a logical value such as a table |
| Query provider | DuckDB, q IPC provider | Expose table/query semantics directly, potentially with pushdown |
These concepts can compose.
For example, the proposed:
is conceptually:
HTTP transport
-> content
-> CSV decoder
-> table
-> qSQL
A proposed ZIP example:
is conceptually:
file transport
-> ZIP container
-> trades.csv member
-> CSV decoder
-> table
-> qSQL
ZIP support and zip:// syntax are planned, not currently supported.
This separation is important. HTTP is not a CSV provider: it is a transport. ZIP is not a table format: it is a container. CSV describes how the final content is decoded.
Remote schemes: DuckDB is the transport¶
http(s):// is peachq's own HTTP client. Every other remote scheme is carried by DuckDB's httpfs extension under the
transport law: DuckDB is the transport for every scheme it speaks and peachq does not, and a format is decoded by
whoever owns its meaning.
| Scheme | Transport | Notes |
|---|---|---|
s3:// (s3a://, s3n://) |
DuckDB httpfs | anonymous for a public bucket; credentials are DuckDB secrets (below) |
gcs://, gs:// |
DuckDB httpfs | Google Cloud Storage, through its S3-compatible endpoint |
r2:// |
DuckDB httpfs | Cloudflare R2 (always needs a secret) |
hf:// |
DuckDB httpfs | Hugging Face Hub: hf://datasets/<owner>/<dataset>/<path> |
az:// |
N/A | not supported (needs DuckDB's separate azure extension; handed to DuckDB untested) |
httpfs (and aws, the credential chain) are DuckDB extensions. The -duckdb release archive bundles both beside
q, pinned to the bundled DuckDB, and q points DuckDB at that extensions/ directory with auto-install off, so
every scheme above works offline, with no download at first use. With a DuckDB library you supplied yourself (the
standard archive plus PEACHQ_DUCKDB_LIB, or a system DuckDB), DuckDB's own defaults apply: the first remote read
auto-installs httpfs from extensions.duckdb.org into ~/.duckdb, which needs the network once.
What decodes the bytes is unchanged by the scheme:
select from `:s3://bucket/trades.csv / DuckDB fetches the object, peachq's CSV decoder reads it
select from `:hf://datasets/o/d/x.jsonl / likewise JSON Lines
read0 `:s3://bucket/notes.txt / the lines
read1 `:s3://bucket/blob.bin / the bytes
get `:s3://bucket/trades.csv / the same table select from reads
select from `:s3://bucket/x.parquet / parquet is DuckDB's format: read_parquet on the URL, in place
csv, tsv, json, jsonl, read0, read1 and get see the bytes DuckDB's read_blob('url') returns; peachq's
decoders are THE parser of what a cell means, and a second type inference (DuckDB's CSV reader) would disagree with
them. Parquet is the one format DuckDB owns, so it is read in place with pushdown (Parquet).
read_blob fetches the whole object: a ranged read1 (:s3://…;offset;length)slices that in memory, so it costs
the object, not the range. On any URL the query string and#fragmentare not part of the format claim, so
``:…/k.csv?X-Amz-Signature=… is still CSV: a presigned S3 URL is an `https://` URL and travels on peachq's own
client; the same spelling on `s3://` is handed to DuckDB verbatim. An unrecognised ending on a remote resource is
`'type`; there is no q-object fallback to fetch. A glob names several objects: select from :s3://b/*.parquet ``
reads them all (DuckDB's door), whileread0/read1/csv/json read ONE resource and answer'domain`.
Writes follow the same law. `:s3://bucket/x.parquet set t is DuckDB's COPY TO; `:s3://bucket/x.csv set t
(and .json, .txt, .xml, .xls: every .h.tx key that produces lines) writes peachq's own lines through
DuckDB as a dumb line transport (.duckdb.i.write0), byte for byte what the same set writes to a local file, so
select from reads them back. The lines must be UTF-8 (DuckDB text is). A .h.tx entry that produces BYTES has no
remote transport ('nyi); parquet does not reach that arm.
Credentials are DuckDB secrets on the main instance, through the standard library:
\l pq
.duckdb.secret[`aws;`s3;`key_id`secret`region!("AKIA…";"…";"eu-west-1")] / CREATE OR REPLACE SECRET aws (TYPE s3, KEY_ID '…', …)
.duckdb.secret[`aws;`s3;`provider`persistent!(`credential_chain;1b)] / CREATE PERSISTENT SECRET, the AWS SDK chain
.duckdb.secrets[] / name type provider persistent storage scope; never the values
.duckdb.secret[`aws;`;::] / DROP SECRET aws
Opts ride verbatim as KEY value clauses (a string or symbol quoted, a bool or number bare), so DuckDB's own
documentation of a secret type is the reference and an unknown key is DuckDB's own refusal ('duckdb, .duckdb.err[]
the reason). The -duckdb download bundles httpfs, so it loads offline; with a DuckDB library you supplied yourself it is
fetched from DuckDB's repository once (see Remote schemes above), and a failed load is the same 'duckdb with .duckdb.err[] saying why. In restricted mode (-U) every remote door is
'access before DuckDB is asked.
HTTPS certificates¶
peachq's own client (https:// in select from, read0, read1, .Q.hg, .Q.hp, and hopen of tcps:// or
wss://) always checks the server's certificate, and needs no setup to do it: with nothing configured it trusts the
operating system's root certificates: the Windows root store on Windows, OpenSSL's default bundle elsewhere. kdb's
environment variables change that, read afresh on every connection:
| Variable | Effect |
|---|---|
SSL_CA_CERT_FILE |
trust only the roots in this PEM bundle (replaces the operating system's) |
SSL_CA_CERT_PATH |
trust only the roots in this directory |
SSL_VERIFY_SERVER=NO |
do not check certificates at all |
A KX_ prefix (KX_SSL_CA_CERT_FILE) wins over the bare name. A certificate that fails the check is 'conn;
.pq.tlsinfo[] says why, and what each client in the process trusts:
q)@[.Q.hg;`$":https://self-signed.badssl.com/";{x}]
"conn"
q)first exec last_error from .pq.tlsinfo[] where transport=`peachq
"handshake failed: certificate verify failed: self-signed certificate"
ca_source is `os on Windows, `default elsewhere and `env when a variable names the roots;
overridden_by lists the variables in force. Once DuckDB's httpfs is loaded a duckdb row reports its own
ca_cert_file and enable_curl_server_cert_verification settings. DuckDB's remote schemes check certificates with
their own client, which also needs no setup.
get, read0, read1, and select¶
These operations ask different questions of a resource.
asks for the resource using its normal q get semantics. For ordinary file paths, get retains existing q
object-loading semantics, with one addition: a file whose ending names a table format select from reads (.csv,
.tsv, .json, .jsonl, .ndjson, .parquet) answers that table, so get `:trades.csv is
select from `:trades.csv materialised.
ask for the textual or binary contents of a resource. PeachQ should preserve the existing q distinction: read0 is the text-oriented read operation
and read1 is the binary read operation.
Both operations should preserve q's ranged-read forms:
Offsets are resource byte offsets. A transport that supports native range reads, such as HTTP, may satisfy these operations without fetching the
whole object. A transport that does not support efficient ranges falls back to fetching and slicing; the DuckDB-carried schemes (s3://
and the rest of § Remote schemes) do, since read_blob answers the whole object.
For HTTP, therefore:
read0 `:http://example.com/a.txt
read1 `:http://example.com/a.bin
read1 (`:http://example.com/large.bin;1048576;65536)
while:
means:
HTTP transport
-> resource content
-> CSV decode
-> table
-> qSQL
This works today for CSV over http:// and https://. Parquet over HTTP does not decode as a table yet.
The important rule is that read0 and read1 retrieve resource content, while the table resolver used by qSQL
performs format interpretation, and get asks that resolver, never a resolver of its own.
Format inference¶
For table selection, PeachQ should prefer explicit interpretation over inference.
The initial rule is:
- An explicit scheme or
:pq:alias wins. - Otherwise, a recognised file ending selects the format.
- Otherwise, PeachQ does not invent a tabular interpretation.
For example:
select from `:trades.csv
select from `:trades.tsv
select from `:trades.json
select from `:trades.jsonl
select from `:trades.ndjson
can select the CSV/TSV or JSON decoder from the file ending.
The ending is a declaration, not a hint, so it can carry more than the decoder's name. .jsonl and .ndjson say the
file is JSON Lines and it is read that way; .json says only JSON, and the reader decides whether the file is one
document or a stream. Nothing is inferred from the bytes: a .jsonl holding a single JSON array is an error rather than a
quiet re-reading. Reading JSON covers framing.
The same rule is intended to apply after transport resolution:
The final .csv identifies the decoder after HTTP has retrieved the content.
If the resource does not have a recognised table format:
PeachQ should fail rather than guess.
get (and value, its synonym) asks the same resolver ahead of the q binary reader: a recognised ending answers the
table, no recognised ending is the q object load as in kdb+, and a missing file is the reader's own error ('io). kdb+
signals 'type for any non-kdb file there, so no working kdb+ program changes meaning. read0 and read1 remain
independent of table-format inference.
The same ending selects the WRITER. `:f.EXT set t writes the format .h.tx names for EXT, for every .h.tx
key: csv, txt, xml, xls and json through peachq's own writers (the lines save would write, so
select from `:f.csv reads them back), parquet through .parquet.write (see Parquet). t
must be a table ('type otherwise); no recognised ending is the binary form as in kdb+, and a dotfile such as
`:.json is not a format claim. This is a documented divergence from kdb+, where the same set writes the q
binary form under any name. To a remote scheme (s3://, gcs://, hf://) the same endings write the same bytes,
through DuckDB (§ Remote schemes). The read set is the reader's, not .h.tx's: xml, xls, txt
and raw have writers and no reader, so get `:f.xml stays 'type.
Applications that know an ambiguously named file is CSV can use the explicit CSV API rather than relying on qSQL inference.
PeachQ should not initially make HTTP Content-Type, magic-byte sniffing, or other heuristics part of this contract. They can be considered later
without changing the basic model.
CSV and other decoded formats¶
The explicit CSV API remains:
.csv.read is a local CSV loader and decoder. It should not need to understand HTTP, ZIP, S3, or every future transport.
For direct qSQL over a local CSV:
PeachQ can use the CSV decoder to obtain a table and execute qSQL locally.
For a future remote CSV:
PeachQ owns the composition:
HTTP retrieves content
-> CSV decodes content
-> qSQL evaluates the table
The CSV implementation does not become an HTTP implementation. PeachQ's resource layer obtains the content first and then invokes the CSV decoding path.
TSV and JSON follow the same general model where they can be interpreted as tables. A local Parquet file already selects as a table; Parquet over HTTP does not yet. Parquet can support more efficient projection or predicate pushdown than a simple materialising decoder.
Ranged reads¶
Ranged reads are part of the resource contract because they are useful for both existing file I/O and remote object stores.
The public operation remains read0 or read1; transports should not require separate user-facing APIs such as .s3.readRange. Native range support
is an implementation optimisation, not a different user model.
This is particularly important for formats such as Parquet, where a decoder may read file metadata and selected row groups without materialising the whole object.
Containers¶
Containers introduce another resource-resolution step.
The planned ZIP notation is:
and, when table selection is supported:
zip:// explicitly says that the outer resource is a ZIP container. This avoids relying on the outer filename ending in .zip.
The member can then be interpreted independently from the container:
:zip://anything.dat/member.csv
^ ^
ZIP container CSV format
This is deliberate. A ZIP file does not need to be named .zip, and a CSV file does not always need to be named .csv when the caller uses an
explicit decoding API.
Future transports and containers should compose rather than require combined implementations such as "HTTP CSV" or "ZIP CSV".
Aliases: :pq:<kind>:<alias>¶
Some resources are not naturally modelled as transport + decoder: another q process, a worker, a database. PeachQ
names each by :pq:, the kind, and an alias you choose:
| kind | connection (the alias handle) | table coordinate | opener | its arg |
|---|---|---|---|---|
| another q process | `:pq:q:srv |
`:pq:q:srv:trade/ |
.pq.hopen_q[alias;arg] |
anything kdb+ hopen takes: a port, `::5000, `:host:port, `:host:port:user:pass, a string, `:unix://5000, `:tcps://…, the (conn;timeout) pair |
| a fork of this q | `:pq:qfork:w1 |
none | .pq.hopen_qfork[alias;arg] |
:: (the defaults) or a timeout in ms |
| a fresh peachq | `:pq:qspawn:w2 |
none | .pq.hopen_qspawn[alias;arg] |
its argv, a list of strings ("" or :: for none) |
| DuckDB | `:pq:duckdb:db |
`:pq:duckdb:db:trade/ |
.pq.hopen_duckdb[alias;arg] |
a path, ":memory:", or a dict of path and DuckDB options |
| Kafka | `:pq:kafka:feed |
`:pq:kafka:feed:topic/ |
.pq.hopen_kafka[alias;arg] |
a dict of librdkafka settings; see Kafka |
hopen lists the open forms and their errors.
The segment after :pq: is the kind, the alias follows, and :<table>/ after the alias is a table coordinate; both
names are [a-zA-Z][a-zA-Z0-9_]*, and the spelling is case-sensitive. The reserved pq segment is what keeps these
from reading as kdb+ file paths, so anything else under :pq: is 'domain, never a file. Every opener is one line,
hopen (:pq:, so the raw form is the same call
(a bare `` hopen:pq:qfork:w1 ` is arg::). An opener is rank 2 and stays rank 2: its arg is the kind's obvious
form, or a **dict of named options**, the one extension point. A key means the same for every kind that takes it:timeoutis the ms to obtain a live handle,stdout/stderra worker's log files, and a key a kind does not take
is'domain`, never ignored:
| opener | dict keys |
|---|---|
.pq.hopen_q |
conn (any kdb+ form) and timeout; a timeout in both is 'domain |
.pq.hopen_qfork |
timeout, stdout and stderr |
.pq.hopen_qspawn |
argv, timeout, stdout and stderr |
.pq.hopen_duckdb |
path beside DuckDB's own ATTACH and SET options; timeout, stdout and stderr are 'domain |
The arg is kept, privately, with the alias: a reconnect or a relaunch replays it through the same door.
h:.pq.hopen_q[`srv;(`:localhost:5000;1000)] / answers the alias symbol `:pq:q:srv
h "2+2" / the handle applies like any q handle: text is a call
h (`f;20;5) / on a q alias, a list is a (func;args) message as on a kdb handle
select from `:pq:q:srv:trade/ where px>2 / a table on the peer, the query pushed there
hclose h / the one close door
hopenanswers the alias symbol`:pq:<kind>:<alias>, not an int. The alias is required: a handle needs a name, sohopen `:pq:duckdb:is'domain, and a table coordinate is never a connection ('domain).- The symbol IS the live connection:
`:pq:q:srv "1+1"andh "1+1"are the same call, and the public verbs of a kind take it (.duckdb.exec[h;sql]). Afterhclose hthe same symbol answers'conn, and so does any alias that was never opened: a reference never opens anything. Opening the same alias again re-points it in place and answers the same symbol. hclose his the only close door..pq.conns[]lists every open connection, one row each, its columns named in words (-38!,.z.Wand.z.Hkeep kdb's letters):handleis always the int (what.z.pcgets andnegworks on), andspecthe alias symbol (null for a plain handle), withkind,protocol,family,compressed,queued_msgs,queued_bytes,outbound,user,address,provider(q,qfork,qspawn,duckdb),alias,opened,state,pidandownedbeside them.ownedis1bon a worker's alias row andpidis its process id; they are0band null everywhere else, the socket beneath the alias included, socount select from .pq.conns[] where ownedcounts workers.hcloseof an owned handle kills and reaps the worker, and so does this process's exit (exit,\\or the end of its script), without firing.z.pc.- A q peer's handle is a kdb handle: on a
:pq:q:,:pq:qfork:or:pq:qspawn:alias every message is sent to the other process unchanged, exactly as on an int handle, whatever names exist here. The async send isnegof the alias, as on an int:neg his the alias with a leading-(`:pq:qfork:-w1), soneg[h] "x:1"sends async andneg[h][]flushes.negtoggles the-on any:pq:symbol whatever its kind; the provider refuses what it cannot do (DuckDB signals'domain).hopenof the-form opens the alias and answers the-form. On DuckDB text is SQL and a list is'type. - The int in the
handlecolumn is accepted wherever the symbol is, for code that expectshopento answer an int. - If a q peer dies, its alias keeps its number and its row (
statereadsclosed); the next use makes ONE dial attempt through the same open with the same arg, on that arg's own timeout, and a failure errors that use.
A forked worker. h:.pq.hopen_qfork[`w1;::] forks this process: the worker starts with every global this
process has at that moment, and h "expr", h (f;x)and the asyncneg[h] "expr"talk to it over the kdb
protocol like any q server. It is a snapshot, not a mirror (later changes on either side are not seen by the other),
and it only computes:\t,\pand\sare 0, the.z.p/.z.whandlers are the ones a fresh q starts with (so.z.phis the built-in web handler), the connections this process had are not open in it, and it exits without
running.z.exit. A worker may open its own port (`` h "system \"p 0W\"" ``): it then serves IPC and HTTP from the
snapshot as of the fork, and a worker forked again after a death does not re-open it.hclose hkills it; if it dies,
the next use ofhforks a new worker from this process as it is then (openedchanges). The arg is::or the
fork's handshake timeout in ms (null, zero or negative = the default, 5000). A process that cannot be copied safely
refuses with'nofork(not the main thread, a parallel region running, a swap-backed heap, Windows);'fork` is the
OS refusing. Two forks draw the same random numbers.
A launched worker. h:.pq.hopen_qspawn[`w1;("init.q";"-s";"2")] starts a new peachq (this same executable)
with exactly that argv: no shell, no splitting, nothing of peachq's added, so .z.x and .z.f read as they would for
q init.q -s 2. It is an ordinary q process: its script runs (a script error stops the script, not the process, as in
kdb+), its own .z.p* handlers apply, and a \p its script sets keeps serving. Its stdin is the null device; its stdout
and stderr are this process's unless the stdout/stderr options below name files. h talks to it exactly as to a forked worker, and it lives exactly as long as h:
hclose h kills it, and it dies with this process. If it dies, the next use of h launches it again from the same
argv: its recipe is its argv, so anything pushed into it since is gone. The argv is one string per argument
(enlist "init.q" is one argument); "" and :: mean no arguments, and anything else is 'type. The dict form
`argv`timeout!(("init.q";"-s";"2");20000) bounds the launch (ms; null, zero or negative = the default 10000); a
worker that exits or never answers within it is 'proc. A successful open means the process started and answered,
not that its script succeeded: a script error stops the script and leaves the worker serving, so check what the script
should have defined before relying on it. On Windows hclose ends the worker outright: there is no TERM to ask it
first.
A worker's logs. The stdout and stderr keys, on .pq.hopen_qfork and .pq.hopen_qspawn only, send the worker's
output to files: each is a file symbol, and it behaves exactly as \1/\2 run by the worker itself (appended, its
parent directories created), except that the worker does it first, before its startup script or any option can print.
So a launched worker's startup output, errors included, lands in the file, which a \1 sent after hopen is too late
for. Both keys may name the same file. A relative path is made absolute when the handle opens, so a worker restored
after this process \cds writes to the same file; a restore appends to it. Any other value is 'type, and a file the
worker cannot open fails the open ('fork, 'proc).
.pq.hopen_qspawn[`w2;`argv`stdout`stderr!(("init.q";"-s";"2");`:logs/w2.log;`:logs/w2.log)]
.pq.hopen_qfork[`w1;`stdout`stderr!(`:w1.out;`:w1.err)]
External tables and s). The process has ONE DuckDB database, the main instance, whose own handle is
`:pq:duckdb:main (listed by .pq.conns[], answered by .duckdb.main[]; hopen/hclose refuse the alias, and
q -duckdb path makes it a file). Every DuckDB alias is a catalog ATTACHed to it under the alias's name, shared by
path. A q global bound to a DuckDB table (myt:get `:pq:duckdb:al:dt/) is an external table: the data stays
in DuckDB, qSQL and SQL both work on it, and writes go through to DuckDB. At the moment of assignment it becomes a
same-named VIEW in main (a view, never a copy: `myt insert x writes through to DuckDB and the next s) reads
it); re-pointing the global re-points the view, any other value drops it, and `.ns.t is visible under its full
name. s)SELECT count(*) FROM myt is that view; s)SELECT * FROM al.dt reaches the alias's catalog qualified, and a
bare dt is NOT visible (a bare name in s) is a q name). After hclose of the alias the view errors on use, as
get of the coordinate does. \?duckdb, SQL and Storing tables in DuckDB
have the rest; a q peer has no external tables.
Loading and dropping: the sync law. Anything done THROUGH q is in sync by construction: assigning, re-pointing
or unbinding an external table, insert/upsert on one, hdel of a coordinate; one global-set seam, one provider
hook per verb. Anything done through the provider's own language is best effort, weighted to additions; nothing
prints, and nothing is ever unbound behind your back.
- Provider to q is a LOAD, and
\lis its verb.\l `:pq:duckdb:albinds every table and view of the alias's catalog as an external table under its own name, at the root (the\l dirlaw), later-wins on names: a globaldtyou had becomes the external table;\l `:pq:duckdb:al:dt/loads that one..duckdb.load[h;tables]is the verb form:()none,::all, else the names; it answers the names bound. The alias must be live ('conn): a load never opens a connection. A loaded external table is a q name ins)like any other.q -duckdb pathloads main's tables at startup likeq dir/, and so implies\l pq. s)after aCREATEorALTERbinds the NEW names of main's own catalog (CREATE TABLE … AS SELECTincluded); an existing global keeps its value.\lis the later-wins door; this one is add-only. Row writes need nothing (an external table reads live).s)DROP TABLE xleaves q'sxbound: it errors on use with DuckDB's own text.hdelon a coordinate drops the OBJECT:hdel `:pq:duckdb:al:dt/isDROP TABLE(orDROP VIEW, by what the name is);hdel `:pq:q:al:t/deletes the table on the peer. A q name bound to it stays bound and errors on use. The connection form is'domain, a dead alias'conn. Deleting the q name (delete dt from.) never drops the object, as with a splay: deleting `` t:get:db/t/ `` never removes the directory.
Compatibility principle¶
PeachQ should preserve established kdb+/q meanings of handles wherever practical.
The extended resource model is intended to explain and extend those behaviours, not redefine ordinary q file and IPC operations.
- Existing file
get/set,read0, andread1semantics remain existing file semantics. - Existing q IPC resource and opened-handle behaviour remains compatible.
- Existing splayed-table qSQL behaviour remains valid, including the trailing
/table-resource convention. - New table-resource behaviour extends the same idea to additional sources.
- Aliases live under the reserved
:pq:prefix (:pq:q:,:pq:qfork:,:pq:qspawn:,:pq:duckdb:), so they do not overload unrelated existing handle forms.
This gives existing q code the familiar compact syntax while allowing new code to treat external data sources consistently.