@useairfoil/flight 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.
- package/README.md +1 -0
- package/dist/proto/Flight.proto +524 -0
- package/dist/proto/FlightSql.proto +1925 -0
- package/dist/proto/any.proto +162 -0
- package/dist/src/index.d.ts +749 -0
- package/dist/src/index.js +4133 -0
- package/dist/src/index.js.map +1 -0
- package/dist/test/index.d.ts +29 -0
- package/dist/test/index.js +79 -0
- package/dist/test/index.js.map +1 -0
- package/package.json +58 -0
- package/src/arrow-flight-sql.ts +121 -0
- package/src/arrow-flight.ts +149 -0
- package/src/arrow-utils.ts +30 -0
- package/src/flight-data-encoder.ts +108 -0
- package/src/index.ts +16 -0
- package/src/proto/Flight.ts +2627 -0
- package/src/proto/FlightSql.ts +6592 -0
- package/src/proto/any.ts +269 -0
- package/src/proto/google/protobuf/descriptor.ts +7101 -0
- package/src/proto/google/protobuf/timestamp.ts +231 -0
- package/src/proto/typeRegistry.ts +29 -0
- package/src/proto-utils.ts +39 -0
- package/src/record-batch-decoder.ts +401 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { StartedTestContainer } from "testcontainers";
|
|
2
|
+
import { Context, Effect, Layer } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region test/wings-container.d.ts
|
|
5
|
+
declare class WingsContainer {
|
|
6
|
+
private container;
|
|
7
|
+
start(): Promise<WingsContainer>;
|
|
8
|
+
stop(): Promise<void>;
|
|
9
|
+
getGrpcPort(): number;
|
|
10
|
+
getHttpPort(): number;
|
|
11
|
+
getGrpcHost(): string;
|
|
12
|
+
getHttpHost(): string;
|
|
13
|
+
private getHost;
|
|
14
|
+
getContainer(): StartedTestContainer;
|
|
15
|
+
}
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region test/wings-container.effect.d.ts
|
|
18
|
+
declare const EffectWingsContainer_base: Context.TagClass<EffectWingsContainer, "EffectWingsContainer", {
|
|
19
|
+
readonly getGrpcPort: Effect.Effect<number>;
|
|
20
|
+
readonly getHttpPort: Effect.Effect<number>;
|
|
21
|
+
readonly getGrpcHost: Effect.Effect<string>;
|
|
22
|
+
readonly getHttpHost: Effect.Effect<string>;
|
|
23
|
+
readonly getContainer: Effect.Effect<StartedTestContainer>;
|
|
24
|
+
}>;
|
|
25
|
+
declare class EffectWingsContainer extends EffectWingsContainer_base {}
|
|
26
|
+
declare const EffectWingsContainerLive: Layer.Layer<EffectWingsContainer, Error, never>;
|
|
27
|
+
//#endregion
|
|
28
|
+
export { EffectWingsContainer, EffectWingsContainerLive, WingsContainer };
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { GenericContainer, Wait } from "testcontainers";
|
|
2
|
+
import { Context, Effect, Layer, Scope } from "effect";
|
|
3
|
+
|
|
4
|
+
//#region test/wings-container.ts
|
|
5
|
+
var WingsContainer = class {
|
|
6
|
+
container = null;
|
|
7
|
+
async start() {
|
|
8
|
+
this.container = await new GenericContainer("docker.useairfoil.com/airfoil/wings:latest").withCommand([
|
|
9
|
+
"dev",
|
|
10
|
+
"--http-address=0.0.0.0:7780",
|
|
11
|
+
"--metadata-address=0.0.0.0:7777"
|
|
12
|
+
]).withExposedPorts(7777, 7780).withTmpFs({ "/tmp": "rw" }).withWaitStrategy(Wait.forLogMessage(/gRPC server listening on 0\.0\.0\.0:7777/)).withStartupTimeout(6e4).start();
|
|
13
|
+
return this;
|
|
14
|
+
}
|
|
15
|
+
async stop() {
|
|
16
|
+
if (this.container) {
|
|
17
|
+
await this.container.stop();
|
|
18
|
+
this.container = null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
getGrpcPort() {
|
|
22
|
+
if (!this.container) throw new Error("Container not started");
|
|
23
|
+
return this.container.getMappedPort(7777);
|
|
24
|
+
}
|
|
25
|
+
getHttpPort() {
|
|
26
|
+
if (!this.container) throw new Error("Container not started");
|
|
27
|
+
return this.container.getMappedPort(7780);
|
|
28
|
+
}
|
|
29
|
+
getGrpcHost() {
|
|
30
|
+
return `${this.getHost()}:${this.getGrpcPort()}`;
|
|
31
|
+
}
|
|
32
|
+
getHttpHost() {
|
|
33
|
+
return `${this.getHost()}:${this.getHttpPort()}`;
|
|
34
|
+
}
|
|
35
|
+
getHost() {
|
|
36
|
+
if (!this.container) throw new Error("Container not started");
|
|
37
|
+
return this.container.getHost();
|
|
38
|
+
}
|
|
39
|
+
getContainer() {
|
|
40
|
+
if (!this.container) throw new Error("Container not started");
|
|
41
|
+
return this.container;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
//#endregion
|
|
46
|
+
//#region test/wings-container.effect.ts
|
|
47
|
+
var EffectWingsContainer = class extends Context.Tag("EffectWingsContainer")() {};
|
|
48
|
+
const EffectWingsContainerLive = Layer.scoped(EffectWingsContainer, Effect.gen(function* () {
|
|
49
|
+
const scope = yield* Scope.Scope;
|
|
50
|
+
const container = yield* Effect.tryPromise({
|
|
51
|
+
try: () => new GenericContainer("docker.useairfoil.com/airfoil/wings:latest").withCommand([
|
|
52
|
+
"dev",
|
|
53
|
+
"--http-address=0.0.0.0:7780",
|
|
54
|
+
"--metadata-address=0.0.0.0:7777"
|
|
55
|
+
]).withExposedPorts(7777, 7780).withTmpFs({ "/tmp": "rw" }).withWaitStrategy(Wait.forLogMessage(/gRPC server listening on 0\.0\.0\.0:7777/)).withStartupTimeout(6e4).start(),
|
|
56
|
+
catch: (error) => /* @__PURE__ */ new Error(`Failed to start container: ${error}`)
|
|
57
|
+
});
|
|
58
|
+
yield* Scope.addFinalizer(scope, Effect.tryPromise({
|
|
59
|
+
try: () => container.stop(),
|
|
60
|
+
catch: (error) => /* @__PURE__ */ new Error(`Failed to stop container: ${error}`)
|
|
61
|
+
}).pipe(Effect.tap(() => Effect.log("Container stopped successfully")), Effect.catchAll((error) => Effect.log(`Error stopping container: ${error}`))));
|
|
62
|
+
return {
|
|
63
|
+
getGrpcPort: Effect.sync(() => container.getMappedPort(7777)),
|
|
64
|
+
getHttpPort: Effect.sync(() => container.getMappedPort(7780)),
|
|
65
|
+
getGrpcHost: Effect.gen(function* () {
|
|
66
|
+
const port = yield* Effect.sync(() => container.getMappedPort(7777));
|
|
67
|
+
return `${yield* Effect.sync(() => container.getHost())}:${port}`;
|
|
68
|
+
}),
|
|
69
|
+
getHttpHost: Effect.gen(function* () {
|
|
70
|
+
const port = yield* Effect.sync(() => container.getMappedPort(7780));
|
|
71
|
+
return `${yield* Effect.sync(() => container.getHost())}:${port}`;
|
|
72
|
+
}),
|
|
73
|
+
getContainer: Effect.succeed(container)
|
|
74
|
+
};
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
//#endregion
|
|
78
|
+
export { EffectWingsContainer, EffectWingsContainerLive, WingsContainer };
|
|
79
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../test/wings-container.ts","../../test/wings-container.effect.ts"],"sourcesContent":["import {\n GenericContainer,\n type StartedTestContainer,\n Wait,\n} from \"testcontainers\";\n\nexport class WingsContainer {\n private container: StartedTestContainer | null = null;\n\n async start(): Promise<WingsContainer> {\n const container = new GenericContainer(\n \"docker.useairfoil.com/airfoil/wings:latest\",\n )\n .withCommand([\n \"dev\",\n \"--http-address=0.0.0.0:7780\",\n \"--metadata-address=0.0.0.0:7777\",\n ])\n .withExposedPorts(7777, 7780)\n .withTmpFs({ \"/tmp\": \"rw\" })\n .withWaitStrategy(\n Wait.forLogMessage(/gRPC server listening on 0\\.0\\.0\\.0:7777/),\n )\n .withStartupTimeout(60_000);\n\n this.container = await container.start();\n return this;\n }\n\n async stop(): Promise<void> {\n if (this.container) {\n await this.container.stop();\n this.container = null;\n }\n }\n\n getGrpcPort(): number {\n if (!this.container) {\n throw new Error(\"Container not started\");\n }\n return this.container.getMappedPort(7777);\n }\n\n getHttpPort(): number {\n if (!this.container) {\n throw new Error(\"Container not started\");\n }\n return this.container.getMappedPort(7780);\n }\n\n getGrpcHost(): string {\n return `${this.getHost()}:${this.getGrpcPort()}`;\n }\n\n getHttpHost(): string {\n return `${this.getHost()}:${this.getHttpPort()}`;\n }\n\n private getHost(): string {\n if (!this.container) {\n throw new Error(\"Container not started\");\n }\n return this.container.getHost();\n }\n\n getContainer(): StartedTestContainer {\n if (!this.container) {\n throw new Error(\"Container not started\");\n }\n return this.container;\n }\n}\n","import { Context, Effect, Layer, Scope } from \"effect\";\nimport {\n GenericContainer,\n type StartedTestContainer,\n Wait,\n} from \"testcontainers\";\n\nexport class EffectWingsContainer extends Context.Tag(\"EffectWingsContainer\")<\n EffectWingsContainer,\n {\n readonly getGrpcPort: Effect.Effect<number>;\n readonly getHttpPort: Effect.Effect<number>;\n readonly getGrpcHost: Effect.Effect<string>;\n readonly getHttpHost: Effect.Effect<string>;\n readonly getContainer: Effect.Effect<StartedTestContainer>;\n }\n>() {}\n\nexport const EffectWingsContainerLive = Layer.scoped(\n EffectWingsContainer,\n Effect.gen(function* () {\n const scope = yield* Scope.Scope;\n\n const container = yield* Effect.tryPromise({\n try: () =>\n new GenericContainer(\"docker.useairfoil.com/airfoil/wings:latest\")\n .withCommand([\n \"dev\",\n \"--http-address=0.0.0.0:7780\",\n \"--metadata-address=0.0.0.0:7777\",\n ])\n .withExposedPorts(7777, 7780)\n .withTmpFs({ \"/tmp\": \"rw\" })\n .withWaitStrategy(\n Wait.forLogMessage(/gRPC server listening on 0\\.0\\.0\\.0:7777/),\n )\n .withStartupTimeout(60_000)\n .start(),\n catch: (error) => new Error(`Failed to start container: ${error}`),\n });\n\n yield* Scope.addFinalizer(\n scope,\n Effect.tryPromise({\n try: () => container.stop(),\n catch: (error) => new Error(`Failed to stop container: ${error}`),\n }).pipe(\n Effect.tap(() => Effect.log(\"Container stopped successfully\")),\n Effect.catchAll((error) =>\n Effect.log(`Error stopping container: ${error}`),\n ),\n ),\n );\n\n return {\n getGrpcPort: Effect.sync(() => container.getMappedPort(7777)),\n getHttpPort: Effect.sync(() => container.getMappedPort(7780)),\n getGrpcHost: Effect.gen(function* () {\n const port = yield* Effect.sync(() => container.getMappedPort(7777));\n const host = yield* Effect.sync(() => container.getHost());\n return `${host}:${port}`;\n }),\n getHttpHost: Effect.gen(function* () {\n const port = yield* Effect.sync(() => container.getMappedPort(7780));\n const host = yield* Effect.sync(() => container.getHost());\n return `${host}:${port}`;\n }),\n getContainer: Effect.succeed(container),\n };\n }),\n);\n"],"mappings":";;;;AAMA,IAAa,iBAAb,MAA4B;CAC1B,AAAQ,YAAyC;CAEjD,MAAM,QAAiC;AAgBrC,OAAK,YAAY,MAfC,IAAI,iBACpB,6CACD,CACE,YAAY;GACX;GACA;GACA;GACD,CAAC,CACD,iBAAiB,MAAM,KAAK,CAC5B,UAAU,EAAE,QAAQ,MAAM,CAAC,CAC3B,iBACC,KAAK,cAAc,2CAA2C,CAC/D,CACA,mBAAmB,IAAO,CAEI,OAAO;AACxC,SAAO;;CAGT,MAAM,OAAsB;AAC1B,MAAI,KAAK,WAAW;AAClB,SAAM,KAAK,UAAU,MAAM;AAC3B,QAAK,YAAY;;;CAIrB,cAAsB;AACpB,MAAI,CAAC,KAAK,UACR,OAAM,IAAI,MAAM,wBAAwB;AAE1C,SAAO,KAAK,UAAU,cAAc,KAAK;;CAG3C,cAAsB;AACpB,MAAI,CAAC,KAAK,UACR,OAAM,IAAI,MAAM,wBAAwB;AAE1C,SAAO,KAAK,UAAU,cAAc,KAAK;;CAG3C,cAAsB;AACpB,SAAO,GAAG,KAAK,SAAS,CAAC,GAAG,KAAK,aAAa;;CAGhD,cAAsB;AACpB,SAAO,GAAG,KAAK,SAAS,CAAC,GAAG,KAAK,aAAa;;CAGhD,AAAQ,UAAkB;AACxB,MAAI,CAAC,KAAK,UACR,OAAM,IAAI,MAAM,wBAAwB;AAE1C,SAAO,KAAK,UAAU,SAAS;;CAGjC,eAAqC;AACnC,MAAI,CAAC,KAAK,UACR,OAAM,IAAI,MAAM,wBAAwB;AAE1C,SAAO,KAAK;;;;;;AC9DhB,IAAa,uBAAb,cAA0C,QAAQ,IAAI,uBAAuB,EAS1E,CAAC;AAEJ,MAAa,2BAA2B,MAAM,OAC5C,sBACA,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,OAAO,MAAM;CAE3B,MAAM,YAAY,OAAO,OAAO,WAAW;EACzC,WACE,IAAI,iBAAiB,6CAA6C,CAC/D,YAAY;GACX;GACA;GACA;GACD,CAAC,CACD,iBAAiB,MAAM,KAAK,CAC5B,UAAU,EAAE,QAAQ,MAAM,CAAC,CAC3B,iBACC,KAAK,cAAc,2CAA2C,CAC/D,CACA,mBAAmB,IAAO,CAC1B,OAAO;EACZ,QAAQ,0BAAU,IAAI,MAAM,8BAA8B,QAAQ;EACnE,CAAC;AAEF,QAAO,MAAM,aACX,OACA,OAAO,WAAW;EAChB,WAAW,UAAU,MAAM;EAC3B,QAAQ,0BAAU,IAAI,MAAM,6BAA6B,QAAQ;EAClE,CAAC,CAAC,KACD,OAAO,UAAU,OAAO,IAAI,iCAAiC,CAAC,EAC9D,OAAO,UAAU,UACf,OAAO,IAAI,6BAA6B,QAAQ,CACjD,CACF,CACF;AAED,QAAO;EACL,aAAa,OAAO,WAAW,UAAU,cAAc,KAAK,CAAC;EAC7D,aAAa,OAAO,WAAW,UAAU,cAAc,KAAK,CAAC;EAC7D,aAAa,OAAO,IAAI,aAAa;GACnC,MAAM,OAAO,OAAO,OAAO,WAAW,UAAU,cAAc,KAAK,CAAC;AAEpE,UAAO,GADM,OAAO,OAAO,WAAW,UAAU,SAAS,CAAC,CAC3C,GAAG;IAClB;EACF,aAAa,OAAO,IAAI,aAAa;GACnC,MAAM,OAAO,OAAO,OAAO,WAAW,UAAU,cAAc,KAAK,CAAC;AAEpE,UAAO,GADM,OAAO,OAAO,WAAW,UAAU,SAAS,CAAC,CAC3C,GAAG;IAClB;EACF,cAAc,OAAO,QAAQ,UAAU;EACxC;EACD,CACH"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@useairfoil/flight",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"files": [
|
|
6
|
+
"dist",
|
|
7
|
+
"src",
|
|
8
|
+
"README.md"
|
|
9
|
+
],
|
|
10
|
+
"main": "./dist/src/index.js",
|
|
11
|
+
"types": "./dist/src/index.d.ts",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/src/index.d.ts",
|
|
15
|
+
"import": "./dist/src/index.js",
|
|
16
|
+
"default": "./dist/src/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./test": {
|
|
19
|
+
"types": "./dist/test/index.d.ts",
|
|
20
|
+
"import": "./dist/test/index.js",
|
|
21
|
+
"default": "./dist/test/index.js"
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsdown",
|
|
26
|
+
"build:proto": "buf generate proto",
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"format": "biome format --write .",
|
|
29
|
+
"lint": "biome check .",
|
|
30
|
+
"lint:fix": "biome check . --write",
|
|
31
|
+
"test": "vitest",
|
|
32
|
+
"test:ci": "vitest run"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"effect": "^3.19.15"
|
|
36
|
+
},
|
|
37
|
+
"optionalDependencies": {
|
|
38
|
+
"testcontainers": "^11.11.0"
|
|
39
|
+
},
|
|
40
|
+
"devDependencies": {
|
|
41
|
+
"@bufbuild/buf": "^1.57.2",
|
|
42
|
+
"@effect/vitest": "^0.27.0",
|
|
43
|
+
"@rollup/plugin-typescript": "^12.1.4",
|
|
44
|
+
"effect": "^3.19.15",
|
|
45
|
+
"testcontainers": "^11.11.0",
|
|
46
|
+
"tsdown": "^0.15.6",
|
|
47
|
+
"vitest": "^3.2.4"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"@bufbuild/protobuf": "^2.9.0",
|
|
51
|
+
"apache-arrow": "^21.1.0",
|
|
52
|
+
"flatbuffers": "^25.9.23",
|
|
53
|
+
"iter-ops": "^3.5.0",
|
|
54
|
+
"long": "^5.3.2",
|
|
55
|
+
"nice-grpc": "^2.1.13",
|
|
56
|
+
"nice-grpc-common": "^2.0.2"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { CallOptions } from "nice-grpc";
|
|
2
|
+
import { ArrowFlightClient } from "./arrow-flight";
|
|
3
|
+
import { Any } from "./proto/any";
|
|
4
|
+
import {
|
|
5
|
+
FlightDescriptor,
|
|
6
|
+
FlightDescriptor_DescriptorType,
|
|
7
|
+
type FlightInfo,
|
|
8
|
+
type FlightServiceDefinition,
|
|
9
|
+
} from "./proto/Flight";
|
|
10
|
+
import {
|
|
11
|
+
CommandGetCatalogs,
|
|
12
|
+
CommandGetDbSchemas,
|
|
13
|
+
CommandGetTables,
|
|
14
|
+
CommandGetTableTypes,
|
|
15
|
+
CommandStatementQuery,
|
|
16
|
+
} from "./proto/FlightSql";
|
|
17
|
+
import type {
|
|
18
|
+
ClientOptions,
|
|
19
|
+
HostOrChannel,
|
|
20
|
+
RemoveTypeUrl,
|
|
21
|
+
} from "./proto-utils";
|
|
22
|
+
|
|
23
|
+
export class ArrowFlightSqlClient {
|
|
24
|
+
private inner: ArrowFlightClient;
|
|
25
|
+
|
|
26
|
+
constructor(
|
|
27
|
+
config: HostOrChannel,
|
|
28
|
+
options: ClientOptions<FlightServiceDefinition> = {},
|
|
29
|
+
) {
|
|
30
|
+
this.inner = new ArrowFlightClient(config, options);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
executeFlightInfo(request: FlightInfo, options?: CallOptions) {
|
|
34
|
+
return this.inner.executeFlightInfo(request, options);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async getCatalogs(
|
|
38
|
+
request: RemoveTypeUrl<CommandGetCatalogs>,
|
|
39
|
+
options?: CallOptions,
|
|
40
|
+
) {
|
|
41
|
+
const descriptor = createCommandDescriptor(
|
|
42
|
+
CommandGetCatalogs.$type,
|
|
43
|
+
CommandGetCatalogs.encode({
|
|
44
|
+
$type: CommandGetCatalogs.$type,
|
|
45
|
+
...request,
|
|
46
|
+
}).finish(),
|
|
47
|
+
);
|
|
48
|
+
return this.inner.getFlightInfo(descriptor, options);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async getDbSchemas(
|
|
52
|
+
request: RemoveTypeUrl<CommandGetDbSchemas>,
|
|
53
|
+
options?: CallOptions,
|
|
54
|
+
) {
|
|
55
|
+
const descriptor = createCommandDescriptor(
|
|
56
|
+
CommandGetDbSchemas.$type,
|
|
57
|
+
CommandGetDbSchemas.encode({
|
|
58
|
+
$type: CommandGetDbSchemas.$type,
|
|
59
|
+
...request,
|
|
60
|
+
}).finish(),
|
|
61
|
+
);
|
|
62
|
+
return this.inner.getFlightInfo(descriptor, options);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async getTables(
|
|
66
|
+
request: RemoveTypeUrl<CommandGetTables>,
|
|
67
|
+
options?: CallOptions,
|
|
68
|
+
) {
|
|
69
|
+
const descriptor = createCommandDescriptor(
|
|
70
|
+
CommandGetTables.$type,
|
|
71
|
+
CommandGetTables.encode({
|
|
72
|
+
$type: CommandGetTables.$type,
|
|
73
|
+
...request,
|
|
74
|
+
}).finish(),
|
|
75
|
+
);
|
|
76
|
+
return this.inner.getFlightInfo(descriptor, options);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async getTableTypes(
|
|
80
|
+
request: RemoveTypeUrl<CommandGetTableTypes>,
|
|
81
|
+
options?: CallOptions,
|
|
82
|
+
) {
|
|
83
|
+
const descriptor = createCommandDescriptor(
|
|
84
|
+
CommandGetTableTypes.$type,
|
|
85
|
+
CommandGetTableTypes.encode({
|
|
86
|
+
$type: CommandGetTableTypes.$type,
|
|
87
|
+
...request,
|
|
88
|
+
}).finish(),
|
|
89
|
+
);
|
|
90
|
+
return this.inner.getFlightInfo(descriptor, options);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async executeQuery(
|
|
94
|
+
request: RemoveTypeUrl<CommandStatementQuery>,
|
|
95
|
+
options?: CallOptions,
|
|
96
|
+
) {
|
|
97
|
+
const descriptor = createCommandDescriptor(
|
|
98
|
+
CommandStatementQuery.$type,
|
|
99
|
+
CommandStatementQuery.encode({
|
|
100
|
+
$type: CommandStatementQuery.$type,
|
|
101
|
+
...request,
|
|
102
|
+
}).finish(),
|
|
103
|
+
);
|
|
104
|
+
return this.inner.getFlightInfo(descriptor, options);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function createCommandDescriptor(
|
|
109
|
+
typeUrl: string,
|
|
110
|
+
value: Uint8Array,
|
|
111
|
+
): FlightDescriptor {
|
|
112
|
+
const cmd = Any.create({
|
|
113
|
+
typeUrl: `type.googleapis.com/${typeUrl}`,
|
|
114
|
+
value,
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
return FlightDescriptor.create({
|
|
118
|
+
type: FlightDescriptor_DescriptorType.CMD,
|
|
119
|
+
cmd: Any.encode(cmd).finish(),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import type { RecordBatch, Schema } from "apache-arrow";
|
|
2
|
+
import { type CallOptions, createClient } from "nice-grpc";
|
|
3
|
+
import {
|
|
4
|
+
decodeFlightDataStream,
|
|
5
|
+
decodeSchemaFromFlightInfo,
|
|
6
|
+
} from "./arrow-utils";
|
|
7
|
+
import {
|
|
8
|
+
type FlightData,
|
|
9
|
+
type FlightDescriptor,
|
|
10
|
+
type FlightInfo,
|
|
11
|
+
type FlightServiceClient,
|
|
12
|
+
FlightServiceDefinition,
|
|
13
|
+
type HandshakeRequest,
|
|
14
|
+
type HandshakeResponse,
|
|
15
|
+
type PutResult,
|
|
16
|
+
type Ticket,
|
|
17
|
+
} from "./proto/Flight";
|
|
18
|
+
import {
|
|
19
|
+
type ClientOptions,
|
|
20
|
+
createChannelFromConfig,
|
|
21
|
+
type HostOrChannel,
|
|
22
|
+
} from "./proto-utils";
|
|
23
|
+
|
|
24
|
+
export class ArrowFlightClient {
|
|
25
|
+
private client: FlightServiceClient;
|
|
26
|
+
|
|
27
|
+
constructor(
|
|
28
|
+
config: HostOrChannel,
|
|
29
|
+
options: ClientOptions<FlightServiceDefinition> = {},
|
|
30
|
+
) {
|
|
31
|
+
const channel = createChannelFromConfig(config);
|
|
32
|
+
|
|
33
|
+
this.client = createClient(
|
|
34
|
+
FlightServiceDefinition,
|
|
35
|
+
channel,
|
|
36
|
+
options.defaultCallOptions,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
executeFlightInfo(
|
|
41
|
+
info: FlightInfo,
|
|
42
|
+
options?: CallOptions,
|
|
43
|
+
): AsyncGenerator<RecordBatch> {
|
|
44
|
+
const schema = decodeSchemaFromFlightInfo(info);
|
|
45
|
+
if (!schema) {
|
|
46
|
+
throw new Error("FlightInfo must have a schema");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const client = this;
|
|
50
|
+
|
|
51
|
+
return (async function* () {
|
|
52
|
+
for (const endpoint of info.endpoint) {
|
|
53
|
+
if (endpoint.ticket === undefined) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
yield* client.doGet(endpoint.ticket, { schema, ...options });
|
|
58
|
+
}
|
|
59
|
+
})();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Handshake between client and server.
|
|
64
|
+
*
|
|
65
|
+
* Depending on the server, the handshake may be required to determine the
|
|
66
|
+
* token that should be used for future operations. Both request and response
|
|
67
|
+
* are streams to allow multiple round-trips depending on auth mechanism.
|
|
68
|
+
*/
|
|
69
|
+
handshake(
|
|
70
|
+
request: AsyncIterable<HandshakeRequest>,
|
|
71
|
+
options?: CallOptions,
|
|
72
|
+
): AsyncIterable<HandshakeResponse> {
|
|
73
|
+
return this.client.handshake(request, options);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Get a list of available streams given a particular criteria. */
|
|
77
|
+
// listFlights(
|
|
78
|
+
// request: proto.arrow_flight.Criteria,
|
|
79
|
+
// options?: ClientCallOptions,
|
|
80
|
+
// ): AsyncIterable<proto.arrow_flight.FlightInfo>;
|
|
81
|
+
|
|
82
|
+
getFlightInfo(
|
|
83
|
+
request: FlightDescriptor,
|
|
84
|
+
options?: CallOptions,
|
|
85
|
+
): Promise<FlightInfo> {
|
|
86
|
+
return this.client.getFlightInfo(request, options);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// /** Start a query and get information to poll its execution status. */
|
|
90
|
+
// pollFlightInfo(
|
|
91
|
+
// request: proto.arrow_flight.FlightDescriptor,
|
|
92
|
+
// options?: ClientCallOptions,
|
|
93
|
+
// ): Promise<proto.arrow_flight.PollInfo>;
|
|
94
|
+
|
|
95
|
+
// /** Get the schema for a given FlightDescriptor. */
|
|
96
|
+
// getSchema(
|
|
97
|
+
// request: proto.arrow_flight.FlightDescriptor,
|
|
98
|
+
// options?: ClientCallOptions,
|
|
99
|
+
// ): Promise<proto.arrow_flight.SchemaResult>;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Retrieve a single stream associated with a particular descriptor
|
|
103
|
+
* associated with the referenced ticket. A Flight can be composed of one or
|
|
104
|
+
* more streams where each stream can be retrieved using a separate opaque
|
|
105
|
+
* ticket that the flight service uses for managing a collection of streams.
|
|
106
|
+
*/
|
|
107
|
+
doGet(
|
|
108
|
+
request: Ticket,
|
|
109
|
+
options: { schema: Schema } & CallOptions,
|
|
110
|
+
): AsyncIterable<RecordBatch> {
|
|
111
|
+
const { schema: expectedSchema } = options;
|
|
112
|
+
return decodeFlightDataStream(this.client.doGet(request, options), {
|
|
113
|
+
expectedSchema,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Push a stream to the flight service associated with a particular
|
|
119
|
+
* flight stream. This allows a client of a flight service to upload a stream
|
|
120
|
+
* of data. Depending on the particular flight service, a client consumer
|
|
121
|
+
* could be allowed to upload a single stream per descriptor or an unlimited
|
|
122
|
+
* number. In the latter, the service might implement a 'seal' action that
|
|
123
|
+
* can be applied to a descriptor once all streams are uploaded.
|
|
124
|
+
*/
|
|
125
|
+
doPut(
|
|
126
|
+
request: AsyncIterable<FlightData>,
|
|
127
|
+
options?: CallOptions,
|
|
128
|
+
): AsyncIterable<PutResult> {
|
|
129
|
+
return this.client.doPut(request, options);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// /** Open a bidirectional data channel for a given descriptor. */
|
|
133
|
+
// doExchange(
|
|
134
|
+
// request: AsyncIterable<proto.arrow_flight.FlightData>,
|
|
135
|
+
// options?: ClientCallOptions,
|
|
136
|
+
// ): AsyncIterable<proto.arrow_flight.FlightData>;
|
|
137
|
+
|
|
138
|
+
// /** Execute a specific action against the flight service. */
|
|
139
|
+
// doAction(
|
|
140
|
+
// request: proto.arrow_flight.Action,
|
|
141
|
+
// options?: ClientCallOptions,
|
|
142
|
+
// ): AsyncIterable<proto.arrow_flight.Result>;
|
|
143
|
+
|
|
144
|
+
// /** Get all available action types. */
|
|
145
|
+
// listActions(
|
|
146
|
+
// request: proto.arrow_flight.Empty,
|
|
147
|
+
// options?: ClientCallOptions,
|
|
148
|
+
// ): AsyncIterable<proto.arrow_flight.ActionType>;
|
|
149
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { Message, type Schema } from "apache-arrow";
|
|
2
|
+
import type { FlightData, FlightInfo } from "./proto/Flight";
|
|
3
|
+
import { RecordBatchStreamReaderFromFlightData } from "./record-batch-decoder";
|
|
4
|
+
|
|
5
|
+
export function decodeSchemaFromFlightInfo(
|
|
6
|
+
info: FlightInfo,
|
|
7
|
+
): Schema | undefined {
|
|
8
|
+
// Notice that the `info.schema` field has the following format:
|
|
9
|
+
// The schema of the dataset in its IPC form:
|
|
10
|
+
// 4 bytes - an optional IPC_CONTINUATION_TOKEN prefix
|
|
11
|
+
// 4 bytes - the byte length of the payload
|
|
12
|
+
// a flatbuffer Message whose header is the Schema
|
|
13
|
+
const message = Message.decode(info.schema.slice(8));
|
|
14
|
+
return getMessageSchema(message);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getMessageSchema(message: Message): Schema | undefined {
|
|
18
|
+
if (message.isSchema()) {
|
|
19
|
+
return message.header();
|
|
20
|
+
}
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function decodeFlightDataStream(
|
|
25
|
+
stream: AsyncIterable<FlightData>,
|
|
26
|
+
{ expectedSchema: _expectedSchema }: { expectedSchema: Schema },
|
|
27
|
+
) {
|
|
28
|
+
// TODO: we want to validate the schema of the stream?
|
|
29
|
+
return new RecordBatchStreamReaderFromFlightData(stream);
|
|
30
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { Message, type RecordBatch, type Schema } from "apache-arrow";
|
|
2
|
+
import * as metadata from "apache-arrow/ipc/metadata/message";
|
|
3
|
+
import { VectorAssembler } from "apache-arrow/visitor/vectorassembler";
|
|
4
|
+
import { FlightData, type FlightDescriptor } from "./proto/Flight";
|
|
5
|
+
|
|
6
|
+
export const FlightDataEncoder = {
|
|
7
|
+
encodeSchema(
|
|
8
|
+
schema: Schema,
|
|
9
|
+
{
|
|
10
|
+
flightDescriptor,
|
|
11
|
+
appMetadata,
|
|
12
|
+
}: { flightDescriptor: FlightDescriptor; appMetadata?: Uint8Array },
|
|
13
|
+
): FlightData {
|
|
14
|
+
const message = Message.from(schema);
|
|
15
|
+
return FlightData.create({
|
|
16
|
+
dataHeader: Message.encode(message),
|
|
17
|
+
appMetadata,
|
|
18
|
+
flightDescriptor,
|
|
19
|
+
});
|
|
20
|
+
},
|
|
21
|
+
encodeBatch(
|
|
22
|
+
batch: RecordBatch,
|
|
23
|
+
{
|
|
24
|
+
appMetadata,
|
|
25
|
+
}: {
|
|
26
|
+
appMetadata?: (args: {
|
|
27
|
+
index: number;
|
|
28
|
+
length: number;
|
|
29
|
+
}) => Uint8Array | undefined;
|
|
30
|
+
} = {},
|
|
31
|
+
): ReadonlyArray<FlightData> {
|
|
32
|
+
const { byteLength, nodes, bufferRegions, buffers } =
|
|
33
|
+
VectorAssembler.assemble(batch);
|
|
34
|
+
|
|
35
|
+
const metadataRecordBatch = new metadata.RecordBatch(
|
|
36
|
+
batch.numRows,
|
|
37
|
+
nodes,
|
|
38
|
+
bufferRegions,
|
|
39
|
+
null,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const message = Message.from(metadataRecordBatch, byteLength);
|
|
43
|
+
|
|
44
|
+
const flightData = FlightData.create({
|
|
45
|
+
dataHeader: Message.encode(message),
|
|
46
|
+
appMetadata: appMetadata?.({ index: 0, length: 1 }),
|
|
47
|
+
dataBody: encodeRecordBatchContent(buffers, byteLength, null),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return [flightData];
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Write the content of the record batch to the output buffer.
|
|
55
|
+
//
|
|
56
|
+
// This code is taken from the arrow-js ipc module.
|
|
57
|
+
// https://github.com/apache/arrow-js/blob/870e27eb3e467a6f2936e8d17c5e021c5eb07ad7/src/ipc/writer.ts
|
|
58
|
+
//
|
|
59
|
+
// Licensed to the Apache Software Foundation (ASF) under one or more
|
|
60
|
+
// contributor license agreements. See the NOTICE file distributed with this
|
|
61
|
+
// work for additional information regarding copyright ownership. The ASF
|
|
62
|
+
// licenses this file to you under the Apache License, Version 2.0 (the
|
|
63
|
+
// "License"); you may not use this file except in compliance with the License.
|
|
64
|
+
// You may obtain a copy of the License at
|
|
65
|
+
//
|
|
66
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
67
|
+
//
|
|
68
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
69
|
+
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
70
|
+
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
71
|
+
// License for the specific language governing permissions and limitations under
|
|
72
|
+
// the License.
|
|
73
|
+
function encodeRecordBatchContent(
|
|
74
|
+
buffers: ArrayBufferView<ArrayBufferLike>[],
|
|
75
|
+
byteLength: number,
|
|
76
|
+
compression: metadata.BodyCompression | null = null,
|
|
77
|
+
): Uint8Array {
|
|
78
|
+
const out = new Uint8Array(byteLength);
|
|
79
|
+
const bufGroupSize = compression != null ? 2 : 1;
|
|
80
|
+
const bufs = new Array(bufGroupSize);
|
|
81
|
+
let pos = 0;
|
|
82
|
+
|
|
83
|
+
for (let i = 0; i < buffers.length; i += bufGroupSize) {
|
|
84
|
+
let size = 0;
|
|
85
|
+
for (let j = -1; ++j < bufGroupSize; ) {
|
|
86
|
+
bufs[j] = buffers[i + j];
|
|
87
|
+
size += bufs[j].byteLength;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (size === 0) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const buf of bufs) {
|
|
95
|
+
out.set(new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength), pos);
|
|
96
|
+
pos += buf.byteLength;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const padding = ((size + 7) & ~7) - size;
|
|
100
|
+
|
|
101
|
+
if (padding > 0) {
|
|
102
|
+
out.set(new Uint8Array(padding), pos);
|
|
103
|
+
pos += padding;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return out;
|
|
108
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export { Metadata } from "nice-grpc";
|
|
2
|
+
export { ArrowFlightClient } from "./arrow-flight";
|
|
3
|
+
export { ArrowFlightSqlClient } from "./arrow-flight-sql";
|
|
4
|
+
export { FlightDataEncoder } from "./flight-data-encoder";
|
|
5
|
+
export {
|
|
6
|
+
FlightData,
|
|
7
|
+
FlightDescriptor,
|
|
8
|
+
FlightDescriptor_DescriptorType,
|
|
9
|
+
PutResult,
|
|
10
|
+
Ticket,
|
|
11
|
+
} from "./proto/Flight";
|
|
12
|
+
export {
|
|
13
|
+
type ClientOptions,
|
|
14
|
+
createChannelFromConfig,
|
|
15
|
+
type HostOrChannel,
|
|
16
|
+
} from "./proto-utils";
|