@qualithm/arrow-flight-client 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +258 -0
  3. package/dist/client/create-flight-client.d.ts +28 -0
  4. package/dist/client/create-flight-client.d.ts.map +1 -0
  5. package/dist/client/create-flight-client.js +29 -0
  6. package/dist/client/create-flight-client.js.map +1 -0
  7. package/dist/client/create-flight-sql-client.d.ts +28 -0
  8. package/dist/client/create-flight-sql-client.d.ts.map +1 -0
  9. package/dist/client/create-flight-sql-client.js +29 -0
  10. package/dist/client/create-flight-sql-client.js.map +1 -0
  11. package/dist/client/errors.d.ts +81 -0
  12. package/dist/client/errors.d.ts.map +1 -0
  13. package/dist/client/errors.js +106 -0
  14. package/dist/client/errors.js.map +1 -0
  15. package/dist/client/flight-client.d.ts +161 -0
  16. package/dist/client/flight-client.d.ts.map +1 -0
  17. package/dist/client/flight-client.js +403 -0
  18. package/dist/client/flight-client.js.map +1 -0
  19. package/dist/client/flight-sql-client.d.ts +348 -0
  20. package/dist/client/flight-sql-client.d.ts.map +1 -0
  21. package/dist/client/flight-sql-client.js +689 -0
  22. package/dist/client/flight-sql-client.js.map +1 -0
  23. package/dist/client/index.d.ts +11 -0
  24. package/dist/client/index.d.ts.map +1 -0
  25. package/dist/client/index.js +10 -0
  26. package/dist/client/index.js.map +1 -0
  27. package/dist/client/ipc.d.ts +131 -0
  28. package/dist/client/ipc.d.ts.map +1 -0
  29. package/dist/client/ipc.js +246 -0
  30. package/dist/client/ipc.js.map +1 -0
  31. package/dist/client/types.d.ts +153 -0
  32. package/dist/client/types.d.ts.map +1 -0
  33. package/dist/client/types.js +26 -0
  34. package/dist/client/types.js.map +1 -0
  35. package/dist/gen/arrow/flight/FlightSql_pb.d.ts +3173 -0
  36. package/dist/gen/arrow/flight/FlightSql_pb.d.ts.map +1 -0
  37. package/dist/gen/arrow/flight/FlightSql_pb.js +2258 -0
  38. package/dist/gen/arrow/flight/FlightSql_pb.js.map +1 -0
  39. package/dist/gen/arrow/flight/Flight_pb.d.ts +1159 -0
  40. package/dist/gen/arrow/flight/Flight_pb.d.ts.map +1 -0
  41. package/dist/gen/arrow/flight/Flight_pb.js +397 -0
  42. package/dist/gen/arrow/flight/Flight_pb.js.map +1 -0
  43. package/dist/index.d.ts +12 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +14 -0
  46. package/dist/index.js.map +1 -0
  47. package/package.json +96 -0
