-
-
Notifications
You must be signed in to change notification settings - Fork 707
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #156 from drizzle-team/aws-dataapi
Aws dataapi
- Loading branch information
Showing
27 changed files
with
2,484 additions
and
42 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
We have released [AWS Data API support](https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/data-api.html) for PostgreSQL | ||
|
||
--- | ||
|
||
Connection example | ||
|
||
```typescript | ||
import { drizzle, migrate } from 'drizzle-orm/aws-data-api/pg'; | ||
|
||
const rdsClient = new RDSDataClient({}); | ||
|
||
const db = drizzle(rdsClient, { | ||
database: '', | ||
secretArn: '', | ||
resourceArn: '', | ||
}); | ||
|
||
await migrate(db, { migrationsFolder: '' }); | ||
``` | ||
|
||
> **Note**: | ||
> All drizzle pg data types are working well with data api, except of `interval`. This type is not yet mapped in proper way |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { Field, TypeHint } from "@aws-sdk/client-rds-data"; | ||
import { QueryTypingsValue } from "~/sql"; | ||
|
||
export function getValueFromDataApi(row: Field) { | ||
if (typeof row.stringValue !== 'undefined') { | ||
return row.stringValue; | ||
} else if (typeof row.booleanValue !== 'undefined') { | ||
return row.booleanValue; | ||
} else if (typeof row.doubleValue !== 'undefined') { | ||
return row.doubleValue; | ||
} else if (typeof row.isNull !== 'undefined') { | ||
return null; | ||
} else if (typeof row.longValue !== 'undefined') { | ||
return row.longValue; | ||
} else if (typeof row.blobValue !== 'undefined') { | ||
return row.blobValue; | ||
} else if (typeof row.arrayValue !== 'undefined') { | ||
if (typeof row.arrayValue.stringValues !== 'undefined') { | ||
return row.arrayValue.stringValues; | ||
} | ||
throw Error('Unknown array type'); | ||
} else { | ||
throw Error('Unknown type'); | ||
} | ||
} | ||
|
||
export function typingsToAwsTypeHint(typings?: QueryTypingsValue): TypeHint | undefined { | ||
if (typings === 'date') { | ||
return TypeHint.DATE; | ||
} else if (typings === 'decimal') { | ||
return TypeHint.DECIMAL; | ||
} else if (typings === 'json') { | ||
return TypeHint.JSON; | ||
} else if (typings === 'time') { | ||
return TypeHint.TIME; | ||
} else if (typings === 'timestamp') { | ||
return TypeHint.TIMESTAMP; | ||
} else if (typings === 'uuid') { | ||
return TypeHint.UUID; | ||
} else { | ||
return undefined; | ||
} | ||
} | ||
|
||
export function toValueParam(value: any, typings?: QueryTypingsValue): { value: Field; typeHint?: TypeHint } { | ||
let response: { value: Field; typeHint?: TypeHint } = { | ||
value: {} as any, | ||
typeHint: typingsToAwsTypeHint(typings), | ||
}; | ||
|
||
if (value === null) { | ||
response.value = { isNull: true }; | ||
} else if (typeof value === 'string') { | ||
if (response.typeHint === 'DATE') { | ||
response.value = { stringValue: value.split('T')[0]! }; | ||
} else { | ||
response.value = { stringValue: value }; | ||
} | ||
} else if (typeof value === 'number' && Number.isInteger(value)) { | ||
response.value = { longValue: value }; | ||
} else if (typeof value === 'number' && !Number.isInteger(value)) { | ||
response.value = { doubleValue: value }; | ||
} else if (typeof value === 'boolean') { | ||
response.value = { booleanValue: value }; | ||
} else if (value instanceof Date) { | ||
response.value = { stringValue: value.toISOString().replace('T', ' ').replace('Z', '') }; | ||
} else { | ||
throw Error(`Unknown type for ${value}`); | ||
} | ||
|
||
return response; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { Logger } from '~/logger'; | ||
import { PgDatabase } from '~/pg-core/db'; | ||
import { PgDialect } from '~/pg-core/dialect'; | ||
import { AwsDataApiClient, AwsDataApiPgQueryResultHKT, AwsDataApiSession } from './session'; | ||
|
||
export interface PgDriverOptions { | ||
logger?: Logger; | ||
database: string; | ||
resourceArn: string; | ||
secretArn: string; | ||
} | ||
|
||
export class AwsDataApiDriver { | ||
constructor( | ||
private client: AwsDataApiClient, | ||
private dialect: PgDialect, | ||
private options: PgDriverOptions, | ||
) { | ||
} | ||
|
||
createSession(): AwsDataApiSession { | ||
return new AwsDataApiSession(this.client, this.dialect, this.options); | ||
} | ||
} | ||
|
||
export interface DrizzleConfig { | ||
logger?: Logger; | ||
database: string; | ||
resourceArn: string; | ||
secretArn: string; | ||
} | ||
|
||
export type AwsDataApiPgDatabase = PgDatabase<AwsDataApiPgQueryResultHKT, AwsDataApiSession>; | ||
|
||
export class AwsPgDialect extends PgDialect { | ||
override escapeName(name: string): string { | ||
return `"${name}"`; | ||
} | ||
|
||
override escapeParam(num: number): string { | ||
return `:${num + 1}`; | ||
} | ||
} | ||
|
||
export function drizzle(client: AwsDataApiClient, config: DrizzleConfig): AwsDataApiPgDatabase { | ||
const dialect = new AwsPgDialect(); | ||
const driver = new AwsDataApiDriver(client, dialect, config); | ||
const session = driver.createSession(); | ||
return new PgDatabase(dialect, session); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './driver'; | ||
export * from './session'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { MigrationConfig, readMigrationFiles } from '~/migrator'; | ||
import { sql } from '~/sql'; | ||
import { AwsDataApiPgDatabase } from './driver'; | ||
import { AwsDataApiSession } from './session'; | ||
|
||
export async function migrate(db: AwsDataApiPgDatabase, config: string | MigrationConfig) { | ||
const migrations = readMigrationFiles(config); | ||
|
||
// Write own aws datapi migrator | ||
const session = db.session as AwsDataApiSession; | ||
|
||
const migrationTableCreate = sql`CREATE TABLE IF NOT EXISTS "drizzle"."__drizzle_migrations" ( | ||
id SERIAL PRIMARY KEY, | ||
hash text NOT NULL, | ||
created_at bigint | ||
)`; | ||
await session.execute(sql`CREATE SCHEMA IF NOT EXISTS "drizzle"`); | ||
await session.execute(migrationTableCreate); | ||
|
||
const dbMigrations = await session.execute<{ id: number; hash: string; created_at: string }[]>( | ||
sql`SELECT id, hash, created_at FROM "drizzle"."__drizzle_migrations" ORDER BY created_at DESC LIMIT 1`, | ||
); | ||
|
||
const lastDbMigration = dbMigrations[0]; | ||
const transactionId = await session.beginTransaction(); | ||
|
||
try { | ||
for await (const migration of migrations) { | ||
if ( | ||
!lastDbMigration | ||
|| parseInt(lastDbMigration.created_at, 10) < migration.folderMillis | ||
) { | ||
await session.executeWithTransaction(sql.raw(migration.sql), transactionId); | ||
await session.executeWithTransaction( | ||
sql`INSERT INTO "drizzle"."__drizzle_migrations" ("hash", "created_at") VALUES(${migration.hash}, ${migration.folderMillis})`, | ||
transactionId, | ||
); | ||
} | ||
} | ||
|
||
await session.commitTransaction(transactionId!); | ||
} catch (e) { | ||
await session.rollbackTransaction(transactionId!); | ||
throw e; | ||
} | ||
} |
Oops, something went wrong.