Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/docs/drivers.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Altimate Code connects to 10 databases natively via TypeScript drivers. No Pytho
|----------|---------|-------------|------------|-------|
| PostgreSQL | `pg` | Password, Connection String, SSL | ✅ Docker | Stable, fully parameterized queries |
| DuckDB | `duckdb` | File/Memory (no auth) | ✅ In-memory | Default local database |
| SQLite | `better-sqlite3` | File (no auth) | ✅ File-based | Sync API wrapped async |
| SQLite | `bun:sqlite` (built-in) | File (no auth) | ✅ File-based | Zero-install, built into runtime |
| MySQL | `mysql2` | Password | ✅ Docker | Parameterized introspection |
| SQL Server | `mssql` | Password, Azure AD | ✅ Docker | Uses `tedious` TDS protocol |
| Redshift | `pg` (wire-compat) | Password | ✅ Docker (PG wire) | Uses SVV system views |
Expand All @@ -26,7 +26,7 @@ Drivers are `optionalDependencies`, so install only what you need:
```bash
# Embedded databases (no external service needed)
bun add duckdb
bun add better-sqlite3
# SQLite uses bun:sqlite (built-in, no install needed)

# Standard databases
bun add pg # PostgreSQL + Redshift
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@
},
"devDependencies": {
"@tsconfig/bun": "catalog:",
"@types/better-sqlite3": "7.6.13",
"@types/pg": "8.18.0",
"@typescript/native-preview": "catalog:",
"husky": "9.1.7",
Expand Down
3 changes: 1 addition & 2 deletions packages/drivers/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
"mysql2": "^3.0.0",
"mssql": "^11.0.0",
"oracledb": "^6.0.0",
"duckdb": "^1.0.0",
"better-sqlite3": "^11.0.0"
"duckdb": "^1.0.0"
}
}
37 changes: 19 additions & 18 deletions packages/drivers/src/sqlite.ts
Original file line number Diff line number Diff line change
@@ -1,46 +1,45 @@
/**
* SQLite driver using the `better-sqlite3` package.
* SQLite driver using Bun's built-in `bun:sqlite`.
* Synchronous API wrapped in async interface.
*/

import { Database } from "bun:sqlite"
import type { ConnectionConfig, Connector, ConnectorResult, SchemaColumn } from "./types"

export async function connect(config: ConnectionConfig): Promise<Connector> {
let Database: any
try {
const mod = await import("better-sqlite3")
Database = mod.default || mod
} catch {
throw new Error(
"SQLite driver not installed. Run: npm install better-sqlite3",
)
}

const dbPath = (config.path as string) ?? ":memory:"
let db: any
let db: Database | null = null

return {
async connect() {
const isReadonly = config.readonly === true
db = new Database(dbPath, {
readonly: config.readonly === true,
readonly: isReadonly,
create: !isReadonly,
})
db.pragma("journal_mode = WAL")
if (!isReadonly) {
db.exec("PRAGMA journal_mode = WAL")
}
},

async execute(sql: string, limit?: number, _binds?: any[]): Promise<ConnectorResult> {
if (!db) throw new Error("SQLite connection not open")
const effectiveLimit = limit ?? 1000

// Determine if this is a SELECT-like statement
const trimmed = sql.trim().toLowerCase()
const isPragma = trimmed.startsWith("pragma")
const isSelect =
trimmed.startsWith("select") ||
trimmed.startsWith("pragma") ||
isPragma ||
trimmed.startsWith("with") ||
trimmed.startsWith("explain")

// PRAGMA statements don't support LIMIT clause
let query = sql
if (
isSelect &&
!isPragma &&
effectiveLimit &&
!/\bLIMIT\b/i.test(sql)
) {
Expand All @@ -59,7 +58,7 @@ export async function connect(config: ConnectionConfig): Promise<Connector> {
}

const stmt = db.prepare(query)
const rows = stmt.all()
const rows = stmt.all() as any[]
const columns = rows.length > 0 ? Object.keys(rows[0]) : []
const truncated = rows.length > effectiveLimit
const limitedRows = truncated ? rows.slice(0, effectiveLimit) : rows
Expand All @@ -82,11 +81,12 @@ export async function connect(config: ConnectionConfig): Promise<Connector> {
async listTables(
_schema: string,
): Promise<Array<{ name: string; type: string }>> {
if (!db) throw new Error("SQLite connection not open")
const rows = db
.prepare(
"SELECT name, type FROM sqlite_master WHERE type IN ('table','view') AND name NOT LIKE 'sqlite_%' ORDER BY name",
)
.all()
.all() as any[]
return rows.map((r: any) => ({
name: r.name as string,
type: r.type as string,
Expand All @@ -97,7 +97,8 @@ export async function connect(config: ConnectionConfig): Promise<Connector> {
_schema: string,
table: string,
): Promise<SchemaColumn[]> {
const rows = db.prepare('SELECT * FROM pragma_table_info(?) ORDER BY cid').all(table)
if (!db) throw new Error("SQLite connection not open")
const rows = db.prepare('SELECT * FROM pragma_table_info(?) ORDER BY cid').all(table) as any[]
return rows.map((r: any) => ({
name: r.name as string,
data_type: r.type as string,
Expand Down
2 changes: 1 addition & 1 deletion packages/opencode/script/build.ts
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ for (const item of targets) {
"@altimateai/altimate-core",
// Database drivers — native addons, users install on demand per warehouse
"pg", "snowflake-sdk", "@google-cloud/bigquery", "@databricks/sql",
"mysql2", "mssql", "oracledb", "duckdb", "better-sqlite3",
"mysql2", "mssql", "oracledb", "duckdb",
// Optional infra packages — native addons or heavy optional deps
"keytar", "ssh2", "dockerode",
],
Expand Down
1 change: 0 additions & 1 deletion packages/opencode/script/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ const driverPeerDependencies: Record<string, string> = {
"mssql": ">=11",
"oracledb": ">=6",
"duckdb": ">=1",
"better-sqlite3": ">=11",
}

const driverPeerDependenciesMeta: Record<string, { optional: true }> = Object.fromEntries(
Expand Down
73 changes: 35 additions & 38 deletions packages/opencode/src/altimate/native/schema/cache.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/**
* Schema cache — indexes warehouse metadata into SQLite for fast search.
*
* Uses better-sqlite3 (optional dependency, dynamically imported) to build
* a local FTS-ready cache of warehouse schemas, tables, and columns.
* Uses bun:sqlite (built into the Bun runtime) to build a local FTS-ready
* cache of warehouse schemas, tables, and columns.
* Cache location: ~/.altimate-code/schema-cache.db
*/

import { Database } from "bun:sqlite"
import * as path from "path"
import * as os from "os"
import * as fs from "fs"
Expand Down Expand Up @@ -65,7 +66,7 @@ CREATE INDEX IF NOT EXISTS idx_tables_search ON tables_cache(search_text);
CREATE INDEX IF NOT EXISTS idx_columns_search ON columns_cache(search_text);
CREATE INDEX IF NOT EXISTS idx_tables_warehouse ON tables_cache(warehouse);
CREATE INDEX IF NOT EXISTS idx_columns_warehouse ON columns_cache(warehouse);
CREATE INDEX IF NOT EXISTS idx_columns_table ON columns_cache(warehouse, schema_name, table_name);
CREATE INDEX IF NOT EXISTS idx_columns_table ON columns_cache(warehouse, schema_name, table_name, column_name);
`

