|
| 1 | +// Copyright (c) 2026 Databricks, Inc. |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
1 | 15 | import IBackend from '../contracts/IBackend'; |
2 | 16 | import ISessionBackend from '../contracts/ISessionBackend'; |
| 17 | +import IOperationBackend from '../contracts/IOperationBackend'; |
3 | 18 | import { ConnectionOptions, OpenSessionRequest } from '../contracts/IDBSQLClient'; |
| 19 | +import { |
| 20 | + ExecuteStatementOptions, |
| 21 | + TypeInfoRequest, |
| 22 | + CatalogsRequest, |
| 23 | + SchemasRequest, |
| 24 | + TablesRequest, |
| 25 | + TableTypesRequest, |
| 26 | + ColumnsRequest, |
| 27 | + FunctionsRequest, |
| 28 | + PrimaryKeysRequest, |
| 29 | + CrossReferenceRequest, |
| 30 | +} from '../contracts/IDBSQLSession'; |
| 31 | +import Status from '../dto/Status'; |
| 32 | +import InfoValue from '../dto/InfoValue'; |
4 | 33 | import HiveDriverError from '../errors/HiveDriverError'; |
| 34 | +import { getSeaNative, SeaNativeBinding } from './SeaNativeLoader'; |
| 35 | +import { buildSeaConnectionOptions, SeaNativeConnectionOptions } from './SeaAuth'; |
| 36 | + |
| 37 | +const NOT_IMPLEMENTED_SESSION = |
| 38 | + 'SEA session backend: method not implemented in sea-auth (M0); lands in sea-execution/sea-operation.'; |
| 39 | + |
| 40 | +/** |
| 41 | + * Opaque handle to the napi binding's `Connection` class. The exact |
| 42 | + * shape lives in `native/sea/index.d.ts` (auto-generated). We type it as |
| 43 | + * a structural minimum here so the loader's pass-through typing doesn't |
| 44 | + * leak into every call site. |
| 45 | + */ |
| 46 | +interface NativeConnection { |
| 47 | + close(): Promise<void>; |
| 48 | +} |
| 49 | + |
| 50 | +/** |
| 51 | + * Minimal `ISessionBackend` that wraps the napi-binding's `Connection`. |
| 52 | + * |
| 53 | + * For M0 (sea-auth) only `id` and `close()` are functional — they're the |
| 54 | + * subset required to round-trip a connect-open-close cycle. Every other |
| 55 | + * method throws a clear "not implemented in M0" `HiveDriverError`. |
| 56 | + * |
| 57 | + * The `id` field is currently a synthetic counter-based string; the kernel |
| 58 | + * exposes a real session-id through a follow-on getter that |
| 59 | + * `sea-execution` will wire through. |
| 60 | + */ |
| 61 | +export class SeaSessionBackend implements ISessionBackend { |
| 62 | + private static seq = 0; |
| 63 | + |
| 64 | + public readonly id: string; |
| 65 | + |
| 66 | + private readonly connection: NativeConnection; |
| 67 | + |
| 68 | + constructor(connection: NativeConnection) { |
| 69 | + this.connection = connection; |
| 70 | + SeaSessionBackend.seq += 1; |
| 71 | + this.id = `sea-session-${SeaSessionBackend.seq}`; |
| 72 | + } |
| 73 | + |
| 74 | + /* eslint-disable @typescript-eslint/no-unused-vars */ |
| 75 | + public async getInfo(_infoType: number): Promise<InfoValue> { |
| 76 | + throw new HiveDriverError(NOT_IMPLEMENTED_SESSION); |
| 77 | + } |
| 78 | + |
| 79 | + public async executeStatement( |
| 80 | + _statement: string, |
| 81 | + _options: ExecuteStatementOptions, |
| 82 | + ): Promise<IOperationBackend> { |
| 83 | + throw new HiveDriverError(NOT_IMPLEMENTED_SESSION); |
| 84 | + } |
| 85 | + |
| 86 | + public async getTypeInfo(_request: TypeInfoRequest): Promise<IOperationBackend> { |
| 87 | + throw new HiveDriverError(NOT_IMPLEMENTED_SESSION); |
| 88 | + } |
| 89 | + |
| 90 | + public async getCatalogs(_request: CatalogsRequest): Promise<IOperationBackend> { |
| 91 | + throw new HiveDriverError(NOT_IMPLEMENTED_SESSION); |
| 92 | + } |
| 93 | + |
| 94 | + public async getSchemas(_request: SchemasRequest): Promise<IOperationBackend> { |
| 95 | + throw new HiveDriverError(NOT_IMPLEMENTED_SESSION); |
| 96 | + } |
5 | 97 |
|
6 | | -const NOT_IMPLEMENTED = 'SEA backend not implemented yet — wired in sea-napi-binding feature'; |
| 98 | + public async getTables(_request: TablesRequest): Promise<IOperationBackend> { |
| 99 | + throw new HiveDriverError(NOT_IMPLEMENTED_SESSION); |
| 100 | + } |
| 101 | + |
| 102 | + public async getTableTypes(_request: TableTypesRequest): Promise<IOperationBackend> { |
| 103 | + throw new HiveDriverError(NOT_IMPLEMENTED_SESSION); |
| 104 | + } |
| 105 | + |
| 106 | + public async getColumns(_request: ColumnsRequest): Promise<IOperationBackend> { |
| 107 | + throw new HiveDriverError(NOT_IMPLEMENTED_SESSION); |
| 108 | + } |
7 | 109 |
|
| 110 | + public async getFunctions(_request: FunctionsRequest): Promise<IOperationBackend> { |
| 111 | + throw new HiveDriverError(NOT_IMPLEMENTED_SESSION); |
| 112 | + } |
| 113 | + |
| 114 | + public async getPrimaryKeys(_request: PrimaryKeysRequest): Promise<IOperationBackend> { |
| 115 | + throw new HiveDriverError(NOT_IMPLEMENTED_SESSION); |
| 116 | + } |
| 117 | + |
| 118 | + public async getCrossReference(_request: CrossReferenceRequest): Promise<IOperationBackend> { |
| 119 | + throw new HiveDriverError(NOT_IMPLEMENTED_SESSION); |
| 120 | + } |
| 121 | + /* eslint-enable @typescript-eslint/no-unused-vars */ |
| 122 | + |
| 123 | + public async close(): Promise<Status> { |
| 124 | + await this.connection.close(); |
| 125 | + return Status.success(); |
| 126 | + } |
| 127 | +} |
| 128 | + |
| 129 | +/** |
| 130 | + * M0 SeaBackend — wires PAT auth + napi `openSession` end-to-end. |
| 131 | + * |
| 132 | + * Connect is a no-op at this layer (the napi binding has no notion of a |
| 133 | + * standalone "connect"; a session is opened directly). We capture the |
| 134 | + * validated PAT options and hand them to `openSession()` on demand. |
| 135 | + * |
| 136 | + * Subsequent milestones (`sea-execution`, `sea-operation`) replace the |
| 137 | + * stubbed `ISessionBackend` / `IOperationBackend` methods with real |
| 138 | + * napi-binding calls. |
| 139 | + */ |
8 | 140 | export default class SeaBackend implements IBackend { |
9 | | - // eslint-disable-next-line @typescript-eslint/no-unused-vars, class-methods-use-this |
| 141 | + private nativeOptions?: SeaNativeConnectionOptions; |
| 142 | + |
| 143 | + private readonly native: SeaNativeBinding; |
| 144 | + |
| 145 | + constructor(native: SeaNativeBinding = getSeaNative()) { |
| 146 | + this.native = native; |
| 147 | + } |
| 148 | + |
10 | 149 | public async connect(options: ConnectionOptions): Promise<void> { |
11 | | - throw new HiveDriverError(NOT_IMPLEMENTED); |
| 150 | + // Validate PAT auth + capture the napi-binding option shape. |
| 151 | + // Any non-PAT mode (or a missing token) throws here, before we ever |
| 152 | + // touch the native binding. |
| 153 | + this.nativeOptions = buildSeaConnectionOptions(options); |
12 | 154 | } |
13 | 155 |
|
14 | | - // eslint-disable-next-line @typescript-eslint/no-unused-vars, class-methods-use-this |
15 | | - public async openSession(request: OpenSessionRequest): Promise<ISessionBackend> { |
16 | | - throw new HiveDriverError(NOT_IMPLEMENTED); |
| 156 | + // eslint-disable-next-line @typescript-eslint/no-unused-vars |
| 157 | + public async openSession(_request: OpenSessionRequest): Promise<ISessionBackend> { |
| 158 | + if (!this.nativeOptions) { |
| 159 | + throw new HiveDriverError('SeaBackend: connect() must be called before openSession().'); |
| 160 | + } |
| 161 | + const connection = (await this.native.openSession(this.nativeOptions)) as NativeConnection; |
| 162 | + return new SeaSessionBackend(connection); |
17 | 163 | } |
18 | 164 |
|
19 | | - // No-op so DBSQLClient.close() can finish its state-clearing block after a |
20 | | - // failed useSEA: true connect. Real teardown lands with the M1 SEA impl. |
21 | | - // eslint-disable-next-line @typescript-eslint/no-empty-function, class-methods-use-this |
22 | | - public async close(): Promise<void> {} |
| 165 | + public async close(): Promise<void> { |
| 166 | + // Connection-level resources are owned by the session wrapper. No-op here. |
| 167 | + this.nativeOptions = undefined; |
| 168 | + } |
23 | 169 | } |
0 commit comments