| Property | Value |
|---|---|
| Dialect name | athena |
| Extends | TrinoDialect (trino) |
| Driver | @aws-sdk/client-athena |
| Cloud SDK | @aws-sdk/client-athena |
| Default port | 8080 |
| Local runtime | none — hosted service only |
athenaathena, athena-driverOFFSET before LIMIT, ARRAY(...) types, no key constraints, no identity columns — is Trino's.AthenaDialect therefore extends TrinoDialect, the same way TimescaleDBDialect extends PostgresDialect and TiDBDialect extends MySQLDialect:export class AthenaDialect extends TrinoDialect {
readonly library = '@aws-sdk/client-athena';
// ...
}connect() builds an AthenaClient; queries go out through StartQueryExecution → GetQueryExecution → GetQueryResults.string, int, binary, array<t>) in DDL; Trino names (VARCHAR, INTEGER, VARBINARY, ARRAY(t)) in queries.CREATE TABLE (cols). A table is a CREATE EXTERNAL TABLE ... LOCATION 's3://...', an Iceberg CREATE TABLE ... TBLPROPERTIES ('table_type'='ICEBERG'), or a CTAS.INSERT INTO works everywhere; UPDATE/DELETE/MERGE work on Iceberg tables only; there is no TRUNCATE.$ npm install ts-prorm-orm @aws-sdk/client-athena$ pnpm add ts-prorm-orm @aws-sdk/client-athena$ yarn add ts-prorm-orm @aws-sdk/client-athena$ bun add ts-prorm-orm @aws-sdk/client-athenaimport { Prorm } from 'ts-prorm-orm';
export const db = new Prorm({
dialect: 'athena',
database: 'analytics', // the Glue database
dialectOptions: {
region: 'us-east-1',
workGroup: 'primary',
outputLocation: 's3://my-bucket/athena-results/',
},
});import { DataTypes } from 'ts-prorm-orm';
const User = db.define('User', {
id: { type: DataTypes.INTEGER(), primaryKey: true, autoIncrement: true },
email: { type: DataTypes.STRING(255), allowNull: false, unique: true },
name: { type: DataTypes.STRING(100) },
});
await db.sync(); // CREATE TABLE — see Generated SQL below[!WARNING] db.sync()cannot create this table
Athena cannot create the table 'users' without an S3location. Athena stores nothing itself — every table points at a prefix in S3, e.g.{ location: 's3://my-bucket/warehouse/events/' }.
import { Op } from 'ts-prorm-orm';
await User.create({ email: 'ada@example.com', name: 'Ada' });
const page = await User.findAll({
where: { name: { [Op.like]: 'A%' } },
order: [['id', 'ASC']],
limit: 20,
offset: 40,
});query(), captured with the driver stubbed out. The model layer inlines the values.await User.create({ email: 'ada@example.com', name: 'Ada' });[!WARNING] No row-level update
UPDATE is not supported on athena. Athena runs UPDATE only against Apache Iceberg tables. Configure the dialect withtableType: 'iceberg'when your tables are Iceberg, or rewrite the change as an INSERT INTO plus a CTAS rebuild for Hive tables.
[!WARNING] No row-level delete
DELETE is not supported on athena. Athena runs DELETE only against Apache Iceberg tables. Configure the dialect withtableType: 'iceberg'when your tables are Iceberg, or rewrite the change as an INSERT INTO plus a CTAS rebuild for Hive tables.
[!WARNING] No upsert
MERGE (upsert) is not supported on athena. Athena runs MERGE (upsert) only against Apache Iceberg tables. Configure the dialect withtableType: 'iceberg'when your tables are Iceberg, or rewrite the change as an INSERT INTO plus a CTAS rebuild for Hive tables.
await User.findAll({
where: { name: { [Op.like]: 'A%' } },
order: [['id', 'ASC']],
limit: 20,
offset: 40,
});[!CAUTION] No transactions
Transactions (startTransaction) is not supported on athena. Athena executes each statement independently with no session and no BEGIN/COMMIT. An individual Iceberg write is atomic on its own; nothing spans two statements.
sample on the select builder reads a random fraction of the table, for estimates over large tables:const sample = db.getDialectInstance()!.buildSelectQuery({
tableName: 'users',
sample: { method: 'BERNOULLI', percent: 10 },
});
await db.query(sample.sql);STRING → VARCHAR, auto-increment INTEGER → INTEGER, BOOLEAN → BOOLEAN, DATE → TIMESTAMP, UUID → VARCHAR, JSON → VARCHAR. The type comparison lines the major engines up side by side.export interface AthenaDialectOptions extends TrinoDialectOptions {
region?: string; // e.g. 'us-east-1'
workGroup?: string; // default 'primary'
outputLocation?: string; // 's3://my-bucket/athena-results/'
encryptionOption?: 'SSE_S3' | 'SSE_KMS' | 'CSE_KMS';
kmsKey?: string;
resultReuseMaxAgeMinutes?: number;
credentials?: { accessKeyId: string; secretAccessKey: string; sessionToken?: string };
endpoint?: string;
pollIntervalMs?: number; // default 200
maxPollIntervalMs?: number; // default 5000
queryTimeoutMs?: number; // default 300000
pageSize?: number; // default 1000 (Athena's maximum)
tableType?: 'hive' | 'iceberg'; // default 'hive'
}import { Prorm } from 'ts-prorm-orm';
const prorm = new Prorm({
dialect: 'athena',
database: 'analytics', // the Glue database
dialectOptions: {
region: 'us-east-1',
workGroup: 'analytics-wg',
outputLocation: 's3://my-bucket/athena-results/',
catalog: 'AwsDataCatalog', // enables "catalog"."db"."table" in queries
tableType: 'iceberg', // unlocks UPDATE / DELETE / MERGE
},
});connect():connect() makes no AWS call. The only cheap probe (GetWorkGroup) needs an IAM permission that query-only roles routinely lack, and the alternative — running a statement — is a billed query on every connect. Credentials are validated by the first real query. What connect() does check is that results have somewhere to go:new AthenaDialect({ region: 'us-east-1' }).connect();
// throws: "Athena needs somewhere to write query results: set `outputLocation` ..."catalog, database and workGroupcatalog is the Athena data catalog ('AwsDataCatalog' for Glue, or a federated Lambda catalog). It occupies the same slot as Trino's connector catalog and, when set, turns on three-part "catalog"."database"."table" naming in queries.database (or schema) is the Glue database. It is what information_schema introspection is scoped to, and what DDL statements qualify with.workGroup selects the engine version, the enforced result location and the data-scanned limits.d.escapeId('events'); // "events" — queries (inherited from Trino)
d.escapeHiveId('events'); // `events` — DDL
d.quoteTable('events'); // "analytics"."events" (with a catalog: three parts)
d.quoteHiveTable('events'); // `analytics`.`events`quoteHiveTable never emits a three-part name even when catalog is configured.CREATE EXTERNAL TABLElocation is mandatory:d.buildCreateTableSQL(
'events',
{ id: { type: DataTypes.BIGINT }, name: { type: DataTypes.STRING } },
{ location: 's3://bucket/events/' },
);d.buildCreateTableSQL('events', { id: { type: DataTypes.BIGINT } });
// throws: "Athena cannot create the table 'events' without an S3 `location`. ..."export interface AthenaTableOptions extends TrinoTableOptions {
location?: string; // LOCATION 's3://...' (required)
partitionedBy?: Record<string, DataType>; // PARTITIONED BY (`day` string)
partitionTransforms?: string[]; // Iceberg: PARTITIONED BY (day(ts), bucket(16, id))
storedAs?: string; // STORED AS PARQUET
serde?: string; // ROW FORMAT SERDE '...'
serdeProperties?: Record<string, string>; // WITH SERDEPROPERTIES (...)
rowFormatDelimited?: string; // ROW FORMAT DELIMITED ...
bucketedBy?: string[]; // CLUSTERED BY (...) INTO n BUCKETS
bucketCount?: number;
tblProperties?: Record<string, string | number | boolean>;
partitionProjection?: AthenaPartitionProjection;
tableType?: 'hive' | 'iceberg'; // per-table override
external?: boolean; // default true for Hive
}// Hive partition columns live in the S3 key, never in the data file.
d.buildCreateTableSQL('events', { day: { type: DataTypes.STRING } },
{ location: 's3://b/e/', partitionedBy: { day: DataTypes.STRING } });
// throws: "'day' is declared both as a column and in `partitionedBy` ..."
d.buildCreateTableSQL('events', { id: { type: DataTypes.BIGINT } },
{ location: 's3://b/e/', bucketedBy: ['id'] });
// throws: "`bucketedBy` on 'events' needs a `bucketCount` ..."EXTERNAL, carries TBLPROPERTIES ('table_type' = 'ICEBERG'), and partitions by transforms over ordinary columns (which stay in the column list):new AthenaDialect({ database: 'analytics', tableType: 'iceberg' }).buildCreateTableSQL(
'events',
{ id: { type: DataTypes.BIGINT }, ts: { type: DataTypes.DATE } },
{ location: 's3://bucket/events/', format: 'PARQUET',
partitionTransforms: ['day(ts)', 'bucket(16, id)'] },
);table_type property Athena silently creates a plain Hive table, and the first UPDATE fails with "not an Iceberg table".CREATE TABLE AS SELECT| Athena | Trino |
|---|---|
external_location (Hive) / location (Iceberg) | — |
partitioned_by = ARRAY[...] | partitioning = ARRAY[...] |
bucketed_by / bucket_count | — |
write_compression, field_delimiter | — |
table_type = 'ICEBERG' | — |
d.buildCreateTableAsSelectSQL('summary', 'SELECT day, count(*) AS n FROM events GROUP BY day', {
location: 's3://bucket/summary/',
format: 'PARQUET',
partitionedBy: ['day'],
writeCompression: 'ZSTD',
});partitioning spelling is accepted as an alias and rendered as partitioned_by, so a query written against the Trino dialect keeps working. The CTAS target is double-quoted: CTAS is a query statement, not Hive DDL.await d.repairTable('events');
// MSCK REPAIR TABLE `analytics`.`events` — scan S3 for key=value directories
await d.addPartitionSpec('events', { day: '2024-01-01' }, {
location: 's3://bucket/events/2024-01-01/', ifNotExists: true,
});
// ALTER TABLE `analytics`.`events` ADD IF NOT EXISTS PARTITION (`day` = '2024-01-01')
// LOCATION 's3://bucket/events/2024-01-01/'
await d.dropPartitionSpec('events', { day: '2024-01-01' }, { ifExists: true });
await d.showPartitions('events'); // SHOW PARTITIONS `analytics`.`events`buildAddPartitionSQL / buildDropPartitionSQL are the synchronous builders behind the last two.TBLPROPERTIES.d.buildCreateTableSQL('events', { id: { type: DataTypes.BIGINT } }, {
location: 's3://bucket/events/',
partitionedBy: { region: DataTypes.STRING, day: DataTypes.STRING },
partitionProjection: {
storageLocationTemplate: 's3://bucket/events/${region}/${day}/',
columns: {
region: { type: 'enum', values: ['us-east-1', 'eu-west-1'] },
day: { type: 'date', range: ['2020/01/01', 'NOW'], format: 'yyyy/MM/dd',
interval: 1, intervalUnit: 'DAYS' },
},
},
});{ region: { type: 'enum' } } // throws: is type 'enum' but lists no `values`
{ day: { type: 'date', format: 'yyyy' } } // throws: is type 'date' but has no `range`
{ day: { type: 'date', range: ['2020', 'NOW'] } } // throws: is type 'date' but has no `format`query() submits, polls to a terminal state, then pages GetQueryResults to exhaustion:pollIntervalMs and backs off ×1.5 to maxPollIntervalMs, because GetQueryExecution is itself rate limited.queryTimeoutMs is cancelled with StopQueryExecution before the error is thrown — an abandoned Athena query keeps scanning, and keeps billing.MaxResults is 1000, Athena's maximum.SELECT result with a row that repeats the column names. It is dropped only when it actually is the header (every cell equals its column name); dropping row 0 unconditionally would eat a real row from an INSERT/CTAS/UNLOAD result.DDL statements skip GetQueryResults entirely.INSERT/UPDATE/DELETE/MERGE report their effect as UpdateCount, which becomes rowCount.bigint too large for an IEEE double is left as its exact digits rather than silently rounded:(await d.query('SELECT id FROM t')).rows[0].id; // '9007199254740993', not 9007199254740992d.getLastQueryExecutionId(); // 'a1b2c3d4-...' — the key for Athena history and CloudTrail
d.getLastQueryStatistics(); // { dataScannedInBytes, engineExecutionTimeInMillis, ... }dataScannedInBytes is what Athena bills.queryStream() uses the shared paginated pager (SELECT * FROM (<sql>) AS ... OFFSET n LIMIT m). Athena has no server-side cursor: GetQueryResults pages a finished result set, so a page of a new query is a new — and separately billed — query.$1. That is harmless: this library never sends bind parameters on the wire. query() inlines them through the inherited replaceReplacements()/escape() before submitting, which is what every dialect here already does.await d.query('SELECT * FROM t WHERE name = $1', { replacements: ["o'brien"] });
// submitted: SELECT * FROM t WHERE name = 'o''brien'buildInsertQuery and friends still carry $1 placeholders (they are Trino's, inherited unchanged) together with the values array — pair them back up if you execute them somewhere other than through this dialect.version(). getDatabaseVersion() reads the effective engine version off the workgroup:await d.getDatabaseVersion(); // 'Athena engine version 3' (via GetWorkGroup)getDataTypeSql() is the query/CTAS one (Trino's, inherited with two corrections); getHiveDataTypeSql() is the DDL one.| ORM type / key | Query (getDataTypeSql) | DDL (getHiveDataTypeSql) |
|---|---|---|
STRING / TEXT | VARCHAR | string |
CHAR | CHAR(n) | char(n) |
INTEGER | INTEGER | int |
BIGINT | BIGINT | bigint |
FLOAT | REAL | float |
DOUBLE | DOUBLE | double |
DECIMAL | DECIMAL(p, s) | decimal(p, s) |
BOOLEAN | BOOLEAN | boolean |
DATE | TIMESTAMP | timestamp |
DATEONLY | DATE | date |
TIME | TIME | throws |
BLOB | VARBINARY | binary |
JSON / JSONB | VARCHAR | string |
UUID | VARCHAR | string |
ARRAY | ARRAY(elementType) | array<elementType> |
'DATETIME' / 'DATETIME(n)' | TIMESTAMP / TIMESTAMP(n) | timestamp (no precision) |
JSON and UUID do not exist in Athena, in either vocabulary or either engine version. JSON lives in a string column and is read with json_extract; a UUID is a string. Emitting JSON or UUID produces a type error from Athena. The correction applies inside an ARRAY element type too.TIME column throws rather than being mapped to something Athena will reject:d.getHiveDataTypeSql(DataTypes.TIME);
// throws: "A TIME column is not supported on athena. Athena table definitions have no
// time-of-day type. Store the value as a `string`, or as a `timestamp` ..."| Statement | Hive table | Iceberg table |
|---|---|---|
SELECT | yes | yes |
INSERT INTO | yes | yes |
UPDATE | throws | yes |
DELETE | throws | yes |
MERGE (upsert) | throws | yes |
TRUNCATE | throws — Athena has no TRUNCATE | unqualified DELETE FROM |
OPTIMIZE / VACUUM | throws | yes |
tableType:const hive = new AthenaDialect({ database: 'analytics' });
hive.buildUpdateQuery('events', { n: 1 }, { id: 1 });
// throws: "UPDATE is not supported on athena. Athena runs UPDATE only against Apache
// Iceberg tables. Configure the dialect with `tableType: 'iceberg'` ..."
const iceberg = new AthenaDialect({ database: 'analytics', tableType: 'iceberg' });
iceberg.buildUpsertQuery('events', { id: 1, n: 2 }, { conflictFields: ['id'] });
// MERGE INTO "events" AS t USING (VALUES ($1, $2)) AS s ("id", "n") ON t."id" = s."id"
// WHEN MATCHED THEN UPDATE SET "n" = s."n"
// WHEN NOT MATCHED THEN INSERT ("id", "n") VALUES (s."id", s."n")INSERT INTO writes new S3 objects rather than appending to existing ones, so many small inserts produce many small files. On Iceberg, optimizeTable() (OPTIMIZE ... REWRITE DATA USING BIN_PACK) compacts them and vacuumTable() expires old snapshots.INSERT never emits RETURNING (Athena has none), and neither UPDATE nor DELETE emits LIMIT.await d.addColumn('events', 'age', { type: DataTypes.INTEGER });
// ALTER TABLE `analytics`.`events` ADD COLUMNS (`age` int)ADD COLUMNS — plural, and parenthesised. Trino's ADD COLUMN name type is a syntax error in Athena.await d.dropTable('events', { ifExists: true, cascade: true });
// DROP TABLE IF EXISTS `analytics`.`events` (never CASCADE)
await d.createSchema('staging');
// CREATE DATABASE IF NOT EXISTS `staging` (a Glue database; SCHEMA is a synonym)
await d.dropSchema('staging', { ifExists: true, cascade: true });
// DROP DATABASE IF EXISTS `staging` CASCADEremoveColumn is Iceberg-only, because a Hive table has no DROP COLUMN:await hive.removeColumn('events', 'age');
// throws: "... For a Hive table, restate the surviving columns with
// `ALTER TABLE ... REPLACE COLUMNS (...)` ..."COMMENT. NOT NULL, DEFAULT, PRIMARY KEY, UNIQUE and REFERENCES are all omitted: Athena's DDL parser rejects them, and nothing enforces a constraint over S3 objects anyway.information_schema, so these are real queries — scoped to the configured Glue database, not PostgreSQL's 'public':await d.showTables(); // information_schema.tables WHERE table_schema = 'analytics'
await d.showViews(); // information_schema.views
await d.describeTable('events');describeTable reports primaryKey: false, autoIncrement: false and defaultValue: null for every column — Athena has none of the three, so these are facts rather than gaps. Calling any of them without a configured database throws and says which option is missing.UnsupportedSchemaObjectError naming athena and the replacement, rather than emitting SQL Athena will reject.| Area | Methods | Replacement named in the error |
|---|---|---|
| Indexes | addIndex, createIndex, removeIndex, dropIndex, createPartialIndex, createExpressionIndex, createFulltextIndex, createSpatialIndex, showIndexes | PARTITIONED BY, CLUSTERED BY ... INTO n BUCKETS, a columnar format, partition projection |
| Constraints | addConstraint, removeConstraint, createConstraint, dropConstraint, addForeignKey, showConstraints | validate before writing, or query for violations |
| Transactions | startTransaction, commitTransaction, rollbackTransaction | one statement at a time; an Iceberg write is atomic on its own |
| Renames / type changes | renameTable, renameColumn, changeColumn | recreate over the same S3 location; ALTER TABLE ... REPLACE COLUMNS |
| Extensions | createExtension, dropExtension, getExtensions, hasExtension | federated catalogs backed by Lambda connectors |
| Sequences | createSequence, dropSequence, nextSequenceValue, currSequenceValue, hasSequence, listSequences | supply keys from the application, or row_number() in a CTAS |
| Triggers | createTrigger, dropTrigger, hasTrigger | EventBridge / S3 event notifications |
| Stored procedures | createStoredProcedure, createProcedure, dropStoredProcedure, dropProcedure, executeStoredProcedure, hasStoredProcedure | orchestrate from the application, Step Functions or Glue |
| Row-level security | createPolicy, dropPolicy, hasPolicy, createSecurityPolicy, dropSecurityPolicy | Lake Formation permissions, IAM policies over the S3 prefixes |
| Materialized views | createMaterializedView, refreshMaterializedView, dropMaterializedView | CTAS on a schedule |
| Declarative partitioning | createPartitionedTable, createPartition, attachPartition, detachPartition, dropPartition | the partitionedBy table option, addPartitionSpec()/repairTable(), projection |
| LISTEN/NOTIFY | listen, unlisten, notify | EventBridge or SNS |
CREATE VIEW / DROP VIEW are supported and inherited unchanged — Athena has Trino views.athena.test.ts asserts on generated SQL strings and thrown errors; athena-driver.test.ts runs the execution layer against a jest.mock(..., { virtual: true }) stand-in for @aws-sdk/client-athena (which also proves the driver require() is lazy — nothing loads until connect()).name === 'athena', library === '@aws-sdk/client-athena') and construction with no options.quoteHiveTable omitting the data catalog; inherited three-part query naming.JSON/UUID corrections (including inside ARRAY), and the TIME refusal.CREATE EXTERNAL TABLE with IF NOT EXISTS, COMMENT, PARTITIONED BY, CLUSTERED BY, SerDe + SERDEPROPERTIES, STORED AS, LOCATION, TBLPROPERTIES; the missing-location, duplicated-partition-column and missing-bucketCount refusals; omission of every key constraint.CREATE TABLE form, table_type='ICEBERG', transform partitioning, and per-table tableType override.enum/integer/date and the three incomplete-rule refusals.external_location/location, partitioned_by, bucketed_by, bucket_count, write_compression), the partitioning alias, and double-quoted CTAS targets.ADD COLUMNS, DROP TABLE, CREATE/DROP DATABASE, MSCK REPAIR TABLE, ADD/DROP PARTITION, OPTIMIZE, VACUUM, Iceberg-only DROP COLUMN.information_schema introspection scoped to the Glue database, and the missing-database error.connect(), disconnect() releasing sockets, the StartQueryExecution input (context / result configuration / workgroup / result reuse), replacement inlining, polling through QUEUED/RUNNING, DDL skipping GetQueryResults, FAILED/CANCELLED error text, timeout + StopQueryExecution, NextToken paging, MaxResults 1000, header-row detection (both directions), type coercion including the unsafe-integer case, UpdateCount as rowCount, field metadata, execution id and statistics, GetWorkGroup version lookup, and the paginated stream.outputLocation, and KMS result encryption.WITH NO DATA on CTAS. It is inherited from Trino and appears in Athena's documented CTAS synopsis, but has not been run.STORED AS format or TBLPROPERTIES key is accepted for a particular table.OFFSET on engine version 2 (it is an engine v3 feature; the dialect always emits Trino ordering).UPDATE/DELETE/MERGE/OPTIMIZE/VACUUM against a real Iceberg table.struct/map column values (returned as their string rendering), and results larger than the 1000-row page across many pages.AthenaDialect extends TrinoDialect (trino) andObject.getOwnPropertyNames(AthenaDialect.prototype),| Area | Overridden |
|---|---|
| Connection and execution | commitTransaction, connect, disconnect, getConnection, isConnected, listen, notify, query, queryStream, rollbackTransaction, startTransaction, unlisten |
| DML builders | buildDeleteQuery, buildInsertQuery, buildUpdateQuery, buildUpsertMergeQuery, buildUpsertQuery, coerceValue, quoteLiteral |
| Types | canonicalTypeSql, dataTypeKey, documentTypeSql, effectiveTableType, getDataTypeSql, getHiveColumnDefinitionSql, getHiveDataTypeSql, networkTypeSql, temporalTypeSql |
| DDL and schema | addColumn, addConstraint, addForeignKey, addIndex, addPartitionSpec, attachPartition, buildAddPartitionSQL, buildCreateTableAsSelectSQL, buildCreateTableSQL, buildDropPartitionSQL, buildPartitionProjectionProperties, buildTblPropertiesClause, changeColumn, createConstraint, createExpressionIndex, createExtension, createFulltextIndex, createIndex, createMaterializedView, createPartialIndex, createPartition, createPartitionedTable, createPolicy, createProcedure, createSchema, createSecurityPolicy, createSequence, createSpatialIndex, createStoredProcedure, createTable, createTrigger, detachPartition, dropConstraint, dropExtension, dropIndex, dropMaterializedView, dropPartition, dropPartitionSpec, dropPolicy, dropProcedure, dropSchema, dropSecurityPolicy, dropSequence, dropStoredProcedure, dropTable, dropTrigger, optimizeTable, refreshMaterializedView, removeColumn, removeConstraint, removeIndex, renameColumn, renameTable, vacuumTable |
| Introspection | describeTable, getDatabaseVersion, getExtensions, getLastQueryExecutionId, getLastQueryStatistics, hasExtension, hasPolicy, hasSequence, hasStoredProcedure, hasTrigger, isHeaderRow, listSequences, showConstraints, showIndexes, showPartitions, showTables, showViews |
| Other | buildAthenaCtasPropertiesClause, collectResults, currSequenceValue, escapeHiveId, executeStoredProcedure, mapRow, mapStatistics, nextSequenceValue, noConstraints, noDeclarativePartitions, noIndexSupport, noMaterializedViews, noNotifications, noPolicies, noProcedures, noSequences, noTransactions, noTriggers, quoteHiveTable, repairTable, requireDatabase, requireIceberg, runStatement, sleep, unsupported |
capabilitiesOf() (src/transaction-support.ts),whereSupportOf() (src/query-builders/expression-render.ts),sqlObjectCapabilitiesOf() (src/sql-objects/capabilities.ts) and the| Capability | athena |
|---|---|
| Transactions atomic | yes |
| Savepoints | no — a nested transaction() throws |
| Isolation | up to SERIALIZABLE |
| Transaction modes | read-only yes; deferrable yes; constraint deferral yes |
| RETURNING | not emitted; returning is accepted and ignored |
| Upsert | refused — MERGE (upsert) is not supported on athena. Athena runs MERGE (upsert) only against Apache Iceberg tables. Configure the dialect with tableType: 'iceberg' when |
| JSON column | VARCHAR (JSONB → VARCHAR) |
| Vector column | ARRAY(REAL) |
| Array column | ARRAY(INTEGER) |
| WHERE builder | 46 operator keys; $and/$or/$not; $exists: yes; $in subquery: yes; expression operands: yes |
| SQL objects | family unknown: triggers no; procedures no; functions no; materialized views no; sequences no; enums check; RLS no |
| Streaming | queryStream() present; LISTEN present; createExtension() present |
tests/dialects/athena.test.ts exercises the dialect with a mocked driver, so nothing touches a server.FindOptions becomes SQL