Detect database capabilities using probe queries
- Dominant language
- TypeScript
- Stars
- 481
- Forks
- 108
- Avg merge
- 6d 8h
- Merged PRs (30d)
- 5
Description
## Description
Different graph database engines and versions support different query language features. Currently, query templates use a one-size-fits-all approach that cannot take advantage of newer capabilities (e.g., case-insensitive matching) or gracefully degrade when features are unavailable.
We need a capabilities detection system that runs probe queries against the connected database to determine which features are supported, then stores the results on the `RawConfiguration` so query templates can adapt accordingly.
### Example: Case-Insensitive Search
| Engine | Approach |
|---|---|
| Gremlin 3.7.1+ | `asString().toLower()` step |
| Gremlin 3.6.0+ | `TextP.regex()` with `(?i)` flag |
| Gremlin < 3.6 | Case-sensitive `containing()` only |
| openCypher | `toLower()` (always available per openCypher spec v9) |
| SPARQL | `regex()` with `"i"` flag or `LCASE()` (W3C SPARQL 1.1 standard) |
## Preferred Solution
### 1. Define capabilities as a const type with query-language-prefixed keys
Following the same pattern as `queryEngineOptions`:
```typescript
export const databaseCapabilityOptions = [
"gremlin:toLower",
"gremlin:regex",
] as const;
export type DatabaseCapability = (typeof databaseCapabilityOptions)[number];
```
### 2. Store capabilities on `RawConfiguration`
Capabilities are a per-connection concern persisted alongside the schema:
```typescript
export type RawConfiguration = {
id: ConfigurationId;
displayLabel?: string;
connection?: ConnectionConfig;
schema?: SchemaStorageModel;
capabilities?: DatabaseCapability[];
};
```
### 3. Expose `hasCapability` on `NormalizedConnection`
The `Set` is abstracted away from consumers. `normalizeConnection` accepts the capabilities array from `RawConfiguration` and exposes a typed method:
```typescript
export function normalizeConnection(
connection: ConnectionConfig,
capabilities?: DatabaseCapability[],
) {
const capabilitySet = new Set(capabilities);
return {
...connection,
// ...existing normalization...
hasCapability(key: DatabaseCapability) {
return capabilitySet.has(key);
},
};
}
```
`activeConnectionAtom` and `mergeConfiguration` would pass `config.capabilities` through to `normalizeConnection`. Since `NormalizedConnection` is `ReturnType`, the method is automatically available on the type.
### 4. Implement probe queries per engine
Run lightweight queries that succeed or fail to detect support:
- **Gremlin toLower** (3.7.1+): `g.inject("TEST").asString().toLower()`
- **Gremlin regex** (3.6.0+): `g.V().limit(0).has("_x", regex(".*"))`
- **openCypher**: No probing needed — `toLower()` is part of the openCypher spec v9
- **SPARQL**: No probing needed — `regex()` and `LCASE()` are part of the W3C SPARQL 1.1 standard
For openCypher and SPARQL, probing simply populates the capabilities array with their inherent capabilities.
### 5. Run probing at connection establishment time
Probing could run as part of:
- The test connection flow (#1289)
- Schema sync
- Or both
Results are persisted on `RawConfiguration` so they survive page reloads.
### 6. Use capabilities in query templates
Templates conditionally generate different query syntax:
```typescript
if (connection.hasCapability("gremlin:toLower")) {
// use toLower step
} else if (connection.hasCapability("gremlin:regex")) {
// use regex with (?i)
} else {
// case-sensitive fallback
}
```
### Initial capability candidates
- **`gremlin:toLower`** — TinkerPop 3.7.1+ string manipulation step
- **`gremlin:regex`** — TinkerPop 3.6.0+ regex predicates
This list can grow over time as we identify more features that vary across engines and versions.
## Related Issues
- Related to #1289
Contributor guide
Research direction
Start with RawConfiguration, normalizeConnection, activeConnectionAtom, and mergeConfiguration to trace how connection state is stored and established. Review the test connection flow (#1289), schema sync, and query templates before choosing where probes run. Done means supported capabilities are persisted, exposed through hasCapability, and used by templates with the specified fallbacks.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- backend-api-design, databases
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 30/100