@@ -0,0 +1,689 @@
1
+ import { create, fromBinary, toBinary } from "@bufbuild/protobuf";
2
+ import { ActionBeginTransactionRequestSchema, ActionBeginTransactionResultSchema, ActionClosePreparedStatementRequestSchema, ActionCreatePreparedStatementRequestSchema, ActionCreatePreparedStatementResultSchema, ActionEndTransactionRequest_EndTransaction, ActionEndTransactionRequestSchema, CommandGetCatalogsSchema, CommandGetDbSchemasSchema, CommandGetPrimaryKeysSchema, CommandGetTablesSchema, CommandGetTableTypesSchema, CommandPreparedStatementQuerySchema, CommandPreparedStatementUpdateSchema, CommandStatementQuerySchema, CommandStatementUpdateSchema, DoPutUpdateResultSchema } from "../gen/arrow/flight/FlightSql_pb.js";
3
+ import { FlightError } from "./errors.js";
4
+ import { FlightClient } from "./flight-client.js";
5
+ import { decodeFlightDataStream, decodeFlightDataToTable, encodeRecordBatchesToFlightData } from "./ipc.js";
6
+ /**
7
+ * Flight SQL action type identifiers.
8
+ */
9
+ const FLIGHT_SQL_ACTIONS = {
10
+ CREATE_PREPARED_STATEMENT: "CreatePreparedStatement",
11
+ CLOSE_PREPARED_STATEMENT: "ClosePreparedStatement",
12
+ BEGIN_TRANSACTION: "BeginTransaction",
13
+ END_TRANSACTION: "EndTransaction"
14
+ };
15
+ /**
16
+ * Flight SQL type URL prefix for protobuf Any encoding.
17
+ */
18
+ const TYPE_URL_PREFIX = "type.googleapis.com/arrow.flight.protocol.sql";
19
+ /**
20
+ * Encodes a Flight SQL command as a protobuf Any message.
21
+ *
22
+ * Flight SQL commands must be wrapped in an Any message with a type_url
23
+ * that identifies the command type. The server uses this to dispatch
24
+ * to the correct handler.
25
+ *
26
+ * @param typeName - The command type name (e.g., "CommandStatementQuery")
27
+ * @param data - The serialised command bytes
28
+ * @returns The encoded Any message as Uint8Array
29
+ */
30
+ function packAny(typeName, data) {
31
+ const typeUrl = `${TYPE_URL_PREFIX}.${typeName}`;
32
+ const typeUrlBytes = new TextEncoder().encode(typeUrl);
33
+ const typeUrlLen = typeUrlBytes.length;
34
+ const dataLen = data.length;
35
+ // Calculate varint sizes
36
+ const typeUrlVarIntSize = varIntSize(typeUrlLen);
37
+ const dataVarIntSize = varIntSize(dataLen);
38
+ // Total size: 1 (tag) + varint + typeUrl + 1 (tag) + varint + data
39
+ const totalSize = 1 + typeUrlVarIntSize + typeUrlLen + 1 + dataVarIntSize + dataLen;
40
+ const buffer = new Uint8Array(totalSize);
41
+ let offset = 0;
42
+ // Field 1: type_url (tag = 0x0a = field 1, wire type 2)
43
+ buffer[offset++] = 0x0a;
44
+ offset = writeVarInt(buffer, offset, typeUrlLen);
45
+ buffer.set(typeUrlBytes, offset);
46
+ offset += typeUrlLen;
47
+ // Field 2: value (tag = 0x12 = field 2, wire type 2)
48
+ buffer[offset++] = 0x12;
49
+ offset = writeVarInt(buffer, offset, dataLen);
50
+ buffer.set(data, offset);
51
+ return buffer;
52
+ }
53
+ /**
54
+ * Calculates the size of a varint encoding.
55
+ */
56
+ function varIntSize(value) {
57
+ let size = 1;
58
+ while (value >= 0x80) {
59
+ value >>>= 7;
60
+ size++;
61
+ }
62
+ return size;
63
+ }
64
+ /**
65
+ * Writes a varint to a Uint8Array.
66
+ */
67
+ function writeVarInt(buffer, offset, value) {
68
+ while (value >= 0x80) {
69
+ buffer[offset++] = (value & 0x7f) | 0x80;
70
+ value >>>= 7;
71
+ }
72
+ buffer[offset++] = value;
73
+ return offset;
74
+ }
75
+ /**
76
+ * Reads a varint from a Uint8Array.
77
+ * Returns [value, newOffset].
78
+ */
79
+ function readVarInt(buffer, offset) {
80
+ let value = 0;
81
+ let shift = 0;
82
+ while (offset < buffer.length) {
83
+ const byte = buffer[offset++];
84
+ value |= (byte & 0x7f) << shift;
85
+ if ((byte & 0x80) === 0) {
86
+ break;
87
+ }
88
+ shift += 7;
89
+ }
90
+ return [value, offset];
91
+ }
92
+ /**
93
+ * Unpacks a protobuf Any message and returns the inner value.
94
+ *
95
+ * The Any message format is:
96
+ * - Field 1 (type_url): string
97
+ * - Field 2 (value): bytes
98
+ *
99
+ * @param data - The Any-encoded message
100
+ * @returns The inner value bytes
101
+ */
102
+ function unpackAny(data) {
103
+ let offset = 0;
104
+ while (offset < data.length) {
105
+ const tag = data[offset++];
106
+ const fieldNumber = tag >>> 3;
107
+ const wireType = tag & 0x07;
108
+ if (wireType === 2) {
109
+ // Length-delimited field
110
+ const [length, newOffset] = readVarInt(data, offset);
111
+ offset = newOffset;
112
+ if (fieldNumber === 2) {
113
+ // This is the value field - return it
114
+ return data.slice(offset, offset + length);
115
+ }
116
+ // Skip this field (e.g., type_url)
117
+ offset += length;
118
+ }
119
+ else {
120
+ // Unknown wire type - shouldn't happen for Any
121
+ break;
122
+ }
123
+ }
124
+ // No value field found - return original data (not wrapped in Any)
125
+ return data;
126
+ }
127
+ /**
128
+ * Arrow Flight SQL client for executing SQL queries and managing transactions.
129
+ *
130
+ * FlightSqlClient wraps FlightClient using composition, providing Flight SQL
131
+ * specific operations while delegating core Flight RPC to the underlying client.
132
+ *
133
+ * @example
134
+ * ```ts
135
+ * const client = new FlightSqlClient({ url: "https://flight.example.com:8815" })
136
+ *
137
+ * // Execute a query and get results as a Table
138
+ * const table = await client.query("SELECT * FROM users")
139
+ * console.log(`Got ${table.numRows} rows`)
140
+ *
141
+ * // Execute an update
142
+ * const result = await client.executeUpdate("INSERT INTO users (name) VALUES ('Alice')")
143
+ * console.log(`Inserted ${result.recordCount} rows`)
144
+ *
145
+ * // Use prepared statements
146
+ * const stmt = await client.prepare("SELECT * FROM users WHERE id = ?")
147
+ * // ... bind parameters and execute ...
148
+ * await client.closePreparedStatement(stmt)
149
+ *
150
+ * client.close()
151
+ * ```
152
+ */
153
+ export class FlightSqlClient {
154
+ #flight;
155
+ /**
156
+ * Creates a new FlightSqlClient.
157
+ *
158
+ * @param optionsOrClient - Either FlightClientOptions to create a new FlightClient,
159
+ * or an existing FlightClient instance to wrap.
160
+ */
161
+ constructor(optionsOrClient) {
162
+ // Duck-type check: FlightClient has getFlightInfo method, options don't
163
+ if (typeof optionsOrClient.getFlightInfo === "function" &&
164
+ typeof optionsOrClient.doGet === "function") {
165
+ this.#flight = optionsOrClient;
166
+ }
167
+ else {
168
+ this.#flight = new FlightClient(optionsOrClient);
169
+ }
170
+ }
171
+ /**
172
+ * The underlying Flight client.
173
+ * Use this for advanced Flight operations not covered by Flight SQL.
174
+ */
175
+ get flight() {
176
+ return this.#flight;
177
+ }
178
+ /**
179
+ * The base URL of the Flight server.
180
+ */
181
+ get url() {
182
+ return this.#flight.url;
183
+ }
184
+ /**
185
+ * Whether the client has been closed.
186
+ */
187
+ get closed() {
188
+ return this.#flight.closed;
189
+ }
190
+ /**
191
+ * Close the client and release resources.
192
+ */
193
+ close() {
194
+ this.#flight.close();
195
+ }
196
+ /**
197
+ * Authenticate with the server using configured credentials.
198
+ */
199
+ async authenticate() {
200
+ return this.#flight.authenticate();
201
+ }
202
+ // ── SQL Query Execution ─────────────────────────────────────────────
203
+ /**
204
+ * Execute a SQL query and return results as an Arrow Table.
205
+ *
206
+ * This is a convenience method that combines getFlightInfo and doGet
207
+ * to fetch all query results into memory.
208
+ *
209
+ * @param query - SQL query string
210
+ * @param options - Query execution options
211
+ * @returns Arrow Table containing query results
212
+ *
213
+ * @example
214
+ * ```ts
215
+ * const table = await client.query("SELECT * FROM users WHERE active = true")
216
+ * console.log(`Found ${table.numRows} active users`)
217
+ * ```
218
+ */
219
+ async query(query, options) {
220
+ const stream = this.queryStream(query, options);
221
+ return decodeFlightDataToTable(stream);
222
+ }
223
+ /**
224
+ * Execute a SQL query and return results as a stream of RecordBatches.
225
+ *
226
+ * Use this for large result sets to avoid loading all data into memory.
227
+ *
228
+ * @param query - SQL query string
229
+ * @param options - Query execution options
230
+ * @yields RecordBatch objects
231
+ *
232
+ * @example
233
+ * ```ts
234
+ * for await (const batch of client.queryBatches("SELECT * FROM large_table")) {
235
+ * processBatch(batch)
236
+ * }
237
+ * ```
238
+ */
239
+ async *queryBatches(query, options) {
240
+ const stream = this.queryStream(query, options);
241
+ for await (const batch of decodeFlightDataStream(stream)) {
242
+ yield batch;
243
+ }
244
+ }
245
+ /**
246
+ * Execute a SQL query and return the raw FlightData stream.
247
+ *
248
+ * This is the lowest-level query method, useful when you need
249
+ * access to raw Flight data or custom decoding.
250
+ *
251
+ * @param query - SQL query string
252
+ * @param options - Query execution options
253
+ * @yields FlightData messages
254
+ */
255
+ async *queryStream(query, options) {
256
+ // Create CommandStatementQuery
257
+ const command = create(CommandStatementQuerySchema, {
258
+ query,
259
+ transactionId: options?.transactionId
260
+ });
261
+ // Serialize command to bytes (wrapped in protobuf Any)
262
+ const cmdBytes = packAny("CommandStatementQuery", toBinary(CommandStatementQuerySchema, command));
263
+ // Get flight info for the query
264
+ const descriptor = { type: "cmd", cmd: cmdBytes };
265
+ const flightInfo = await this.#flight.getFlightInfo(descriptor);
266
+ // Fetch data from each endpoint
267
+ for (const endpoint of flightInfo.endpoint) {
268
+ if (!endpoint.ticket) {
269
+ continue;
270
+ }
271
+ // Yield FlightData from this endpoint
272
+ for await (const data of this.#flight.doGet(endpoint.ticket)) {
273
+ yield data;
274
+ }
275
+ }
276
+ }
277
+ /**
278
+ * Get FlightInfo for a SQL query without executing it.
279
+ *
280
+ * Use this to inspect the query plan, schema, or endpoints
281
+ * before fetching data.
282
+ *
283
+ * @param query - SQL query string
284
+ * @param options - Query execution options
285
+ * @returns FlightInfo describing the query results
286
+ */
287
+ async getQueryInfo(query, options) {
288
+ const command = create(CommandStatementQuerySchema, {
289
+ query,
290
+ transactionId: options?.transactionId
291
+ });
292
+ const cmdBytes = packAny("CommandStatementQuery", toBinary(CommandStatementQuerySchema, command));
293
+ return this.#flight.getFlightInfo({ type: "cmd", cmd: cmdBytes });
294
+ }
295
+ // ── SQL Update Execution ────────────────────────────────────────────
296
+ /**
297
+ * Execute a SQL update statement (INSERT, UPDATE, DELETE, DDL).
298
+ *
299
+ * @param query - SQL update statement
300
+ * @param options - Update execution options
301
+ * @returns Update result with affected row count
302
+ *
303
+ * @example
304
+ * ```ts
305
+ * const result = await client.executeUpdate("DELETE FROM users WHERE inactive = true")
306
+ * console.log(`Deleted ${result.recordCount} inactive users`)
307
+ * ```
308
+ */
309
+ async executeUpdate(query, options) {
310
+ // Create CommandStatementUpdate
311
+ const command = create(CommandStatementUpdateSchema, {
312
+ query,
313
+ transactionId: options?.transactionId
314
+ });
315
+ const cmdBytes = packAny("CommandStatementUpdate", toBinary(CommandStatementUpdateSchema, command));
316
+ // For updates, we use DoPut with the command as descriptor
317
+ // Create empty FlightData stream with descriptor
318
+ // eslint-disable-next-line @typescript-eslint/require-await
319
+ async function* emptyStream() {
320
+ yield {
321
+ flightDescriptor: {
322
+ type: 2, // CMD
323
+ cmd: cmdBytes,
324
+ path: []
325
+ },
326
+ dataHeader: new Uint8Array(),
327
+ dataBody: new Uint8Array(),
328
+ appMetadata: new Uint8Array()
329
+ };
330
+ }
331
+ // Execute DoPut and collect result
332
+ let updateResult;
333
+ for await (const result of this.#flight.doPut(emptyStream())) {
334
+ // Parse the result as DoPutUpdateResult
335
+ if (result.appMetadata.length > 0) {
336
+ updateResult = fromBinary(DoPutUpdateResultSchema, result.appMetadata);
337
+ }
338
+ }
339
+ return {
340
+ recordCount: updateResult?.recordCount ?? -1n
341
+ };
342
+ }
343
+ // ── Prepared Statements ─────────────────────────────────────────────
344
+ /**
345
+ * Prepare a SQL statement for repeated execution.
346
+ *
347
+ * @param query - SQL query with optional parameter placeholders
348
+ * @param transactionId - Optional transaction to associate with the statement
349
+ * @returns Prepared statement handle
350
+ *
351
+ * @example
352
+ * ```ts
353
+ * const stmt = await client.prepare("SELECT * FROM users WHERE id = ?")
354
+ * // Use executePrepared() to execute with parameters
355
+ * await client.closePreparedStatement(stmt)
356
+ * ```
357
+ */
358
+ async prepare(query, transactionId) {
359
+ const request = create(ActionCreatePreparedStatementRequestSchema, {
360
+ query,
361
+ transactionId
362
+ });
363
+ const requestBytes = packAny("ActionCreatePreparedStatementRequest", toBinary(ActionCreatePreparedStatementRequestSchema, request));
364
+ let result;
365
+ for await (const response of this.#flight.doAction({
366
+ type: FLIGHT_SQL_ACTIONS.CREATE_PREPARED_STATEMENT,
367
+ body: requestBytes
368
+ })) {
369
+ result = fromBinary(ActionCreatePreparedStatementResultSchema, unpackAny(response.body));
370
+ break;
371
+ }
372
+ if (!result) {
373
+ throw new FlightError("failed to create prepared statement: no response from server");
374
+ }
375
+ return {
376
+ handle: result.preparedStatementHandle,
377
+ datasetSchema: result.datasetSchema,
378
+ parameterSchema: result.parameterSchema
379
+ };
380
+ }
381
+ /**
382
+ * Execute a prepared query statement and return results as a Table.
383
+ *
384
+ * @param statement - Prepared statement from prepare()
385
+ * @returns Arrow Table containing query results
386
+ */
387
+ async executePrepared(statement) {
388
+ const stream = this.executePreparedStream(statement);
389
+ return decodeFlightDataToTable(stream);
390
+ }
391
+ /**
392
+ * Execute a prepared query statement and return results as a stream.
393
+ *
394
+ * @param statement - Prepared statement from prepare()
395
+ * @yields FlightData messages
396
+ */
397
+ async *executePreparedStream(statement) {
398
+ const command = create(CommandPreparedStatementQuerySchema, {
399
+ preparedStatementHandle: statement.handle
400
+ });
401
+ const cmdBytes = packAny("CommandPreparedStatementQuery", toBinary(CommandPreparedStatementQuerySchema, command));
402
+ const flightInfo = await this.#flight.getFlightInfo({ type: "cmd", cmd: cmdBytes });
403
+ for (const endpoint of flightInfo.endpoint) {
404
+ if (!endpoint.ticket) {
405
+ continue;
406
+ }
407
+ for await (const data of this.#flight.doGet(endpoint.ticket)) {
408
+ yield data;
409
+ }
410
+ }
411
+ }
412
+ /**
413
+ * Execute a prepared update statement and return affected row count.
414
+ *
415
+ * @param statement - Prepared statement from prepare()
416
+ * @param parameters - Optional parameter values as RecordBatches
417
+ * @returns Update result with affected row count
418
+ */
419
+ async executePreparedUpdate(statement, parameters) {
420
+ const command = create(CommandPreparedStatementUpdateSchema, {
421
+ preparedStatementHandle: statement.handle
422
+ });
423
+ const cmdBytes = packAny("CommandPreparedStatementUpdate", toBinary(CommandPreparedStatementUpdateSchema, command));
424
+ // If parameters provided, send them via DoPut
425
+ // Otherwise send empty stream
426
+ let dataStream;
427
+ if (parameters) {
428
+ // We need a schema to encode - for now we'll collect batches and use first batch's schema
429
+ const batchArray = [];
430
+ for await (const batch of parameters) {
431
+ batchArray.push(batch);
432
+ }
433
+ if (batchArray.length > 0) {
434
+ const { schema } = batchArray[0];
435
+ // Wrap encoded data with descriptor
436
+ async function* withDescriptor(encoded) {
437
+ let first = true;
438
+ for await (const data of encoded) {
439
+ if (first) {
440
+ yield {
441
+ ...data,
442
+ flightDescriptor: { type: 2, cmd: cmdBytes, path: [] }
443
+ };
444
+ first = false;
445
+ }
446
+ else {
447
+ yield data;
448
+ }
449
+ }
450
+ }
451
+ dataStream = withDescriptor(encodeRecordBatchesToFlightData(batchArray, schema));
452
+ }
453
+ else {
454
+ dataStream = this.#createEmptyStream(cmdBytes);
455
+ }
456
+ }
457
+ else {
458
+ dataStream = this.#createEmptyStream(cmdBytes);
459
+ }
460
+ let updateResult;
461
+ for await (const result of this.#flight.doPut(dataStream)) {
462
+ if (result.appMetadata.length > 0) {
463
+ updateResult = fromBinary(DoPutUpdateResultSchema, result.appMetadata);
464
+ }
465
+ }
466
+ return {
467
+ recordCount: updateResult?.recordCount ?? -1n
468
+ };
469
+ }
470
+ /**
471
+ * Close a prepared statement and release server resources.
472
+ *
473
+ * @param statement - Prepared statement to close
474
+ */
475
+ async closePreparedStatement(statement) {
476
+ const request = create(ActionClosePreparedStatementRequestSchema, {
477
+ preparedStatementHandle: statement.handle
478
+ });
479
+ const requestBytes = packAny("ActionClosePreparedStatementRequest", toBinary(ActionClosePreparedStatementRequestSchema, request));
480
+ // Fire and forget - no response expected
481
+ for await (const _ of this.#flight.doAction({
482
+ type: FLIGHT_SQL_ACTIONS.CLOSE_PREPARED_STATEMENT,
483
+ body: requestBytes
484
+ })) {
485
+ // Consume stream
486
+ }
487
+ }
488
+ // ── Transactions ────────────────────────────────────────────────────
489
+ /**
490
+ * Begin a new transaction.
491
+ *
492
+ * @returns Transaction handle
493
+ *
494
+ * @example
495
+ * ```ts
496
+ * const txn = await client.beginTransaction()
497
+ * try {
498
+ * await client.executeUpdate("INSERT INTO users ...", { transactionId: txn.id })
499
+ * await client.executeUpdate("INSERT INTO logs ...", { transactionId: txn.id })
500
+ * await client.commit(txn)
501
+ * } catch (e) {
502
+ * await client.rollback(txn)
503
+ * throw e
504
+ * }
505
+ * ```
506
+ */
507
+ async beginTransaction() {
508
+ const request = create(ActionBeginTransactionRequestSchema, {});
509
+ const requestBytes = packAny("ActionBeginTransactionRequest", toBinary(ActionBeginTransactionRequestSchema, request));
510
+ let result;
511
+ for await (const response of this.#flight.doAction({
512
+ type: FLIGHT_SQL_ACTIONS.BEGIN_TRANSACTION,
513
+ body: requestBytes
514
+ })) {
515
+ result = fromBinary(ActionBeginTransactionResultSchema, unpackAny(response.body));
516
+ break;
517
+ }
518
+ if (!result) {
519
+ throw new FlightError("failed to begin transaction: no response from server");
520
+ }
521
+ return {
522
+ id: result.transactionId
523
+ };
524
+ }
525
+ /**
526
+ * Commit a transaction.
527
+ *
528
+ * @param transaction - Transaction to commit
529
+ */
530
+ async commit(transaction) {
531
+ await this.#endTransaction(transaction, ActionEndTransactionRequest_EndTransaction.COMMIT);
532
+ }
533
+ /**
534
+ * Roll back a transaction.
535
+ *
536
+ * @param transaction - Transaction to roll back
537
+ */
538
+ async rollback(transaction) {
539
+ await this.#endTransaction(transaction, ActionEndTransactionRequest_EndTransaction.ROLLBACK);
540
+ }
541
+ // ── Database Metadata ───────────────────────────────────────────────
542
+ /**
543
+ * Get the list of catalogs in the database.
544
+ *
545
+ * @returns Arrow Table with catalog_name column
546
+ *
547
+ * @example
548
+ * ```ts
549
+ * const catalogs = await client.getCatalogs()
550
+ * for (const row of catalogs) {
551
+ * console.log(row.catalog_name)
552
+ * }
553
+ * ```
554
+ */
555
+ async getCatalogs() {
556
+ const command = create(CommandGetCatalogsSchema, {});
557
+ const cmdBytes = packAny("CommandGetCatalogs", toBinary(CommandGetCatalogsSchema, command));
558
+ return this.#executeMetadataQuery(cmdBytes);
559
+ }
560
+ /**
561
+ * Get the list of database schemas.
562
+ *
563
+ * @param options - Filter options
564
+ * @returns Arrow Table with catalog_name and db_schema_name columns
565
+ *
566
+ * @example
567
+ * ```ts
568
+ * const schemas = await client.getDbSchemas({ catalog: "my_catalog" })
569
+ * ```
570
+ */
571
+ async getDbSchemas(options) {
572
+ const command = create(CommandGetDbSchemasSchema, {
573
+ catalog: options?.catalog,
574
+ dbSchemaFilterPattern: options?.dbSchemaFilterPattern
575
+ });
576
+ const cmdBytes = packAny("CommandGetDbSchemas", toBinary(CommandGetDbSchemasSchema, command));
577
+ return this.#executeMetadataQuery(cmdBytes);
578
+ }
579
+ /**
580
+ * Get the list of tables.
581
+ *
582
+ * @param options - Filter options
583
+ * @returns Arrow Table with catalog_name, db_schema_name, table_name, table_type columns
584
+ *
585
+ * @example
586
+ * ```ts
587
+ * const tables = await client.getTables({ tableTypes: ["TABLE", "VIEW"] })
588
+ * ```
589
+ */
590
+ async getTables(options) {
591
+ const command = create(CommandGetTablesSchema, {
592
+ catalog: options?.catalog,
593
+ dbSchemaFilterPattern: options?.dbSchemaFilterPattern,
594
+ tableNameFilterPattern: options?.tableNameFilterPattern,
595
+ tableTypes: options?.tableTypes ?? [],
596
+ includeSchema: options?.includeSchema ?? false
597
+ });
598
+ const cmdBytes = packAny("CommandGetTables", toBinary(CommandGetTablesSchema, command));
599
+ return this.#executeMetadataQuery(cmdBytes);
600
+ }
601
+ /**
602
+ * Get the list of table types supported by the server.
603
+ *
604
+ * @returns Arrow Table with table_type column
605
+ *
606
+ * @example
607
+ * ```ts
608
+ * const tableTypes = await client.getTableTypes()
609
+ * // Common types: TABLE, VIEW, SYSTEM TABLE
610
+ * ```
611
+ */
612
+ async getTableTypes() {
613
+ const command = create(CommandGetTableTypesSchema, {});
614
+ const cmdBytes = packAny("CommandGetTableTypes", toBinary(CommandGetTableTypesSchema, command));
615
+ return this.#executeMetadataQuery(cmdBytes);
616
+ }
617
+ /**
618
+ * Get the primary keys for a table.
619
+ *
620
+ * @param table - Table name (required)
621
+ * @param options - Additional filter options
622
+ * @returns Arrow Table with catalog_name, db_schema_name, table_name, column_name, key_name, key_sequence columns
623
+ *
624
+ * @example
625
+ * ```ts
626
+ * const keys = await client.getPrimaryKeys("users", { catalog: "my_db" })
627
+ * ```
628
+ */
629
+ async getPrimaryKeys(table, options) {
630
+ const command = create(CommandGetPrimaryKeysSchema, {
631
+ table,
632
+ catalog: options?.catalog,
633
+ dbSchema: options?.dbSchema
634
+ });
635
+ const cmdBytes = packAny("CommandGetPrimaryKeys", toBinary(CommandGetPrimaryKeysSchema, command));
636
+ return this.#executeMetadataQuery(cmdBytes);
637
+ }
638
+ // ── Private Helpers ─────────────────────────────────────────────────
639
+ /**
640
+ * Execute a metadata query and return the results as a Table.
641
+ */
642
+ async #executeMetadataQuery(cmdBytes) {
643
+ const descriptor = { type: "cmd", cmd: cmdBytes };
644
+ const flightInfo = await this.#flight.getFlightInfo(descriptor);
645
+ // Collect all data from all endpoints
646
+ const allData = [];
647
+ for (const endpoint of flightInfo.endpoint) {
648
+ if (!endpoint.ticket) {
649
+ continue;
650
+ }
651
+ for await (const data of this.#flight.doGet(endpoint.ticket)) {
652
+ allData.push(data);
653
+ }
654
+ }
655
+ // Convert to async iterable for decoding
656
+ // eslint-disable-next-line @typescript-eslint/require-await
657
+ async function* toAsyncIterable(items) {
658
+ for (const item of items) {
659
+ yield item;
660
+ }
661
+ }
662
+ return decodeFlightDataToTable(toAsyncIterable(allData));
663
+ }
664
+ /** Commits or rolls back a transaction using EndTransaction action. */
665
+ async #endTransaction(transaction, action) {
666
+ const request = create(ActionEndTransactionRequestSchema, {
667
+ transactionId: transaction.id,
668
+ action
669
+ });
670
+ const requestBytes = packAny("ActionEndTransactionRequest", toBinary(ActionEndTransactionRequestSchema, request));
671
+ for await (const _ of this.#flight.doAction({
672
+ type: FLIGHT_SQL_ACTIONS.END_TRANSACTION,
673
+ body: requestBytes
674
+ })) {
675
+ // Consume stream
676
+ }
677
+ }
678
+ /** Creates an empty FlightData stream with only a descriptor (for updates). */
679
+ // eslint-disable-next-line @typescript-eslint/require-await
680
+ async *#createEmptyStream(cmdBytes) {
681
+ yield {
682
+ flightDescriptor: { type: 2, cmd: cmdBytes, path: [] },
683
+ dataHeader: new Uint8Array(),
684
+ dataBody: new Uint8Array(),
685
+ appMetadata: new Uint8Array()
686
+ };
687
+ }
688
+ }
689
+ //# sourceMappingURL=flight-sql-client.js.map