feat: add Amazon Redshift connector#304
Conversation
|
@samuelayanshina is attempting to deploy a commit to the Kaelio Team on Vercel. A member of the Team first needs to authorize it. |
|
Friendly ping on this one. CI is green and it merges cleanly. The only red check is the Vercel preview deploy, which needs a maintainer to authorize it (external-contributor gate) rather than anything in the diff. No rush on your end, just flagging it's ready for review whenever someone has a cycle. And as noted above, happy to fold in IAM auth or |
Add a redshift connector modeled on the Postgres connector (Redshift speaks the Postgres wire protocol), using Redshift system views for introspection: SVV_TABLES and SVV_TABLE_INFO for tables/row counts, SVV_COLUMNS for columns. Wires the driver into the type union, dialect factory, driver registry, and connection schema.
Mirror the Postgres connector test suite with mocked pool factories, adapting SQL mock keys to the Redshift SVV_* introspection queries (svv_tables, svv_columns, svv_table_info) and the 5439 default port.
…spection Add a Redshift section to primary-sources.mdx with connection-config examples and honest feature/auth scoping (password auth + SVV_* metadata introspection; IAM auth and STL_QUERY history noted as follow-ups). Remove the copied live-database-introspection module and its tests since this connector does not wire the live-database ingest path.
696a607 to
de464cc
Compare
| @@ -0,0 +1,834 @@ | |||
| import { resolveStringReference } from '../shared/string-reference.js'; | |||
| import { getDialectForDriver } from '../../context/connections/dialects.js'; | |||
There was a problem hiding this comment.
This should use getSqlDialectForDriver('redshift'), not getDialectForDriver. The latter returns only KtxDialect, but this connector calls SQL-only methods such as generateSampleQuery, quoteIdentifier, and formatTableName, so the PR currently fails TypeScript with TS2339 errors.
| CASE WHEN table_type = 'VIEW' THEN 'v' ELSE 'r' END AS table_kind | ||
| FROM svv_tables | ||
| WHERE table_schema = ANY($1) | ||
| AND table_type IN ('TABLE', 'VIEW', 'EXTERNAL TABLE') |
There was a problem hiding this comment.
This filter drops normal Redshift tables. SVV_TABLES.table_type reports regular tables as BASE TABLE, not TABLE, so both listTables() and the similar introspection filter below can come back empty or view-only against a normal schema.
| SELECT table_schema AS schema_name, table_name AS table_name, | ||
| CASE WHEN table_type = 'VIEW' THEN 'v' ELSE 'r' END AS table_kind | ||
| FROM svv_tables | ||
| WHERE table_schema = ANY($1) |
There was a problem hiding this comment.
This copies the Postgres array-binding pattern, but Redshift does not accept a bound Postgres array for = ANY($1) here. listTables(['public']) and scoped scans can fail against a real cluster even though the mocks pass. Please generate a Redshift-valid scoped predicate, for example safe positional IN ($1, $2, ...) placeholders.
| } | ||
|
|
||
| getSampleValueAggregation(innerSql: string): string { | ||
| return `(SELECT STRING_AGG(CAST(value AS TEXT), CHR(31)) FROM (${innerSql}) AS relationship_profile_values)`; |
There was a problem hiding this comment.
This emits Postgres STRING_AGG, but Redshift's string aggregation function is LISTAGG. This path is used by relationship profiling, so enriched/relationship scans can fail or lose profile data once they hit this dialect. CHR(31) is fine on Redshift; the aggregate function is the issue.
| clickhouse: () => new KtxClickHouseDialect(), | ||
| mysql: () => new KtxMysqlDialect(), | ||
| postgres: () => new KtxPostgresDialect(), | ||
| redshift: () => new KtxRedshiftDialect(), |
There was a problem hiding this comment.
Adding the SQL dialect factory here is only half of the dialect wiring. packages/cli/src/context/sql-analysis/dialect.ts still does not map the redshift driver, so SQL analysis and local semantic-layer query compilation silently fall back to Postgres. That means Redshift-native SQL can be parsed/generated under the wrong dialect.
| }; | ||
| }, | ||
| }, | ||
| redshift: { |
There was a problem hiding this comment.
This registers Redshift for scanning, but it is still missing from the local warehouse identity path: no REDSHIFT ConnectionType, no DRIVER_TO_CONNECTION_TYPE entry in local-warehouse-descriptor.ts, and no Metabase/Looker mapping support. A hand-written Redshift connection can scan, but local ingest warehouse discovery and BI mapping flows do not treat it as a first-class warehouse target.
| | Field | Required | Applies to | Description | | ||
| |-------|----------|------------|-------------| | ||
| | `driver` | Yes | all connections | Connector driver such as `postgres`, `snowflake`, `bigquery`, `mysql`, `clickhouse`, `sqlserver`, `sqlite`, or `mongodb` | | ||
| | `driver` | Yes | all connections | Connector driver such as `postgres`, `redshift`, `snowflake`, `bigquery`, `mysql`, `clickhouse`, `sqlserver`, `sqlite`, or `mongodb` | |
There was a problem hiding this comment.
This advertises redshift as a normal primary-source driver, but ktx setup still does not know about it: the setup driver union, interactive options, default connection id, scope discovery, and URL/field setup path exclude Redshift. Either setup should support creating/managing Redshift connections in this PR, or the docs should clearly say manual config only.
| }; | ||
| const searchPathSchemas = searchPathSchemasFromConnection(merged); | ||
| if (searchPathSchemas.length > 0) { | ||
| config.options = `-c search_path=${searchPathSchemas.join(',')}`; |
There was a problem hiding this comment.
The Postgres connector now includes query_timeout_ms in the pool options as server-side statement_timeout and normalizes 57014 cancellations to the shared query exceeded Ns error. This fork dropped that behavior, which is user-visible because the docs describe query_timeout_ms as applying to all warehouses. The broader shared pg-wire base can be a follow-up, but the timeout behavior should not regress in the new connector.
# Conflicts: # docs-site/content/docs/integrations/primary-sources.mdx # packages/cli/src/context/scan/types.ts # packages/cli/test/context/connections/dialects.test.ts
Upstream split KtxDialect into base and SQL variants; quoteIdentifier now lives on KtxSqlDialect, so the connector must resolve its dialect via getSqlDialectForDriver like the Postgres connector does.
Extract the pg-protocol plumbing that Postgres and Redshift both need into connectors/shared/pg-wire.ts: pool factory and pool types, pool config resolution (parameterised by default port and connector label), URL parsing, read-only query prep, and query/row helpers. Postgres and Redshift now import this module instead of each carrying their own copy. Each connector keeps its own metadata introspection (Postgres pg_catalog, Redshift SVV_*), driver name, dialect, and default port (5432 / 5439). Postgres behaviour is unchanged: its test suite passes untouched, and its public exports are preserved as thin aliases over the shared types. Redshift picks up the statement_timeout / query-deadline handling it was previously missing, matching Postgres.
|
Thanks for the thorough review @andreybavt, and apologies for the slow reply, I missed the inline comments until now. Two are already addressed in the commits I pushed since:
On the rest — the Redshift-correctness ones are fair hits, and the mocks hid them:
On I'll push the fixes and ping you. |
…ng aggregate Three Redshift-correctness fixes from review: - SVV_TABLES.table_type reports regular tables as BASE TABLE, not TABLE. The old filter dropped every ordinary table from listTables() and introspection. - Redshift does not accept a bound array for '= ANY($n)'. Scoped predicates now expand to positional IN ($n, $n+1, ...) placeholders, derived from the value count only. - Redshift has no STRING_AGG; the dialect now emits LISTAGG for sample value aggregation used by relationship profiling. Tests assert the generated SQL for each, so the previous behaviour would now fail.
sqlglot has a first-class redshift dialect that differs from postgres in identifier normalization (case-insensitive) and array index offset, so falling through to the postgres default parsed Redshift SQL under the wrong dialect.
Register REDSHIFT as a ConnectionType, map the redshift driver to it in DRIVER_TO_CONNECTION_TYPE, and give it its sqlglot dialect in the connection-type dialect map, so a Redshift connection is treated as a first-class warehouse target by local ingest and semantic-layer compute rather than only being scannable.
ktx setup has no Redshift path yet, so the integration docs should not imply it can create one. Redshift is manual ktx.yaml config for now.
|
Pushed fixes for the review. Rundown: getSqlDialectForDriver instead of getDialectForDriver (d207a8e). The BASE TABLE and ANY($n) bugs were mock-hidden: the fake pool returned table_kind already CASE-transformed, so neither the table_type filter nor the scoped predicate was ever exercised. Tests now assert the generated SQL for each, so the old behaviour fails. Metabase/Looker mapping. I'd rather not guess the keys, Looker exposes several Redshift dialects (Amazon Redshift, 2.1+, Serverless 2.1+) and its identifiers aren't guessable from outside (awsathena, bigquery_standard_sql); same for Metabase's engine string. A wrong key silently never matches, which is worse than omitting it. This also mirrors how Athena landed: connector in #309, warehouse identity + BI mapping in #332. Happy to do it here if you can confirm the exact identifiers, or as a follow-up. Also rebased onto latest main. Full suite green (only the pre-existing repo-fetch failure), pre-commit clean including biome and knip. |
|
@andreybavt whenever you have a cycle, all review comments are addressed and CI is green. The two open questions are whether you want the Metabase/Looker mapping in this PR (I'd need the exact Looker dialect and Metabase engine identifiers for Redshift) or as a follow-up like Athena's #332, and same for ktx setup. Happy either way. |
Closes #161
Adds a
redshiftconnector for read-only schema introspection, table/column sampling, column statistics, and read-only SQL execution against Amazon Redshift.Approach
Redshift speaks the PostgreSQL wire protocol, so the connector is modeled on
connectors/postgresand reuses thepgdriver for connection plumbing. Metadata introspection is swapped to Redshift system views for accuracy:SVV_TABLESfor the table/view list (includingEXTERNAL TABLE/ Spectrum)SVV_TABLE_INFO.tbl_rowsfor accurate row counts (Redshift'spg_class.reltuplesis unreliable)SVV_COLUMNSfor column metadatainformation_schema(Redshift stores declared constraints there; it does not enforce them, and there is no betterSVV_*source)The driver is wired into the connection driver union, dialect factory, driver registry, and warehouse connection schema. Default port is
5439.Tests
test/connectors/redshift/connector.test.tswith mocked pool factories covering introspection, sampling, column stats, read-only SQL, schema/table listing, and pool error handlingDocs
Added a Redshift section to
primary-sources.mdxwith connection-config examples and a feature/auth matrix.Scope (honest about what's not here)
This is a focused v1. Two acceptance-criteria items from the issue are intentionally deferred so this PR stays reviewable, and I've marked them as such in the docs:
GetClusterCredentials) is a planned follow-up.STL_QUERY)hasHistoricSqlReaderisfalse; query-history ingestion is a planned follow-up.Happy to fast-follow with either (or fold them into this PR) based on what you'd prefer to see first.
(Following up from the MySQL
columnStatswork in #233.)