-
Notifications
You must be signed in to change notification settings - Fork 36
fix: graceful degradation when native binding fails to load #324
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,10 +1,24 @@ | ||||||||||||||||||
| // altimate_change - SQL query classifier for write detection | ||||||||||||||||||
| // altimate_change start — SQL query classifier for write detection | ||||||||||||||||||
| // | ||||||||||||||||||
| // Uses altimate-core's AST-based getStatementTypes() for accurate classification. | ||||||||||||||||||
| // Handles CTEs, string literals, procedural blocks, all dialects correctly. | ||||||||||||||||||
| // Lazy-loads altimate-core on first use to avoid crashing at import time | ||||||||||||||||||
| // when the native binary is unavailable (e.g. GLIBC mismatch). | ||||||||||||||||||
|
|
||||||||||||||||||
| // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||||||||||||||||||
| const core: any = require("@altimateai/altimate-core") | ||||||||||||||||||
| let _core: any = null | ||||||||||||||||||
|
|
||||||||||||||||||
| function getCore(): any { | ||||||||||||||||||
| if (!_core) { | ||||||||||||||||||
| try { | ||||||||||||||||||
| _core = require("@altimateai/altimate-core") | ||||||||||||||||||
| } catch { | ||||||||||||||||||
| // Native binding unavailable — return null so callers can degrade gracefully | ||||||||||||||||||
| return null | ||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
| return _core | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| // Categories from altimate-core that indicate write operations | ||||||||||||||||||
| const WRITE_CATEGORIES = new Set(["dml", "ddl", "dcl", "tcl"]) | ||||||||||||||||||
|
|
@@ -17,8 +31,11 @@ const HARD_DENY_TYPES = new Set(["DROP DATABASE", "DROP SCHEMA", "TRUNCATE", "TR | |||||||||||||||||
| /** | ||||||||||||||||||
| * Classify a SQL string as "read" or "write" using AST parsing. | ||||||||||||||||||
| * If ANY statement is a write, returns "write". | ||||||||||||||||||
| * Falls back to "write" (safe default) if native binding is unavailable. | ||||||||||||||||||
| */ | ||||||||||||||||||
| export function classify(sql: string): "read" | "write" { | ||||||||||||||||||
| const core = getCore() | ||||||||||||||||||
| if (!core) return "write" // fail-safe: treat as write when native unavailable | ||||||||||||||||||
| const result = core.getStatementTypes(sql) | ||||||||||||||||||
| if (!result?.categories?.length) return "read" | ||||||||||||||||||
| // Treat unknown categories (not in WRITE or READ sets) as write to fail safe | ||||||||||||||||||
|
|
@@ -36,8 +53,11 @@ export function classifyMulti(sql: string): "read" | "write" { | |||||||||||||||||
| /** | ||||||||||||||||||
| * Single-pass: classify and check for hard-denied statement types. | ||||||||||||||||||
| * Returns both the overall query type and whether a hard-deny pattern was found. | ||||||||||||||||||
| * Falls back to write + not-blocked when native binding is unavailable. | ||||||||||||||||||
| */ | ||||||||||||||||||
| export function classifyAndCheck(sql: string): { queryType: "read" | "write"; blocked: boolean } { | ||||||||||||||||||
| const core = getCore() | ||||||||||||||||||
| if (!core) return { queryType: "write", blocked: false } | ||||||||||||||||||
|
Comment on lines
58
to
+60
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hard-deny safety is bypassed when native core is unavailable. On Line 60, fallback returns 🔧 Proposed fix const HARD_DENY_TYPES = new Set(["DROP DATABASE", "DROP SCHEMA", "TRUNCATE", "TRUNCATE TABLE"])
+const HARD_DENY_FALLBACK_REGEX = /\bDROP\s+(DATABASE|SCHEMA)\b|\bTRUNCATE(?:\s+TABLE)?\b/i
@@
export function classifyAndCheck(sql: string): { queryType: "read" | "write"; blocked: boolean } {
const core = getCore()
- if (!core) return { queryType: "write", blocked: false }
+ if (!core) {
+ return { queryType: "write", blocked: HARD_DENY_FALLBACK_REGEX.test(sql) }
+ }📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||
| const result = core.getStatementTypes(sql) | ||||||||||||||||||
| if (!result?.statements?.length) return { queryType: "read", blocked: false } | ||||||||||||||||||
|
|
||||||||||||||||||
|
|
@@ -50,3 +70,4 @@ export function classifyAndCheck(sql: string): { queryType: "read" | "write"; bl | |||||||||||||||||
| const queryType = categories.some((c: string) => !READ_CATEGORIES.has(c)) ? "write" : "read" | ||||||||||||||||||
| return { queryType: queryType as "read" | "write", blocked } | ||||||||||||||||||
| } | ||||||||||||||||||
| // altimate_change end | ||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.