Building a provider
A provider plugin teaches SQL Explorer to talk to one database engine. It implements a single
interface, IDbProvider, from the public SDK — which is MIT-licensed precisely so anyone can
build and ship an engine without asking us.
SqlExplorer.Sdk. No reference to the host's Core or App
assemblies is needed — or allowed. That boundary is what lets a plugin carry its own driver version
without colliding with anyone else's.The contract: IDbProvider
| Member | Purpose |
|---|---|
DisplayName | Human-readable engine name, e.g. "PostgreSQL". |
Icon | Optional glyph or image for connection nodes. ProviderIconLoader.Load(typeof(YourProvider), "🔧") embeds an icon.png next to the project if there is one, and falls back to the emoji otherwise. |
Dialect | Your ISqlDialect — quoting, qualification and pagination. |
ConnectionFields | Declares the connection dialog. The host renders the form from this, so a provider never writes UI code. |
BuildConnectionString | Composes the driver connection string from the submitted values, including the secret just fetched from the OS keychain. |
TestConnectionAsync | Opens and validates a connection — the "Test connection" button. |
GetChildNodesAsync | Lists the children of one tree node, on demand, so a large server is never introspected all at once. ancestors is the path from the connection root; empty for the top level. Your engine decides its own hierarchy: server → database → schema → tables, or something flatter, as SQLite does. |
ExecuteQueryAsync | Runs free-form SQL, returns a QueryResult. |
ExecuteBatchAsync | Runs parameterised statements in one transaction, rolling back on failure. This is the commit step of the editable grid: the host generates the quoted INSERT/UPDATE/DELETE, the provider owns only parameter binding and the transaction. |
The dialect: ISqlDialect
Keywords drives syntax highlighting; QuoteIdentifier and QualifyName keep
generated SQL correct for your engine; Paginate wraps a query in the engine's own paging syntax
(LIMIT/OFFSET, OFFSET/FETCH, …) and powers the Browse tab's paging and sorting.
The DTOs you'll meet
| Type | What it carries |
|---|---|
ConnectionField | One field of the connection dialog: Text | Password | Number | File | Bool, plus grouping and "advanced" metadata. A Password field goes to the OS keychain and is never written to the config file. |
ConnectionProfile | What every provider method receives: the resolved connection string plus the optional Database context chosen in the UI. |
DbNodeKind | The tree's node kinds — databases, schemas, tables, views, columns, indexes, sequences, routines, triggers, foreign keys, and a generic Object for anything engine-specific. |
DbTreeNode / DbNodeRef | A node you return / a path segment you receive. DbTreeNode.Count lets a folder show "Tables (22)" without being expanded. |
QueryResult | Columns, rows, affected count, elapsed. Each ResultColumn carries edit metadata (BaseTable, IsKey, AllowDbNull, …) — that's what tells the host whether a grid is safely editable. |
SqlStatement / SqlParam | A parameterised statement with named placeholders. |
Optional capabilities
Beyond the required members, IDbProvider carries a set of optional capabilities as
default-interface members that return "nothing extra", so a minimal provider ignores them entirely. The
convention is consistent: a flag defaults to false, a nullable return to null, a
collection to empty, and the paired builder throws until the flag turns it on.
Examples already in the SDK: SupportsActivityMonitor, CanManageUsers,
ParseConnectionString, CreateCapabilities, GetObjectDefinitionAsync,
ContainerRecipe.
Non-SQL engines
By default the host assumes SQL: it generates SELECT/DROP/TRUNCATE text
itself for the tree's convenience actions. A document store opts out and owns that generation instead.
MongoDB is the reference implementation: IsSqlBased => false,
BuildNodeQuery returns db.coll.find({}).limit(1000), and
BuildAlterStatement returns db.coll.drop(). Both builders are available to SQL
providers too, as a plain override hook for any single action.
Server version
Report the engine's version and the host shows it next to your DisplayName —
PostgreSQL 16.2 in the status bar and the connect message. Return null and the name shows
alone.
Step by step
1. Create the project
2. Implement the two interfaces
The SQLite provider is the simplest reference — no server, database or schema layers, tables straight under the connection root. For an engine with server → database → schema layering, read the Postgres or SQL Server provider.
3. Write the manifest
The id is the engine's permanent identity — there is no host-side enum of engines, which is
exactly what keeps the set open. Saved connections reference it, so pick something short, lowercase and
stable. Full field reference: Manifest schema.
4. Ship it
A plugin is a folder next to the host executable:
The .deps.json matters: it is what the plugin's load context resolves your driver against. See
Discovery & loading.
SQL Explorer