// ---------------------------------------------------------------------------
Expand Down Expand Up @@ -115,45 +116,24 @@ function tokenizeQuery(query: string): string[] {

/** SQLite-backed schema metadata cache for fast warehouse search. */
export class SchemaCache {
private db: any // better-sqlite3 Database instance
private db: Database
private dbPath: string

private constructor(db: any, dbPath: string) {
private constructor(db: Database, dbPath: string) {
this.db = db
this.dbPath = dbPath
}

/**
* Create a SchemaCache instance.
* Uses dynamic import for better-sqlite3 (optional dependency).
*/
static async create(dbPath?: string): Promise<SchemaCache> {
/** Create a SchemaCache instance backed by a file on disk. */
static create(dbPath?: string): SchemaCache {
const resolvedPath = dbPath || defaultCachePath()
let Database: any
try {
const mod = await import("better-sqlite3")
Database = mod.default || mod
} catch {
throw new Error(
"better-sqlite3 not installed. Install with: npm install better-sqlite3",
)
}
const db = new Database(resolvedPath)
const db = new Database(resolvedPath, { create: true })
db.exec(CREATE_TABLES_SQL)
return new SchemaCache(db, resolvedPath)
}

/**
* Create a SchemaCache with an in-memory database (for testing).
*/
static async createInMemory(): Promise<SchemaCache> {
let Database: any
try {
const mod = await import("better-sqlite3")
Database = mod.default || mod
} catch {
throw new Error("better-sqlite3 not installed.")
}
/** Create a SchemaCache with an in-memory database (for testing). */
static createInMemory(): SchemaCache {
const db = new Database(":memory:")
db.exec(CREATE_TABLES_SQL)
return new SchemaCache(db, ":memory:")
Expand Down Expand Up @@ -197,6 +177,18 @@ export class SchemaCache {
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
)

// Batch inserts per-table inside a transaction to avoid per-statement disk fsyncs.
// The async connector calls (listTables, describeTable) run outside the transaction;
// only the synchronous SQLite inserts are wrapped.
const insertTableBatch = this.db.transaction(
(tableArgs: any[], columnArgsBatch: any[][]) => {
insertTable.run(...tableArgs)
for (const colArgs of columnArgsBatch) {
insertColumn.run(...colArgs)
}
},
)

for (const schemaName of schemas) {
if (schemaName.toUpperCase() === "INFORMATION_SCHEMA") continue
totalSchemas++
Expand All @@ -211,27 +203,32 @@ export class SchemaCache {
for (const tableInfo of tables) {
totalTables++
const searchText = makeSearchText(databaseName, schemaName, tableInfo.name, tableInfo.type)
insertTable.run(
warehouseName, databaseName, schemaName, tableInfo.name, tableInfo.type, searchText,
)

let columns: Array<{ name: string; data_type: string; nullable: boolean }> = []
try {
columns = await connector.describeTable(schemaName, tableInfo.name)
} catch {
continue
// continue with empty columns
}

// Build column insert args
const columnArgsBatch: any[][] = []
for (const col of columns) {
totalColumns++
const colSearch = makeSearchText(
databaseName, schemaName, tableInfo.name, col.name, col.data_type,
)
insertColumn.run(
columnArgsBatch.push([
warehouseName, databaseName, schemaName, tableInfo.name,
col.name, col.data_type, col.nullable ? 1 : 0, colSearch,
)
])
}

// Insert table + all its columns in a single transaction
insertTableBatch(
[warehouseName, databaseName, schemaName, tableInfo.name, tableInfo.type, searchText],
columnArgsBatch,
)
}
}

Expand Down Expand Up @@ -399,7 +396,7 @@ let _cache: SchemaCache | null = null

export async function getCache(): Promise<SchemaCache> {
if (!_cache) {
_cache = await SchemaCache.create()
_cache = SchemaCache.create()
}
return _cache
}
Expand Down
Loading
Loading