Skip to content

Detect database capabilities using probe queries #1596

@kmcginnes

Description

@kmcginnes

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    connectionIssues related to database connection management or optionsdatabase supportIssues related to adding or changing the databases servers or languages supportedenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions