Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/plugin-registry.json
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@
"bundleId": "com.TablePro.DynamoDBDriverPlugin",
"bundled": false,
"displayName": "DynamoDB Driver",
"summary": "Amazon DynamoDB driver with PartiQL queries and AWS IAM/Profile/SSO authentication",
"summary": "Amazon DynamoDB: index-aware browsing, typed editing, PartiQL, the DynamoDB API and table management",
"databaseTypeIds": [
"DynamoDB"
],
Expand Down
28 changes: 28 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **File > Session**, with the agent session commands and the assistant's conversation commands.
- Eight more rebindable commands in **Settings > Keyboard**, among them the sidebar's lists and the session commands.
- **Global** on a saved query folder's menu, for a folder every connection shows.
- DynamoDB reads that query the table, a local index or a global index when the grid's filters allow it.
- Create Table form for DynamoDB: keys, capacity, table class, deletion protection and secondary indexes.
- DynamoDB API requests in the editor, such as `CreateTable {…}`, `UpdateTimeToLive {…}` and `BatchWriteItem {…}`.
- DynamoDB point-in-time recovery, deletion protection, stream, class and billing under **Maintenance**.
- Adding and dropping a DynamoDB global secondary index from the **Structure** tab.
- Nested DynamoDB attribute paths in the filter bar and autocomplete.
- **DynamoDB Local (no credentials)** auth method.
- Items read and read units for a DynamoDB browse in the result status bar.
- Tables from every schema in Open Quickly and the sidebar filter, and `schema.table` searches in both. (#3048)
- Recent-tab switching on Control-Tab, with a list of the window's tabs while Control is held. (#2524)
- **Extensions** for SQLite and local libSQL connections, loading sqlite-vec, SpatiaLite and other libraries on connect. (#2502)
Expand Down Expand Up @@ -67,6 +75,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Middle-dot separators dropped from the CSV inspector's status bar and the query history rows.
- Connection marked with a tinted symbol rather than a color dot in the query history rows.
- Safe Mode list offering only the levels a connection allows, with the reason under it and in the toolbar tooltip.
- DynamoDB maps, lists and sets shown as plain JSON and edited in the JSON editor.
- DynamoDB column types named as the AWS console names them: String, Number, Map, String Set.
- DynamoDB region taken from the AWS profile when the connection names none.
- DynamoDB table counts left to **Count Exactly**, with no automatic full-table count.
- DynamoDB table DDL shown as the `CreateTable` request that recreates it.
- Plain HTTP DynamoDB endpoint refused for any host but this Mac, instead of switched to HTTPS.
- ClickHouse materialized views read-only in the data grid, as on every other engine.
- **Show Previous Window Tab** and **Show Next Window Tab** for window tabs, with no default shortcut.
- SQLite 3.53.4 built into the SQLite and libSQL drivers in place of the macOS copy.
Expand Down Expand Up @@ -373,6 +387,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Destination folder and the first database reading as one path in the backup result sheet. (#3046)
- Only the last line of a failed backup's error shown, which on `pg_dump` is the hint rather than the cause.
- Backup failure reported as an exit code alone when the tool wrote its message and exited at once.
- DynamoDB edits saving numbers, booleans, maps and sets as strings.
- DynamoDB binary, map and long-text edits lost or saved as a fragment, and **Set NULL** leaving a NULL attribute.
- Duplicated DynamoDB row saved with `__DEFAULT__` as its key.
- Nine DynamoDB filter operators matching nothing, and an OR filter dropping other partitions' items.
- DynamoDB column sort ignored, and each page re-reading every page before it.
- DynamoDB **Count Exactly** never finishing.
- DynamoDB export dropping attributes first seen after the first page.
- DynamoDB PartiQL result cut to its first 1 MB.
- DynamoDB **Stop** cancelling the wrong request, and throttled requests failing instead of retrying.
- AWS SSO sign-in prompt never shown for DynamoDB.
- DynamoDB connections to China and European Sovereign Cloud regions failing.
- DynamoDB connection sampling every table on connect.
- Imported DynamoDB connection losing its AWS Region.
- Failed **Count Exactly** showing no error.
- PostgreSQL export and column reorder script failing on an index a failed `CREATE INDEX CONCURRENTLY` left behind.
- PostgreSQL export and column reorder script failing on a foreign key that references a unique index.
- PostgreSQL exclusion constraints missing from exports and the DDL tab.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,11 @@ public struct SQLLexicalProfile: Sendable, Hashable {
.doubleSlashLineComments,
]

/// DynamoDB, from the driver's statement forms: a request is `<Action> {JSON}`, and a backslash escapes inside a
/// JSON string. PartiQL, the other form, documents only a doubled quote, so plain ANSI is kept as an alternative
/// and a gate reads both. Not measured.
static let dynamoDB: SQLLexicalGrammar = [.backslashEscapesInDoubleQuotes]

/// Engines whose statements are commands or JSON documents rather than SQL. Their splitting is what it has always
/// been: a backslash escapes inside any quote, as it does in JSON and in `redis-cli`.
static let commandLine: SQLLexicalGrammar = [
Expand Down Expand Up @@ -250,7 +255,11 @@ public struct SQLLexicalProfile: Sendable, Hashable {
),
"Cassandra": cqlFamily,
"ScyllaDB": cqlFamily,
"DynamoDB": SQLLexicalProfile(grammar: .ansi, undetermined: [.carriageReturnEndsLineComments]),
"DynamoDB": SQLLexicalProfile(
grammar: dynamoDB,
alternatives: [.ansi],
undetermined: [.carriageReturnEndsLineComments]
),
"SurrealDB": SQLLexicalProfile(grammar: surrealQL, undetermined: [.carriageReturnEndsLineComments]),
"Redis": commandLineFamily,
"MongoDB": commandLineFamily,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,40 @@ struct SQLLexicalReadingsTests {
#expect(readings.contains { $0.contains(.taggedDollarQuotes) })
}

@Test("A DynamoDB request whose JSON string escapes a quote is one statement to the driver")
func dynamoDBRequestKeepsEscapedQuotesInsideTheString() {
let readings = SQLLexicalReadings.resolve(databaseTypeId: "DynamoDB", declared: nil, session: nil)
let text = #"PutItem {"TableName": "t", "Item": {"a": {"S": "x\";y"}}}; SELECT * FROM "t""#

let executed = SQLStatementScanner.executableStatements(in: text, grammar: readings.execution).map(\.sql)

#expect(executed == [#"PutItem {"TableName": "t", "Item": {"a": {"S": "x\";y"}}}"#, #"SELECT * FROM "t""#])
}

@Test("DynamoDB keeps plain ANSI as a reading, so a gate still counts what PartiQL could split")
func dynamoDBGateReadsTheANSIReading() {
let readings = SQLLexicalReadings.resolve(databaseTypeId: "DynamoDB", declared: nil, session: nil)
let text = #"PutItem {"TableName": "t", "Item": {"a": {"S": "x\";y\";z"}}}; SELECT * FROM "t""#

let counts = readings.distinct(for: text).map {
SQLStatementScanner.executableStatements(in: text, grammar: $0).count
}

#expect(readings.all.contains(.ansi))
#expect(SQLStatementScanner.executableStatements(in: text, grammar: readings.execution).count == 2)
#expect(counts.max() == 3)
}

@Test("PartiQL's doubled quote still closes nothing under DynamoDB's execution grammar")
func dynamoDBPartiQLDoubledQuoteStillLexes() {
let readings = SQLLexicalReadings.resolve(databaseTypeId: "DynamoDB", declared: nil, session: nil)
let text = #"SELECT * FROM "a""b"; DELETE FROM "t""#

let executed = SQLStatementScanner.executableStatements(in: text, grammar: readings.execution).map(\.sql)

#expect(executed == [#"SELECT * FROM "a""b""#, #"DELETE FROM "t""#])
}

@Test("Every combination of the undetermined facts is a reading")
func undeterminedFactsExpand() {
let profile = SQLLexicalProfile(grammar: .ansi, undetermined: [.hashLineComments, .nestedBlockComments])
Expand Down
Loading
Loading