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
5 changes: 5 additions & 0 deletions changes/unreleased/Added-20260718-233026.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Added
body: 'Add 24 operations to core services: EC2 instance lifecycle (Start/Stop/Reboot), DescribeAvailabilityZones/DescribeRegions, security group rules (Authorize/Revoke ingress+egress), Elastic IP association, DeleteSecurityGroup/DeleteSubnet/DescribeTags/DeleteTags, and IAM group policy management (attach/detach/put/get/delete/list)'
time: 2026-07-18T23:30:26.666352+09:00
custom:
Issue: "97"
5 changes: 5 additions & 0 deletions changes/unreleased/Fixed-20260720-054448.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Fixed
body: 'EC2: Describe{SecurityGroups,Tags,Addresses} now honor GroupId/AllocationId/PublicIp selectors and Filters (and raise NotFound for unknown IDs) instead of returning every resource; delete-by-name removes a single security group; batch Start/Stop is atomic; AssociateAddress validates the instance; duplicate/absent security-group rules are rejected; Authorize/Revoke accept GroupName; all-traffic rules omit ports'
time: 2026-07-20T05:44:48.649862+09:00
custom:
Issue: "97"
5 changes: 5 additions & 0 deletions changes/unreleased/Fixed-20260720-055500.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Fixed
body: 'DynamoDB: numeric (''N'') sort/range keys now order by true numeric value across the full 38-digit precision via a NUMTEXT SQLite collation, instead of a float CAST that lost precision past 2^53'
time: 2026-07-20T05:55:00.000000+09:00
custom:
Issue: "97"
6 changes: 3 additions & 3 deletions internal/services/dynamodb/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -620,11 +620,11 @@ func indexKeyValue(item Item, idx IndexDef, kind string) (value string, ok bool,
// orderByColumn returns the ORDER BY expression for a sort/range key column of
// the given attribute type. Numeric ('N') keys must sort numerically, not by the
// lexicographic byte order of their TEXT storage (so 2, 10 sorts before 100).
// ponytail: CAST AS REAL loses precision past ~2^53 and on high-precision
// decimals; fine for an emulator, swap for a sortable-number encoding if it bites.
// The NUMTEXT collation compares by true numeric value, exact across DynamoDB's
// full 38-digit precision (a float CAST is lossy past 2^53).
func orderByColumn(col, keyType string) string {
if keyType == "N" {
return "CAST(" + col + " AS REAL)"
return col + " COLLATE NUMTEXT"
}
return col
}
Expand Down
35 changes: 35 additions & 0 deletions internal/services/dynamodb/store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,41 @@ func TestDynamoStore_Query(t *testing.T) {
assert.Len(t, results, 3)
}

// TestDynamoStore_QueryNumericSortKeyPrecision verifies numeric ('N') sort keys
// order by true value even where a float CAST would fail: consecutive integers
// past 2^53 (which collapse to the same IEEE double) and near-equal decimals.
func TestDynamoStore_QueryNumericSortKeyPrecision(t *testing.T) {
store := newTestStore(t)

require.NoError(t, store.CreateTable(TableInfo{
Name: "Ledger",
PartitionKey: KeyDef{Name: "Acct", Type: "S"},
SortKey: &KeyDef{Name: "Seq", Type: "N"},
}))

seqs := []string{
"100", "2", "10",
"9007199254740994", "9007199254740992", "9007199254740993", // 2^53+2, 2^53, 2^53+1
"1.0000000000000002", "1.0000000000000001",
}
for _, s := range seqs {
require.NoError(t, store.PutItem("Ledger", Item{"Acct": {S: strPtr("a")}, "Seq": {N: strPtr(s)}}))
}

results, err := store.Query("Ledger", "a", "")
require.NoError(t, err)

got := make([]string, len(results))
for i, it := range results {
got[i] = *it["Seq"].N
}
assert.Equal(t, []string{
"1.0000000000000001", "1.0000000000000002",
"2", "10", "100",
"9007199254740992", "9007199254740993", "9007199254740994",
}, got)
}

// TestDynamoStore_Scan verifies that Scan returns all items in a table.
func TestDynamoStore_Scan(t *testing.T) {
store := newTestStore(t)
Expand Down
Loading