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:
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:
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:
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<typeof normalizeConnection>, 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:
Results are persisted on RawConfiguration so they survive page reloads.
6. Use capabilities in query templates
Templates conditionally generate different query syntax:
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
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
RawConfigurationso query templates can adapt accordingly.Example: Case-Insensitive Search
asString().toLower()stepTextP.regex()with(?i)flagcontaining()onlytoLower()(always available per openCypher spec v9)regex()with"i"flag orLCASE()(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:2. Store capabilities on
RawConfigurationCapabilities are a per-connection concern persisted alongside the schema:
3. Expose
hasCapabilityonNormalizedConnectionThe
Setis abstracted away from consumers.normalizeConnectionaccepts the capabilities array fromRawConfigurationand exposes a typed method:activeConnectionAtomandmergeConfigurationwould passconfig.capabilitiesthrough tonormalizeConnection. SinceNormalizedConnectionisReturnType<typeof normalizeConnection>, the method is automatically available on the type.4. Implement probe queries per engine
Run lightweight queries that succeed or fail to detect support:
g.inject("TEST").asString().toLower()g.V().limit(0).has("_x", regex(".*"))toLower()is part of the openCypher spec v9regex()andLCASE()are part of the W3C SPARQL 1.1 standardFor 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:
Results are persisted on
RawConfigurationso they survive page reloads.6. Use capabilities in query templates
Templates conditionally generate different query syntax:
Initial capability candidates
gremlin:toLower— TinkerPop 3.7.1+ string manipulation stepgremlin:regex— TinkerPop 3.6.0+ regex predicatesThis list can grow over time as we identify more features that vary across engines and versions.
Related Issues