| // Licensed to the Apache Software Foundation (ASF) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The ASF licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| import { test } from 'node:test' |
| import assert from 'node:assert/strict' |
| import { mkdtempSync, writeFileSync, rmSync } from 'node:fs' |
| import { join, isAbsolute } from 'node:path' |
| import { tmpdir } from 'node:os' |
| import { AdbcDatabase, AdbcError } from '../lib/index.js' |
| |
| const testLib = process.env.ADBC_DRIVER_MANAGER_TEST_LIB |
| |
| function tomlPath(p: string): string { |
| return p.replaceAll('\\', '/') |
| } |
| |
| function writeProfileToml(dir: string, name: string, driver: string, options: Record<string, string> = {}): void { |
| const optLines = Object.entries(options) |
| .map(([k, v]) => `${k} = "${tomlPath(v)}"`) |
| .join('\n') |
| writeFileSync( |
| join(dir, `${name}.toml`), |
| `profile_version = 1\ndriver = "${tomlPath(driver)}"\n\n[Options]\n${optLines}\n`, |
| ) |
| } |
| |
| test('profile: load database from profile:// URI', async () => { |
| const driver = testLib ?? 'sqlite' |
| const tmpDir = mkdtempSync(join(tmpdir(), 'adbc-profile-test-')) |
| try { |
| // Create a SQLite file with a known table so we can confirm the profile |
| // is actually pointing us at the right database. |
| const dbPath = join(tmpDir, 'test.db') |
| const setupDb = new AdbcDatabase({ driver, databaseOptions: { uri: dbPath } }) |
| const setupConn = await setupDb.connect() |
| await setupConn.execute('CREATE TABLE profile_marker (id INTEGER, value TEXT)') |
| await setupConn.execute("INSERT INTO profile_marker VALUES (42, 'from_profile')") |
| await setupConn.close() |
| await setupDb.close() |
| |
| writeProfileToml(tmpDir, 'test_sqlite', driver, { uri: dbPath }) |
| |
| const db = new AdbcDatabase({ |
| driver: 'profile://test_sqlite', |
| profileSearchPaths: [tmpDir], |
| }) |
| const conn = await db.connect() |
| |
| const table = await conn.query('SELECT id, value FROM profile_marker') |
| assert.strictEqual(table.numRows, 1) |
| assert.strictEqual(table.getChild('id')?.get(0), 42n) |
| assert.strictEqual(table.getChild('value')?.get(0), 'from_profile') |
| |
| await conn.close() |
| await db.close() |
| } finally { |
| rmSync(tmpDir, { recursive: true }) |
| } |
| }) |
| |
| // URI-style driver strings require a short name as the scheme — skip when the |
| // test env var is set to an absolute path (e.g. on Windows CI). |
| test('profile: load database from sqlite: URI', { skip: testLib !== undefined && isAbsolute(testLib) }, async () => { |
| const driver = testLib ?? 'sqlite' |
| const db = new AdbcDatabase({ driver: `${driver}::memory:` }) |
| const conn = await db.connect() |
| |
| await conn.query('SELECT 1 AS n') |
| |
| await conn.close() |
| await db.close() |
| }) |
| |
| test( |
| 'profile: omit driver, load from URI in databaseOptions.uri', |
| { skip: testLib !== undefined && isAbsolute(testLib) }, |
| async () => { |
| const driver = testLib ?? 'sqlite' |
| const db = new AdbcDatabase({ |
| databaseOptions: { uri: `${driver}::memory:` }, |
| }) |
| const conn = await db.connect() |
| |
| await conn.query('SELECT 1 AS n') |
| |
| await conn.close() |
| await db.close() |
| }, |
| ) |
| |
| test('profile: omit driver, load from profile:// URI in databaseOptions.uri', async () => { |
| const driver = testLib ?? 'sqlite' |
| const tmpDir = mkdtempSync(join(tmpdir(), 'adbc-profile-test-')) |
| try { |
| writeProfileToml(tmpDir, 'my_profile', driver) |
| |
| const db = new AdbcDatabase({ |
| databaseOptions: { uri: 'profile://my_profile' }, |
| profileSearchPaths: [tmpDir], |
| }) |
| const conn = await db.connect() |
| |
| await conn.query('SELECT 1 AS n') |
| |
| await conn.close() |
| await db.close() |
| } finally { |
| rmSync(tmpDir, { recursive: true }) |
| } |
| }) |
| |
| test('profile: omit driver, load from bare profile name in databaseOptions.profile', async () => { |
| const driver = testLib ?? 'sqlite' |
| const tmpDir = mkdtempSync(join(tmpdir(), 'adbc-profile-test-')) |
| try { |
| writeProfileToml(tmpDir, 'my_profile', driver) |
| |
| const db = new AdbcDatabase({ |
| databaseOptions: { profile: 'my_profile' }, |
| profileSearchPaths: [tmpDir], |
| }) |
| const conn = await db.connect() |
| |
| await conn.query('SELECT 1 AS n') |
| |
| await conn.close() |
| await db.close() |
| } finally { |
| rmSync(tmpDir, { recursive: true }) |
| } |
| }) |
| |
| test('profile: driver + databaseOptions.profile loads from profile', async () => { |
| const driver = tomlPath(testLib ?? 'sqlite') |
| const tmpDir = mkdtempSync(join(tmpdir(), 'adbc-profile-test-')) |
| try { |
| writeProfileToml(tmpDir, 'my_profile', driver) |
| |
| const db = new AdbcDatabase({ |
| driver, |
| databaseOptions: { profile: 'my_profile' }, |
| profileSearchPaths: [tmpDir], |
| }) |
| const conn = await db.connect() |
| |
| await conn.query('SELECT 1 AS n') |
| |
| await conn.close() |
| await db.close() |
| } finally { |
| rmSync(tmpDir, { recursive: true }) |
| } |
| }) |
| |
| test('profile: driver + databaseOptions.uri profile:// loads from profile', async () => { |
| const driver = tomlPath(testLib ?? 'sqlite') |
| const tmpDir = mkdtempSync(join(tmpdir(), 'adbc-profile-test-')) |
| try { |
| writeProfileToml(tmpDir, 'my_profile', driver) |
| |
| const db = new AdbcDatabase({ |
| driver, |
| databaseOptions: { uri: 'profile://my_profile' }, |
| profileSearchPaths: [tmpDir], |
| }) |
| const conn = await db.connect() |
| |
| await conn.query('SELECT 1 AS n') |
| |
| await conn.close() |
| await db.close() |
| } finally { |
| rmSync(tmpDir, { recursive: true }) |
| } |
| }) |
| |
| test('profile: error when profile key and profile:// uri are both provided', async () => { |
| assert.throws( |
| () => |
| new AdbcDatabase({ |
| databaseOptions: { profile: 'p1', uri: 'profile://p2' }, |
| }), |
| (err: unknown) => { |
| assert.ok(err instanceof Error) |
| assert.ok(err.message.includes('mutually exclusive'), `unexpected message: ${err.message}`) |
| return true |
| }, |
| ) |
| }) |
| |
| test('profile: driver disagreeing with profile errors', async () => { |
| const driver = tomlPath(testLib ?? 'sqlite') |
| const tmpDir = mkdtempSync(join(tmpdir(), 'adbc-profile-test-')) |
| try { |
| writeProfileToml(tmpDir, 'my_profile', driver) |
| |
| assert.throws( |
| () => |
| new AdbcDatabase({ |
| driver: 'some_other_driver', |
| databaseOptions: { profile: 'my_profile' }, |
| profileSearchPaths: [tmpDir], |
| }), |
| (err: unknown) => { |
| assert.ok(err instanceof AdbcError) |
| assert.strictEqual(err.code, 'InvalidArguments') |
| assert.strictEqual( |
| err.message, |
| `profile specifies driver \`${tomlPath(driver)}\` which does not match requested driver \`some_other_driver\``, |
| ) |
| return true |
| }, |
| ) |
| } finally { |
| rmSync(tmpDir, { recursive: true }) |
| } |
| }) |
| |
| test('profile: error when driver is omitted and neither uri nor profile is present', async () => { |
| assert.throws( |
| // @ts-expect-error — intentionally invalid: neither uri nor profile present |
| () => new AdbcDatabase({ databaseOptions: { username: 'foo' } }), |
| (err: unknown) => { |
| assert.ok(err instanceof Error) |
| assert.ok(err.message.includes('driver is required'), `unexpected message: ${err.message}`) |
| return true |
| }, |
| ) |
| }) |