-
Notifications
You must be signed in to change notification settings - Fork 11
Allow dynamodb to run initial sync only #361
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,6 +7,7 @@ import ( | |
| "errors" | ||
| "fmt" | ||
| "log/slog" | ||
| "os" | ||
| "sync" | ||
|
|
||
| "connectrpc.com/connect" | ||
|
|
@@ -71,36 +72,46 @@ func (c *conn) GeneratePlan(ctx context.Context, r *connect.Request[adiomv1.Gene | |
| eg.Go(func() error { | ||
| tableDetails, err := c.client.TableDetails(egCtx, name) | ||
| if err != nil { | ||
| if errors.Is(err, ErrNotFound) { | ||
| slog.Warn("Table not found. Ignoring.", "name", name) | ||
| return nil | ||
| } | ||
| return err | ||
| } | ||
|
|
||
| if tableDetails.StreamARN == "" { | ||
| if c.spec == "localstack" { | ||
| slog.Debug("No stream found, starting stream", "table", name) | ||
| _, err := c.client.StartStream(egCtx, name, false) | ||
| if err != nil { | ||
| return err | ||
| if r.Msg.GetUpdates() { | ||
| if tableDetails.StreamARN == "" { | ||
| if c.spec == "localstack" { | ||
| slog.Debug("No stream found, starting stream", "table", name) | ||
| _, err := c.client.StartStream(egCtx, name, false) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } else { | ||
| return fmt.Errorf("no stream found") | ||
| } | ||
| } else { | ||
| return fmt.Errorf("no stream found") | ||
| } | ||
| } else if tableDetails.IncompatibleStream { | ||
| if c.spec == "localstack" { | ||
| slog.Debug("Incompatible stream found, restarting stream", "table", name) | ||
| _, err := c.client.StartStream(egCtx, name, true) | ||
| if err != nil { | ||
| return err | ||
| } else if tableDetails.IncompatibleStream { | ||
| if c.spec == "localstack" { | ||
| slog.Debug("Incompatible stream found, restarting stream", "table", name) | ||
| _, err := c.client.StartStream(egCtx, name, true) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| } else { | ||
| return fmt.Errorf("incompatible stream found") | ||
| } | ||
| } else { | ||
| return fmt.Errorf("incompatible stream found") | ||
| } | ||
|
|
||
| state, err := c.client.GetStreamState(egCtx, tableDetails.StreamARN) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| statesCh <- state | ||
| } | ||
|
|
||
| state, err := c.client.GetStreamState(ctx, tableDetails.StreamARN) | ||
| if err != nil { | ||
| return err | ||
| if !r.Msg.GetInitialSync() { | ||
| return nil | ||
| } | ||
| statesCh <- state | ||
|
|
||
| // TODO: reconsider how to map namespaces properly | ||
| ns := name | ||
|
|
@@ -139,22 +150,27 @@ func (c *conn) GeneratePlan(ctx context.Context, r *connect.Request[adiomv1.Gene | |
| return nil, connect.NewError(connect.CodeInternal, err) | ||
| } | ||
|
|
||
| var buf bytes.Buffer | ||
| enc := gob.NewEncoder(&buf) | ||
| err = enc.Encode(stateMap) | ||
| if err != nil { | ||
| return nil, connect.NewError(connect.CodeInternal, err) | ||
| var updates []*adiomv1.UpdatesPartition | ||
| if r.Msg.GetUpdates() { | ||
| var buf bytes.Buffer | ||
| enc := gob.NewEncoder(&buf) | ||
| err = enc.Encode(stateMap) | ||
| if err != nil { | ||
| return nil, connect.NewError(connect.CodeInternal, err) | ||
| } | ||
| updates = append(updates, &adiomv1.UpdatesPartition{Namespaces: namespaces, Cursor: buf.Bytes()}) | ||
| } | ||
|
Comment on lines
+153
to
162
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's look at the full context around lines 148-157 and the earlier part (lines 34-35)
cd connectors/dynamodb && cat -n conn.go | sed -n '30,165p'Repository: adiom-data/dsync Length of output: 4683 🏁 Script executed: # Search for UpdatesPartition usage to see how Namespaces is consumed
rg -n -B2 -A5 'UpdatesPartition' --type=go -g '!*_test.go'Repository: adiom-data/dsync Length of output: 20733 🏁 Script executed: # Look for the proto definition of UpdatesPartition to understand the Namespaces field
find . -name '*.proto' -type f | head -20Repository: adiom-data/dsync Length of output: 150 🏁 Script executed: # Verify the fix: check what tableNames contains and how it should be used
cat -n connectors/dynamodb/conn.go | sed -n '148,157p'Repository: adiom-data/dsync Length of output: 461 🏁 Script executed: # Check the proto definition for UpdatesPartition to understand the expected semantics
cat proto/adiom/v1/messages.proto | grep -A 10 "message UpdatesPartition"Repository: adiom-data/dsync Length of output: 397 Use When Change line 156 to: 🤖 Prompt for AI Agents |
||
|
|
||
| return connect.NewResponse(&adiomv1.GeneratePlanResponse{ | ||
| Partitions: partitions, | ||
| UpdatesPartitions: []*adiomv1.UpdatesPartition{{Namespaces: namespaces, Cursor: buf.Bytes()}}, | ||
| UpdatesPartitions: updates, | ||
| }), nil | ||
| } | ||
|
|
||
| // GetInfo implements adiomv1connect.ConnectorServiceHandler. | ||
| func (c *conn) GetInfo(context.Context, *connect.Request[adiomv1.GetInfoRequest]) (*connect.Response[adiomv1.GetInfoResponse], error) { | ||
| return connect.NewResponse(&adiomv1.GetInfoResponse{ | ||
| Id: c.options.ID, | ||
| DbType: "dynamodb", | ||
| Version: "", | ||
| Spec: c.spec, | ||
|
|
@@ -355,7 +371,10 @@ func (c *conn) WriteUpdates(context.Context, *connect.Request[adiomv1.WriteUpdat | |
| func AWSClientHelper(connStr string) (*dynamodb.Client, *dynamodbstreams.Client) { | ||
| var endpoint string | ||
| if connStr == "localstack" { | ||
| endpoint = "http://localhost:4566" | ||
| endpoint = os.Getenv("AWS_ENDPOINT_URL") | ||
| if endpoint == "" { | ||
| endpoint = "http://localhost:4566" | ||
| } | ||
| } | ||
| awsConfig, err := config.LoadDefaultConfig(context.Background()) | ||
| if err != nil { | ||
|
|
@@ -375,10 +394,29 @@ func AWSClientHelper(connStr string) (*dynamodb.Client, *dynamodbstreams.Client) | |
| } | ||
|
|
||
| type Options struct { | ||
| ID string | ||
| DocsPerSegment int | ||
| PlanParallelism int | ||
| } | ||
|
|
||
| func WithID(s string) func(*Options) { | ||
| return func(o *Options) { | ||
| o.ID = s | ||
| } | ||
| } | ||
|
|
||
| func WithPlanParallelism(n int) func(*Options) { | ||
| return func(o *Options) { | ||
| o.PlanParallelism = n | ||
| } | ||
| } | ||
|
|
||
| func WithDocsPerSegment(n int) func(*Options) { | ||
| return func(o *Options) { | ||
| o.DocsPerSegment = n | ||
| } | ||
| } | ||
|
|
||
| func NewConn(connStr string, optFns ...func(*Options)) adiomv1connect.ConnectorServiceHandler { | ||
| opts := Options{ | ||
| DocsPerSegment: 50000, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Repository: adiom-data/dsync
Length of output: 330
🏁 Script executed:
Repository: adiom-data/dsync
Length of output: 191
🏁 Script executed:
# Let's also check the complete context of lines 81-113 in conn.go wc -l connectors/dynamodb/conn.goRepository: adiom-data/dsync
Length of output: 91
🏁 Script executed:
Repository: adiom-data/dsync
Length of output: 1873
🏁 Script executed:
Repository: adiom-data/dsync
Length of output: 184
🏁 Script executed:
Repository: adiom-data/dsync
Length of output: 1702
🏁 Script executed:
Repository: adiom-data/dsync
Length of output: 994
🏁 Script executed:
Repository: adiom-data/dsync
Length of output: 580
🏁 Script executed:
Repository: adiom-data/dsync
Length of output: 122
🏁 Script executed:
Repository: adiom-data/dsync
Length of output: 1601
🏁 Script executed:
Repository: adiom-data/dsync
Length of output: 578
After
StartStreamis called, use the returned ARN instead of the staletableDetails.StreamARN.When
r.Msg.GetUpdates()is true and a stream doesn't exist (or is incompatible), the code callsStartStreambut discards its return value. The subsequent call toGetStreamStateusestableDetails.StreamARN, which was fetched before the stream was started and remains empty.StartStreamalready returns the new stream ARN—capture and use that instead of re-fetching table details.🔧 Suggested fix: Capture the returned ARN from StartStream
if r.Msg.GetUpdates() { if tableDetails.StreamARN == "" { if c.spec == "localstack" { slog.Debug("No stream found, starting stream", "table", name) - _, err := c.client.StartStream(egCtx, name, false) + tableDetails.StreamARN, err = c.client.StartStream(egCtx, name, false) if err != nil { return err } } else { return fmt.Errorf("no stream found") } } else if tableDetails.IncompatibleStream { if c.spec == "localstack" { slog.Debug("Incompatible stream found, restarting stream", "table", name) - _, err := c.client.StartStream(egCtx, name, true) + tableDetails.StreamARN, err = c.client.StartStream(egCtx, name, true) if err != nil { return err } } else { return fmt.Errorf("incompatible stream found") } }📝 Committable suggestion