#Adding a New SQL Dialect
This repository supports two runtime models:
- built-in runtime dialects in
src/dialects/(netezza,sqlite) - optional runtime dialects delivered from sibling
extensions/<dialect>/packages
The most important rule is:
Keep portable SQL semantics in core, and keep driver/runtime specifics in the dialect runtime package.
That means traits, SQL authoring, and shared naming/qualification behavior belong in core-owned files, while connection classes, system queries, DDL generation, and extension activation stay with the runtime dialect package.
#Architecture Overview
graph TD
A["Extension entry point"] --> B["ConnectionManager"]
B --> C["DatabaseDialectRegistry"]
C --> D["DatabaseDialect"]
D --> E["traits"]
D --> F["metadataProvider"]
D --> G["sqlAuthoring"]
D --> H["runtime connection"]
H --> I["query execution / DDL"]
F --> J["schema browsing / completion"]
G --> K["formatter / validator / LSP"]
style D fill:#e1f5ff
style E fill:#fff9c4
style F fill:#fff9c4
style G fill:#fff9c4The core extension owns portable SQL semantics and the registry/contract system. Built-in dialects also own runtime code in src/dialects/*, while optional dialect runtimes live in extensions/<dialect>/src/ and register themselves through the public API.
#1. Decide whether the dialect is built-in or optional
Use a built-in runtime dialect only when the core extension can ship the driver/runtime dependency safely.
Use an optional runtime dialect when the driver is native, large, platform-specific, or otherwise unsuitable for the core VSIX. In that case:
- core still owns the SQL semantics (
traits.ts,sql/authoring.ts) - the sibling extension owns runtime registration and driver-specific implementation
#2. Create the core-owned dialect metadata
Create a dialect folder under src/dialects/<dialect>/ for the parts that must be available before optional extensions activate.
Typical structure:
src/dialects/<dialect>/
├── traits.ts
└── sql/
└── authoring.tsBuilt-in runtime dialects usually also have:
src/dialects/<dialect>/
├── index.ts
├── connectionForm.ts
├── advancedFeatures.ts
├── metadata/
│ └── provider.ts
├── runtime.ts
├── traits.ts
└── sql/
└── authoring.ts#3. Define dialect traits
Traits drive identifier formatting, qualification rules, and path completion behavior.
import { createDatabaseDialectTraits } from '../../contracts/database';
export const myDialectTraits = createDatabaseDialectTraits({
identifiers: {
quoteStyle: 'double',
unquotedIdentifierPattern: /^[A-Z_][A-Z0-9_]*$/
},
qualification: {
twoPartNameStyle: 'schema-object',
twoPartContainerPreference: 'database-over-schema',
supportsThreePartName: true,
databaseOnlyReferenceStyle: 'double-dot'
},
completion: {
singleDotPathNamespace: 'schema',
supportsDoubleDotPath: false
},
objects: {
supportsIndexes: true
}
});Current meanings:
identifiers.quoteStyle: SQL identifier quoting style used by the formatter/helpersidentifiers.unquotedIdentifierPattern: names that do not require quoting for that dialectqualification.twoPartNameStyle: whetherA.Bshould be treated asschema.objectordatabase.objectqualification.supportsThreePartName: whetherdatabase.schema.objectis emitted/treated as native notationqualification.databaseOnlyReferenceStyle: howdatabase + objectshould be rendered when no schema is providedcompletion.singleDotPathNamespace: howdb./schema.style completion paths should be interpretedcompletion.supportsDoubleDotPath: whetherDB..TABLEcompletion paths are validobjects.supportsIndexes: currently a capability-style flag for future shared object handling
#4. Register the traits in the static core map
Add the new traits entry in src/core/dialectTraits.ts.
import { myDialectTraits } from '../dialects/myDialect/traits';
const DIALECT_TRAITS_BY_KIND = {
// ...existing kinds
myDialect: myDialectTraits
};Also update SUPPORTED_DATABASE_KINDS and aliases in src/contracts/database/index.ts when introducing a new kind.
#5. Add SQL authoring in core
SQL authoring is loaded eagerly from core so formatter, validator, completion, and the LSP keep working before optional extensions activate.
For every new dialect, add a core-owned authoring module:
src/dialects/<dialect>/sql/authoring.tsThen register it in:
src/core/sqlAuthoringRegistry.ts
If the runtime dialect lives in extensions/<dialect>, the extension-side *SqlAuthoring.ts should be only a thin re-export or adapter.
#6. Add or update the runtime dialect package
For a built-in runtime dialect, wire the runtime implementation directly in src/dialects/<dialect>/index.ts.
For an optional runtime dialect, add or update the sibling extension package:
extensions/<dialect>/src/
├── extension.ts
├── <dialect>Dialect.ts
├── <dialect>Connection.ts
├── <dialect>SchemaProvider.ts
├── <dialect>DdlGenerator.ts
└── ...other driver-specific filesThe runtime dialect object must include traits from the core-owned dialect folder:
import { myDialectTraits } from '../../../src/dialects/myDialect/traits';
export const myDialect: DatabaseDialect = {
kind: 'myDialect',
displayName: 'My Dialect',
capabilities: createDatabaseCapabilities(),
traits: myDialectTraits,
metadataProvider: myMetadataProvider,
sqlAuthoring: mySqlAuthoring,
getConnectionConstructor() {
return MyConnection as unknown as DatabaseConnectionStaticConstructor;
},
createConnection(config) {
return new MyConnection(config);
}
};Register optional runtime dialects from the extension entry point through the public API.
#7. Let registration validation protect you
Runtime dialect registration now validates trait consistency in src/core/factories/databaseDialectRegistry.ts.
Current validation rules include:
twoPartNameStyle: 'database-object'requiressupportsThreePartName: falsetwoPartNameStyle: 'database-object'requiressingleDotPathNamespace: 'database'singleDotPathNamespace: 'schema-or-database'requiressupportsDoubleDotPath: truesingleDotPathNamespace: 'schema-or-database'is only valid forschema-objectdialectsunquotedIdentifierPatternmust be a realRegExpand reject obviously invalid identifiers
If registration fails, fix the traits first instead of adding more special-case logic in shared callers.
#8. Understand the dialect contract
Each dialect registration must provide a valid DatabaseDialect implementation with these pillars:
kind,displayName, andcapabilitiestraitsfor identifiers / qualification / completion semanticsmetadataProviderquery builders that return non-empty SQL stringssqlAuthoringassets for completion, formatting, and validationgetConnectionConstructor()andcreateConnection(config)for the runtime connection
The shared metadata-provider contract is defined in src/contracts/database/metadataProvider.ts. In practice, a dialect should always cover at least:
buildListDatabasesQuery()buildListSchemasQuery(database)buildListTablesQuery(database, schema)buildListViewsQuery(database, schema)buildListProceduresQuery(database, schema)buildColumnsWithKeysQuery(database, options)buildObjectSearchQuery(database, likePattern)buildViewSourceSearchQuery(database, options)buildProcedureSourceSearchQuery(database, options)
Trait consistency is validated during runtime registration by src/core/dialectTraitsValidator.ts. If registration fails, fix the dialect contract first instead of adding new special-case branches in shared core code.
#9. Add tests before wiring everything broadly
Required CI-safe coverage:
src/__tests__/dialectTraits.test.tssrc/__tests__/optionalDialects.unit.test.tssrc/__tests__/contracts/databaseDialectContract.test.tssrc/__tests__/sqlAuthoringRegistry.test.tssrc/__tests__/completionEngine.test.ts
Add targeted dialect-specific tests when the dialect has special notation (for example DB..TABLE, database.table, or dialect-specific quoting behavior) or complex system-query behavior.
#10. Prefer extending traits over adding new switches
When you need new shared behavior, prefer one of these approaches:
- extend
DatabaseDialectTraits - update the core trait map
- teach shared helpers to consume the new trait
Avoid sprinkling new if (databaseKind === ...) checks through shared core code unless there is no reasonable contract shape for the behavior.
#11. Pre-registration checklist
Before considering a dialect addition complete, verify:
#Traits
-
twoPartNameStylematches the database notation (schema.objectvsdatabase.object) -
supportsThreePartNameis only enabled when the dialect truly supportsdatabase.schema.object -
singleDotPathNamespacematches the intended completion behavior -
unquotedIdentifierPatternmatches representative unquoted identifiers for that dialect
#Metadata provider
- All required builder methods return non-empty SQL strings
- Database/schema filtering matches the dialect semantics
- System-object filtering is handled where needed
#SQL authoring
- Completion keywords are populated
- Formatter keyword sets are populated
- Validation exposes builtin functions and type metadata
- Quality rules are present when the dialect supports them
#Validation commands
npm run check-types
npm run lint
npm run build
npm run test:validateUse targeted test runs while iterating, but finish with the full gate above.