Skip to content

feat: bulk insert plugin for sqlc#3631

Merged
chronark merged 11 commits intomainfrom
sqlc_bulk_plugin
Jul 18, 2025
Merged

feat: bulk insert plugin for sqlc#3631
chronark merged 11 commits intomainfrom
sqlc_bulk_plugin

Conversation

@Flo4604
Copy link
Member

@Flo4604 Flo4604 commented Jul 17, 2025

What does this PR do?

Fixes # (issue)

If there is not an issue for this, please create one first. This is used to tracking purposes and also helps use understand why this PR exists

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

  • Test A
  • Test B

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • If a UI change was made: Added a screen recording or screenshots to this PR
  • Updated the Unkey Docs if changes were necessary

Summary by CodeRabbit

  • New Features

    • Introduced efficient bulk insert operations across multiple database entities, improving performance for batch data creation and updates.
    • Added upsert (insert or update) support for ratelimit records, ensuring existing entries are updated automatically.
    • Enhanced developer documentation with examples and usage for new bulk insert capabilities.
  • Refactor

    • Replaced previous generic bulk insert methods with specialized, optimized bulk insert functions for various data types.
  • Chores

    • Updated dependencies and configuration to support new bulk insert plugin.
    • Added comprehensive automated tests for bulk insert generation and utility functions.
  • Documentation

    • Added detailed README and code comments for the new bulk insert plugin and usage patterns.
  • Tests

    • Introduced new test suites to ensure correctness of bulk insert logic and code generation.
  • Style

    • Improved code structure and naming consistency for bulk insert methods and interfaces.

@changeset-bot
Copy link

changeset-bot bot commented Jul 17, 2025

⚠️ No Changeset found

Latest commit: 5b144d4

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@vercel
Copy link

vercel bot commented Jul 17, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

2 Skipped Deployments
Name Status Preview Comments Updated (UTC)
dashboard ⬜️ Ignored (Inspect) Visit Preview Jul 17, 2025 5:20pm
engineering ⬜️ Ignored (Inspect) Visit Preview Jul 17, 2025 5:20pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jul 17, 2025

📝 Walkthrough
## Walkthrough

This change introduces a new sqlc bulk-insert code generation plugin, generates specialized bulk insert methods for various database entities, and refactors existing code to use these new methods instead of a generic bulk insert utility. The update includes new plugin source, test, and documentation files, modifies API handlers to use the generated methods, and enhances SQL insert statements with upsert semantics where needed.

## Changes

| File(s) | Change Summary |
|---------|---------------|
| `go/apps/api/routes/v2_identities_create_identity/handler.go`,<br>`go/apps/api/routes/v2_identities_update_identity/handler.go`,<br>`go/apps/api/routes/v2_keys_create_key/handler.go`,<br>`go/apps/api/routes/v2_keys_update_key/handler.go`,<br>`go/internal/services/auditlogs/insert.go` | Refactored to replace generic `db.BulkInsert` calls with specialized bulk insert methods generated by the new plugin (e.g., `InsertIdentityRatelimits`, `InsertKeyRatelimits`, etc.). |
| `go/pkg/db/bulk_insert.go` | Removed the generic bulk insert utility function. |
| `go/pkg/db/queries.go` | Added `BulkQueries` struct and `BulkQuery` variable for bulk operations. |
| `go/pkg/db/querier_bulk_generated.go` | Added generated `BulkQuerier` interface with methods for all bulk insert operations. |
| `go/pkg/db/bulk_*_insert.sql.go`,<br>`go/pkg/db/bulk_*_upsert.sql.go` | Added generated Go files implementing bulk insert/upsert methods for various entities (e.g., keys, identities, permissions, roles, projects, builds, etc.). |
| `go/pkg/db/identity_insert_ratelimit.sql_generated.go`,<br>`go/pkg/db/queries/identity_insert_ratelimit.sql`,<br>`go/pkg/db/querier_generated.go` | Modified SQL to add "ON DUPLICATE KEY UPDATE" (upsert) behavior for ratelimit inserts; updated comments accordingly. |
| `go/pkg/db/doc.go` | Expanded documentation to describe bulk insert features and usage examples. |
| `go/pkg/db/generate.go` | Added go:generate directive to build the bulk-insert plugin. |
| `go/pkg/db/sqlc.json` | Configured sqlc to use the new bulk-insert plugin with DB argument emission; registered the plugin. |
| `go/go.mod` | Added direct dependencies for the plugin and YAML parsing; promoted `gopkg.in/yaml.v2` to direct dependency. |
| `go/pkg/db/plugins/bulk-insert/*` | Added new bulk-insert sqlc plugin implementation, including generator, parser, template, utilities, and comprehensive tests. |
| `go/pkg/db/plugins/bulk-insert/README.md` | Added documentation for the bulk-insert plugin. |
| `go/pkg/db/queries/branch_upsert.sql` | Added trailing newline for formatting consistency. |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant Handler
    participant BulkQuery
    participant DB

    Handler->>BulkQuery: Insert<Entity>(ctx, tx, []Params)
    BulkQuery->>DB: Executes bulk INSERT ... VALUES (...), (...), ... [ON DUPLICATE KEY UPDATE ...]
    DB-->>BulkQuery: Result/Error
    BulkQuery-->>Handler: Error (if any)

Possibly related PRs

  • feat: v2/keys.getKey #3421: Introduces the generic BulkInsert utility and initial bulk insert handling, laying groundwork for the current refactor to specialized bulk insert methods.
  • feat: keys.updateKey #3561: Implements the /v2/keys.updateKey API handler, which uses the newly generated bulk insert methods (e.g., InsertKeyRatelimits) for updating key ratelimits and permissions. The current PR's changes directly support and are used in this handler.

Suggested reviewers

  • perkinsjr
  • imeyer
  • mcstepp
  • ogzhanolguncu

</details>

<!-- walkthrough_end -->


---

<details>
<summary>📜 Recent review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: ASSERTIVE**
**Plan: Pro**


<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 36039c52fffb884f74d81962b7aa47967bef0811 and 5b144d4c8e1d77157cf1817b5a3d9be5feb8a0ff.

</details>

<details>
<summary>📒 Files selected for processing (4)</summary>

* `go/pkg/db/bulk_role_insert.sql.go` (1 hunks)
* `go/pkg/db/querier_generated.go` (2 hunks)
* `go/pkg/db/queries/role_insert.sql` (2 hunks)
* `go/pkg/db/role_insert.sql_generated.go` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧠 Learnings (5)</summary>

<details>
<summary>📓 Common learnings</summary>

Learnt from: Flo4604
PR: #3631
File: go/pkg/db/bulk_keyring_insert.sql.go:23-25
Timestamp: 2025-07-17T14:24:20.359Z
Learning: In go/pkg/db/bulk_keyring_insert.sql.go and similar bulk insert generated files, hardcoded zero values for fields like size_approx and size_last_updated_at are intentional and reflect the original SQL query structure, not missing parameters.


</details>
<details>
<summary>go/pkg/db/role_insert.sql_generated.go (4)</summary>

Learnt from: ogzhanolguncu
PR: #3324
File: apps/dashboard/app/(app)/authorization/roles/components/table/components/actions/keys-table-action.popover.constants.tsx:17-18
Timestamp: 2025-06-19T11:48:05.070Z
Learning: In the authorization roles refactor, the RoleBasic type uses roleId as the property name for the role identifier, not id. This is consistent throughout the codebase in apps/dashboard/lib/trpc/routers/authorization/roles/query.ts.


Learnt from: Flo4604
PR: #3631
File: go/pkg/db/bulk_keyring_insert.sql.go:23-25
Timestamp: 2025-07-17T14:24:20.359Z
Learning: In go/pkg/db/bulk_keyring_insert.sql.go and similar bulk insert generated files, hardcoded zero values for fields like size_approx and size_last_updated_at are intentional and reflect the original SQL query structure, not missing parameters.


Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use ctx.workspace.id directly instead of fetching the workspace separately for better performance and security.


Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the workspace parameter in functions like createWorkspaceNavigation, prefer importing the Workspace type from the database module and picking the necessary keys (e.g., features) instead of redefining the interface.


</details>
<details>
<summary>go/pkg/db/queries/role_insert.sql (3)</summary>

Learnt from: Flo4604
PR: #3631
File: go/pkg/db/bulk_keyring_insert.sql.go:23-25
Timestamp: 2025-07-17T14:24:20.359Z
Learning: In go/pkg/db/bulk_keyring_insert.sql.go and similar bulk insert generated files, hardcoded zero values for fields like size_approx and size_last_updated_at are intentional and reflect the original SQL query structure, not missing parameters.


Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use ctx.workspace.id directly instead of fetching the workspace separately for better performance and security.


Learnt from: ogzhanolguncu
PR: #3324
File: apps/dashboard/app/(app)/authorization/roles/components/table/components/actions/keys-table-action.popover.constants.tsx:17-18
Timestamp: 2025-06-19T11:48:05.070Z
Learning: In the authorization roles refactor, the RoleBasic type uses roleId as the property name for the role identifier, not id. This is consistent throughout the codebase in apps/dashboard/lib/trpc/routers/authorization/roles/query.ts.


</details>
<details>
<summary>go/pkg/db/querier_generated.go (2)</summary>

Learnt from: Flo4604
PR: #3631
File: go/pkg/db/bulk_keyring_insert.sql.go:23-25
Timestamp: 2025-07-17T14:24:20.359Z
Learning: In go/pkg/db/bulk_keyring_insert.sql.go and similar bulk insert generated files, hardcoded zero values for fields like size_approx and size_last_updated_at are intentional and reflect the original SQL query structure, not missing parameters.


Learnt from: chronark
PR: #2294
File: apps/api/src/pkg/keys/service.ts:268-271
Timestamp: 2024-10-20T07:05:55.471Z
Learning: In apps/api/src/pkg/keys/service.ts, ratelimitAsync is a table relation, not a column selection. When querying, ensure that table relations are included appropriately, not as columns.


</details>
<details>
<summary>go/pkg/db/bulk_role_insert.sql.go (6)</summary>

Learnt from: Flo4604
PR: #3631
File: go/pkg/db/bulk_keyring_insert.sql.go:23-25
Timestamp: 2025-07-17T14:24:20.359Z
Learning: In go/pkg/db/bulk_keyring_insert.sql.go and similar bulk insert generated files, hardcoded zero values for fields like size_approx and size_last_updated_at are intentional and reflect the original SQL query structure, not missing parameters.


Learnt from: Flo4604
PR: #3606
File: go/pkg/db/replica.go:8-11
Timestamp: 2025-07-16T15:38:53.464Z
Learning: For debugging database replica usage in go/pkg/db/replica.go, it's acceptable to mark QueryRowContext operations as "success" even though SQL errors only surface during row.Scan() calls. The timing metrics are the primary concern for debugging replica performance patterns.


Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use ctx.workspace.id directly instead of fetching the workspace separately for better performance and security.


Learnt from: ogzhanolguncu
PR: #3324
File: apps/dashboard/app/(app)/authorization/roles/components/table/components/actions/keys-table-action.popover.constants.tsx:17-18
Timestamp: 2025-06-19T11:48:05.070Z
Learning: In the authorization roles refactor, the RoleBasic type uses roleId as the property name for the role identifier, not id. This is consistent throughout the codebase in apps/dashboard/lib/trpc/routers/authorization/roles/query.ts.


Learnt from: chronark
PR: #2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the workspace parameter in functions like createWorkspaceNavigation, prefer importing the Workspace type from the database module and picking the necessary keys (e.g., features) instead of redefining the interface.


Learnt from: ogzhanolguncu
PR: #2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: When querying or updating namespaces in the Unkey dashboard, always scope the operations to the current workspace using eq(table.workspaceId, ctx.workspace.id) to prevent cross-workspace access.


</details>

</details><details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>go/pkg/db/bulk_role_insert.sql.go (3)</summary><blockquote>

<details>
<summary>go/pkg/db/queries.go (1)</summary>

* `BulkQueries` (4-4)

</details>
<details>
<summary>go/pkg/hydra/store/db.go (1)</summary>

* `DBTX` (8-13)

</details>
<details>
<summary>go/pkg/db/role_insert.sql_generated.go (1)</summary>

* `InsertRoleParams` (30-36)

</details>

</blockquote></details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)</summary>

* GitHub Check: Test Agent Local / test_agent_local
* GitHub Check: Test Go API Local / Test
* GitHub Check: Build / Build
* GitHub Check: Test API / API Test Local
* GitHub Check: Test Packages / Test
* GitHub Check: autofix
* GitHub Check: Analyze (javascript-typescript)

</details>

<details>
<summary>🔇 Additional comments (10)</summary><blockquote>

<details>
<summary>go/pkg/db/querier_generated.go (2)</summary>

`665-670`: **LGTM! Documentation correctly reflects the upsert behavior.**

The comment accurately documents the addition of the `ON DUPLICATE KEY UPDATE` clause, which provides upsert semantics for identity ratelimits. This ensures that if a duplicate key conflict occurs, the existing record is updated with new values for `name`, `limit`, `duration`, `auto_apply`, and `updated_at`.

---

`875-875`: **Good catch on the typo fix.**

The correction from `workspace_Id` to `workspace_id` ensures consistency with standard SQL naming conventions.

</details>
<details>
<summary>go/pkg/db/role_insert.sql_generated.go (1)</summary>

`16-16`: **LGTM: Column name casing fixed for consistency.**

The correction from `workspace_Id` to `workspace_id` aligns with standard database naming conventions and ensures consistency across the codebase.




Also applies to: 42-42

</details>
<details>
<summary>go/pkg/db/queries/role_insert.sql (1)</summary>

`4-4`: **LGTM: SQL formatting and column name consistency improved.**

The changes fix the column name casing from `workspace_Id` to `workspace_id` and improve the SQL formatting with proper semicolon placement. These align with the corresponding changes in the generated Go file.




Also applies to: 15-15

</details>
<details>
<summary>go/pkg/db/bulk_role_insert.sql.go (6)</summary>

`1-3`: **LGTM: Proper generated file header.**

The file header correctly identifies this as generated code with appropriate warnings against manual editing.

---

`11-12`: **LGTM: SQL constant uses correct column naming.**

The bulk insert SQL constant correctly uses `workspace_id` with proper casing, addressing the previous naming inconsistency.

---

`15-19`: **LGTM: Proper early return for empty input.**

The method correctly handles the edge case of empty input by returning early, avoiding unnecessary database operations.

---

`21-27`: **LGTM: Dynamic SQL construction is well-implemented.**

The code correctly builds the bulk insert query by dynamically generating the appropriate number of value clauses and joining them properly.

---

`29-37`: **LGTM: Argument collection follows correct parameter order.**

The code correctly flattens the slice of structs into a single argument list, maintaining the proper order that matches the SQL column sequence.

---

`39-42`: **LGTM: Query execution is properly implemented.**

The database execution uses the context correctly and handles the variadic arguments appropriately for the bulk insert operation.

</details>

</blockquote></details>

</details>
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=unkeyed/unkey&utm_content=3631):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Explain this complex logic.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai explain this code block.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and explain its main purpose.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->

@github-actions
Copy link
Contributor

github-actions bot commented Jul 17, 2025

Thank you for following the naming conventions for pull request titles! 🙏

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 20

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (12)
go/pkg/db/bulk_keyring_insert.sql.go (2)

23-25: Hardcoded values issue remains unresolved.

The value clauses still contain hardcoded 0, 0 values for size_approx and size_last_updated_at fields instead of using parameters from the input struct. This will result in incorrect data being inserted.


29-38: Missing arguments for size_approx and size_last_updated_at.

The argument collection only includes 6 fields but the SQL query expects 8 values. The missing SizeApprox and SizeLastUpdatedAt arguments should be added to match the SQL columns.

go/pkg/db/bulk_audit_log_insert.sql.go (1)

29-46: Argument collection properly addresses previous feedback.

The code correctly uses []any instead of []interface{} as suggested in past reviews, and properly flattens all 14 arguments in the correct order to match the SQL column sequence.

go/pkg/db/bulk_audit_log_target_insert.sql.go (1)

29-42: Argument collection addresses previous feedback correctly.

The code properly uses []any instead of []interface{} as suggested in past reviews, and correctly flattens all 10 arguments in the proper order to match the SQL column sequence.

go/pkg/db/bulk_permission_insert.sql.go (1)

40-43: Fix indentation inconsistency as mentioned in previous reviews.

The database execution logic is correct, but the indentation inconsistency with spaces instead of tabs should be fixed as previously identified.

-    _, err := db.ExecContext(ctx, bulkQuery, allArgs...)
-    return err
+	_, err := db.ExecContext(ctx, bulkQuery, allArgs...)
+	return err
go/pkg/db/bulk_ratelimit_namespace_insert.sql.go (1)

38-41: Fix indentation inconsistency as mentioned in previous reviews.

The database execution logic correctly handles the soft-delete pattern, but the indentation inconsistency with spaces instead of tabs should be fixed as previously identified.

-    _, err := db.ExecContext(ctx, bulkQuery, allArgs...)
-    return err
+	_, err := db.ExecContext(ctx, bulkQuery, allArgs...)
+	return err
go/pkg/db/bulk_role_permission_insert.sql.go (1)

39-40: Fix the indentation inconsistency.

The indentation uses spaces instead of tabs, which is inconsistent with Go formatting standards.

go/pkg/db/bulk_key_role_insert.sql.go (1)

39-40: Fix the indentation inconsistency.

The indentation uses spaces instead of tabs, which is inconsistent with Go formatting standards.

go/pkg/db/bulk_ratelimit_override_insert.sql.go (1)

12-12: Simplify the SQL string construction for better readability.

The string concatenation with backticks could be simplified while maintaining MySQL compatibility.

go/pkg/db/bulk_identity_insert.sql.go (1)

12-12: Table name concatenation reduces readability.

The table name escaping using string concatenation with backticks is unconventional and reduces readability compared to using a raw string literal.

go/pkg/db/plugins/bulk-insert/bulk_insert.go.tmpl (1)

35-38: Parameter count validation concern remains valid.

The template appends arguments based on the .Fields slice without validating that its length matches the number of placeholders in .ValuesPart. This could lead to runtime parameter mismatch errors.

Consider adding validation during code generation to ensure field count consistency between .Fields and .ValuesPart placeholders.

go/pkg/db/bulk_identity_insert_ratelimit.sql.go (1)

17-17: Critical issue: incorrect updated_at logic in upsert.

The ON DUPLICATE KEY UPDATE clause sets updated_at = VALUES(created_at), which means the update timestamp will always reflect the creation time instead of the actual update time. This is a data integrity issue.

The fix requires:

  1. Adding updated_at to the InsertIdentityRatelimitParams struct
  2. Including updated_at in the VALUES clause
  3. Changing the conflict clause to updated_at = VALUES(updated_at)

This ensures proper timestamp tracking for record updates.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e89ab3c and 0547a14.

📒 Files selected for processing (25)
  • go/pkg/db/bulk_api_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_audit_log_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_audit_log_target_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_branch_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_branch_upsert.sql.go (1 hunks)
  • go/pkg/db/bulk_build_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_build_step_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_identity_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_identity_insert_ratelimit.sql.go (1 hunks)
  • go/pkg/db/bulk_key_encryption_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_key_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_key_insert_ratelimit.sql.go (1 hunks)
  • go/pkg/db/bulk_key_permission_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_key_role_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_keyring_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_permission_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_project_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_ratelimit_namespace_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_ratelimit_override_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_role_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_role_permission_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_route_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_version_insert.sql.go (1 hunks)
  • go/pkg/db/bulk_workspace_insert.sql.go (1 hunks)
  • go/pkg/db/plugins/bulk-insert/bulk_insert.go.tmpl (1 hunks)
🧰 Additional context used
🧠 Learnings (22)
go/pkg/db/bulk_key_insert.sql.go (1)
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
go/pkg/db/bulk_key_permission_insert.sql.go (2)
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
Learnt from: chronark
PR: unkeyed/unkey#3560
File: go/deploy/metald/internal/database/repository.go:0-0
Timestamp: 2025-07-15T14:59:30.212Z
Learning: go/deploy/metald cannot currently import helpers from go/pkg/db because it is not yet part of the main Go module; avoid suggesting such imports until the modules are unified.
go/pkg/db/bulk_ratelimit_namespace_insert.sql.go (3)
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
Learnt from: Flo4604
PR: unkeyed/unkey#3606
File: go/pkg/db/replica.go:8-11
Timestamp: 2025-07-16T15:38:53.464Z
Learning: For debugging database replica usage in go/pkg/db/replica.go, it's acceptable to mark QueryRowContext operations as "success" even though SQL errors only surface during row.Scan() calls. The timing metrics are the primary concern for debugging replica performance patterns.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: When querying or updating namespaces in the Unkey dashboard, always scope the operations to the current workspace using `eq(table.workspaceId, ctx.workspace.id)` to prevent cross-workspace access.
go/pkg/db/bulk_key_insert_ratelimit.sql.go (3)
Learnt from: chronark
PR: unkeyed/unkey#2294
File: apps/api/src/pkg/keys/service.ts:268-271
Timestamp: 2024-10-20T07:05:55.471Z
Learning: In `apps/api/src/pkg/keys/service.ts`, `ratelimitAsync` is a table relation, not a column selection. When querying, ensure that table relations are included appropriately, not as columns.
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
Learnt from: Flo4604
PR: unkeyed/unkey#3606
File: go/pkg/db/replica.go:8-11
Timestamp: 2025-07-16T15:38:53.464Z
Learning: For debugging database replica usage in go/pkg/db/replica.go, it's acceptable to mark QueryRowContext operations as "success" even though SQL errors only surface during row.Scan() calls. The timing metrics are the primary concern for debugging replica performance patterns.
go/pkg/db/bulk_ratelimit_override_insert.sql.go (7)
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
Learnt from: Flo4604
PR: unkeyed/unkey#3606
File: go/pkg/db/replica.go:8-11
Timestamp: 2025-07-16T15:38:53.464Z
Learning: For debugging database replica usage in go/pkg/db/replica.go, it's acceptable to mark QueryRowContext operations as "success" even though SQL errors only surface during row.Scan() calls. The timing metrics are the primary concern for debugging replica performance patterns.
Learnt from: chronark
PR: unkeyed/unkey#3560
File: go/deploy/metald/internal/database/repository.go:0-0
Timestamp: 2025-07-15T14:59:30.212Z
Learning: go/deploy/metald cannot currently import helpers from go/pkg/db because it is not yet part of the main Go module; avoid suggesting such imports until the modules are unified.
Learnt from: chronark
PR: unkeyed/unkey#2294
File: apps/api/src/pkg/keys/service.ts:268-271
Timestamp: 2024-10-20T07:05:55.471Z
Learning: In `apps/api/src/pkg/keys/service.ts`, `ratelimitAsync` is a table relation, not a column selection. When querying, ensure that table relations are included appropriately, not as columns.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2918
File: apps/dashboard/lib/trpc/routers/api/overview-api-search.ts:0-0
Timestamp: 2025-02-27T14:35:15.251Z
Learning: When using Drizzle ORM with LIKE queries, use the sql`` tagged template syntax (e.g., sql`${table.name} LIKE ${`%${input.query}%`}`) instead of direct string interpolation to prevent SQL injection attacks.
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-09T08:42:29.316Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,rb,java,c,cpp,h,cs,rs,php,html,css,scss,xml} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.
go/pkg/db/bulk_permission_insert.sql.go (2)
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
Learnt from: Flo4604
PR: unkeyed/unkey#3606
File: go/pkg/db/replica.go:8-11
Timestamp: 2025-07-16T15:38:53.464Z
Learning: For debugging database replica usage in go/pkg/db/replica.go, it's acceptable to mark QueryRowContext operations as "success" even though SQL errors only surface during row.Scan() calls. The timing metrics are the primary concern for debugging replica performance patterns.
go/pkg/db/bulk_key_role_insert.sql.go (2)
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
Learnt from: Flo4604
PR: unkeyed/unkey#3606
File: go/pkg/db/replica.go:8-11
Timestamp: 2025-07-16T15:38:53.464Z
Learning: For debugging database replica usage in go/pkg/db/replica.go, it's acceptable to mark QueryRowContext operations as "success" even though SQL errors only surface during row.Scan() calls. The timing metrics are the primary concern for debugging replica performance patterns.
go/pkg/db/bulk_api_insert.sql.go (2)
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
Learnt from: chronark
PR: unkeyed/unkey#3560
File: go/deploy/metald/internal/database/repository.go:0-0
Timestamp: 2025-07-15T14:59:30.212Z
Learning: go/deploy/metald cannot currently import helpers from go/pkg/db because it is not yet part of the main Go module; avoid suggesting such imports until the modules are unified.
go/pkg/db/bulk_project_insert.sql.go (2)
Learnt from: chronark
PR: unkeyed/unkey#3560
File: go/deploy/metald/internal/database/repository.go:0-0
Timestamp: 2025-07-15T14:59:30.212Z
Learning: go/deploy/metald cannot currently import helpers from go/pkg/db because it is not yet part of the main Go module; avoid suggesting such imports until the modules are unified.
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
go/pkg/db/bulk_route_insert.sql.go (1)
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
go/pkg/db/bulk_identity_insert.sql.go (2)
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2918
File: apps/dashboard/lib/trpc/routers/api/overview-api-search.ts:0-0
Timestamp: 2025-02-27T14:35:15.251Z
Learning: When using Drizzle ORM with LIKE queries, use the sql`` tagged template syntax (e.g., sql`${table.name} LIKE ${`%${input.query}%`}`) instead of direct string interpolation to prevent SQL injection attacks.
go/pkg/db/bulk_audit_log_insert.sql.go (2)
Learnt from: Flo4604
PR: unkeyed/unkey#3606
File: go/pkg/db/replica.go:8-11
Timestamp: 2025-07-16T15:38:53.464Z
Learning: For debugging database replica usage in go/pkg/db/replica.go, it's acceptable to mark QueryRowContext operations as "success" even though SQL errors only surface during row.Scan() calls. The timing metrics are the primary concern for debugging replica performance patterns.
Learnt from: mcstepp
PR: unkeyed/unkey#3242
File: apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-search/index.tsx:7-43
Timestamp: 2025-05-15T16:09:49.243Z
Learning: For type safety issues involving `any` type assertions, the team prefers to address these systematically with linter updates rather than fixing them individually in code reviews.
go/pkg/db/bulk_workspace_insert.sql.go (3)
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: In the Unkey dashboard, when making database queries involving workspaces, use `ctx.workspace.id` directly instead of fetching the workspace separately for better performance and security.
Learnt from: chronark
PR: unkeyed/unkey#2180
File: apps/dashboard/lib/constants/workspace-navigations.tsx:56-118
Timestamp: 2024-10-04T20:44:38.489Z
Learning: When typing the `workspace` parameter in functions like `createWorkspaceNavigation`, prefer importing the `Workspace` type from the database module and picking the necessary keys (e.g., `features`) instead of redefining the interface.
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2872
File: apps/dashboard/lib/trpc/routers/ratelimit/createNamespace.ts:36-39
Timestamp: 2025-04-08T09:34:24.576Z
Learning: When querying or updating namespaces in the Unkey dashboard, always scope the operations to the current workspace using `eq(table.workspaceId, ctx.workspace.id)` to prevent cross-workspace access.
go/pkg/db/bulk_version_insert.sql.go (1)
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-09T08:42:29.316Z
Learning: Applies to go/deploy/{assetmanagerd,billaged,builderd,metald}/**/*.go : When a service's `*.go` code changes significantly, increase the patch-level version number.
go/pkg/db/bulk_role_permission_insert.sql.go (2)
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
Learnt from: Flo4604
PR: unkeyed/unkey#3606
File: go/pkg/db/replica.go:8-11
Timestamp: 2025-07-16T15:38:53.464Z
Learning: For debugging database replica usage in go/pkg/db/replica.go, it's acceptable to mark QueryRowContext operations as "success" even though SQL errors only surface during row.Scan() calls. The timing metrics are the primary concern for debugging replica performance patterns.
go/pkg/db/plugins/bulk-insert/bulk_insert.go.tmpl (1)
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-09T08:42:29.316Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,rb,java,c,cpp,h,cs,rs,php,html,css,scss,xml} : Make sure to add relevant anchor comments whenever a file or piece of code is too complex, very important, confusing, or could have a bug.
go/pkg/db/bulk_build_insert.sql.go (2)
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
Learnt from: chronark
PR: unkeyed/unkey#3560
File: go/deploy/metald/internal/database/repository.go:0-0
Timestamp: 2025-07-15T14:59:30.212Z
Learning: go/deploy/metald cannot currently import helpers from go/pkg/db because it is not yet part of the main Go module; avoid suggesting such imports until the modules are unified.
go/pkg/db/bulk_audit_log_target_insert.sql.go (2)
Learnt from: mcstepp
PR: unkeyed/unkey#3242
File: apps/dashboard/app/(app)/apis/[apiId]/keys/[keyAuthId]/[keyId]/components/controls/components/logs-search/index.tsx:7-43
Timestamp: 2025-05-15T16:09:49.243Z
Learning: For type safety issues involving `any` type assertions, the team prefers to address these systematically with linter updates rather than fixing them individually in code reviews.
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-09T08:42:29.316Z
Learning: Applies to go/deploy/**/*.{go,js,ts,tsx,py,sh,md,txt,json,yaml,yml,ini,rb,java,c,cpp,h,cs,rs,php,html,css,scss,xml} : Use `AIDEV-NOTE:`, `AIDEV-TODO:`, `AIDEV-BUSINESS_RULE:`, or `AIDEV-QUESTION:` (all-caps prefix) as anchor comments aimed at AI and developers.
go/pkg/db/bulk_identity_insert_ratelimit.sql.go (2)
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
Learnt from: Flo4604
PR: unkeyed/unkey#3606
File: go/pkg/db/replica.go:8-11
Timestamp: 2025-07-16T15:38:53.464Z
Learning: For debugging database replica usage in go/pkg/db/replica.go, it's acceptable to mark QueryRowContext operations as "success" even though SQL errors only surface during row.Scan() calls. The timing metrics are the primary concern for debugging replica performance patterns.
go/pkg/db/bulk_build_step_insert.sql.go (1)
Learnt from: CR
PR: unkeyed/unkey#0
File: go/deploy/CLAUDE.md:0-0
Timestamp: 2025-07-09T08:42:29.316Z
Learning: Applies to go/deploy/{assetmanagerd,billaged,builderd,metald}/**/*.go : When a service's `*.go` code changes significantly, increase the patch-level version number.
go/pkg/db/bulk_branch_upsert.sql.go (3)
Learnt from: chronark
PR: unkeyed/unkey#3560
File: go/deploy/metald/internal/database/repository.go:0-0
Timestamp: 2025-07-15T14:59:30.212Z
Learning: go/deploy/metald cannot currently import helpers from go/pkg/db because it is not yet part of the main Go module; avoid suggesting such imports until the modules are unified.
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
Learnt from: Flo4604
PR: unkeyed/unkey#3606
File: go/pkg/db/replica.go:8-11
Timestamp: 2025-07-16T15:38:53.464Z
Learning: For debugging database replica usage in go/pkg/db/replica.go, it's acceptable to mark QueryRowContext operations as "success" even though SQL errors only surface during row.Scan() calls. The timing metrics are the primary concern for debugging replica performance patterns.
go/pkg/db/bulk_branch_insert.sql.go (2)
Learnt from: chronark
PR: unkeyed/unkey#3420
File: go/pkg/hydra/store/gorm/gorm.go:486-498
Timestamp: 2025-07-02T11:51:58.572Z
Learning: The Hydra package (go/pkg/hydra) is planned to be migrated from GORM to sqlc for database operations, which explains why raw SQL queries are acceptable in the current implementation.
Learnt from: chronark
PR: unkeyed/unkey#3560
File: go/deploy/metald/internal/database/repository.go:0-0
Timestamp: 2025-07-15T14:59:30.212Z
Learning: go/deploy/metald cannot currently import helpers from go/pkg/db because it is not yet part of the main Go module; avoid suggesting such imports until the modules are unified.
🧬 Code Graph Analysis (17)
go/pkg/db/bulk_key_permission_insert.sql.go (2)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/bulk_role_insert.sql.go (2)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/bulk_ratelimit_namespace_insert.sql.go (2)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/bulk_key_insert_ratelimit.sql.go (3)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/key_insert_ratelimit.sql_generated.go (1)
  • InsertKeyRatelimitParams (35-44)
go/pkg/db/bulk_ratelimit_override_insert.sql.go (2)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/bulk_permission_insert.sql.go (2)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/bulk_key_role_insert.sql.go (2)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/bulk_key_encryption_insert.sql.go (3)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/key_encryption_insert.sql_generated.go (1)
  • InsertKeyEncryptionParams (18-24)
go/pkg/db/bulk_project_insert.sql.go (3)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/project_insert.sql_generated.go (1)
  • InsertProjectParams (30-41)
go/pkg/db/bulk_workspace_insert.sql.go (3)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/workspace_insert.sql_generated.go (1)
  • InsertWorkspaceParams (37-42)
go/pkg/db/bulk_version_insert.sql.go (3)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/version_insert.sql_generated.go (1)
  • InsertVersionParams (45-58)
go/pkg/db/bulk_role_permission_insert.sql.go (2)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/bulk_build_insert.sql.go (3)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/build_insert.sql_generated.go (1)
  • InsertBuildParams (46-52)
go/pkg/db/bulk_audit_log_target_insert.sql.go (3)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/audit_log_target_insert.sql_generated.go (1)
  • InsertAuditLogTargetParams (40-51)
go/pkg/db/bulk_build_step_insert.sql.go (3)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/build_step_insert.sql_generated.go (1)
  • InsertVersionStepParams (29-35)
go/pkg/db/bulk_keyring_insert.sql.go (2)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/bulk_branch_insert.sql.go (3)
go/pkg/db/queries.go (1)
  • Queries (3-3)
go/pkg/hydra/store/db.go (1)
  • DBTX (8-13)
go/pkg/db/branch_insert.sql_generated.go (1)
  • InsertBranchParams (26-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Build / Build
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Packages / Test
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (58)
go/pkg/db/bulk_key_insert_ratelimit.sql.go (4)

1-9: Generated code structure looks correct.

The file header, package declaration, and imports are appropriate for a generated bulk insert function.


11-12: SQL query structure is correct.

The bulk insert query properly handles the MySQL reserved keyword limit with backticks and includes all necessary columns for the ratelimits table.


14-19: Function signature and early return are well-designed.

The function properly handles the empty slice case and uses appropriate parameter types for bulk operations.


21-45: Bulk insert implementation is correct and efficient.

The query building logic properly constructs parameterized queries with the correct number of placeholders, and the argument collection matches the SQL column order. The execution uses ExecContext appropriately.

go/pkg/db/bulk_project_insert.sql.go (4)

1-9: Generated code structure is consistent.

The file header, package declaration, and imports follow the same pattern as other generated bulk insert files.


11-12: SQL query structure is appropriate for projects table.

The bulk insert query includes all necessary columns for the projects table without requiring backticks for reserved keywords.


14-19: Function signature follows the established pattern.

The function uses the correct parameter type for project inserts and maintains the same early return optimization.


21-47: Bulk insert implementation is correct.

The query building uses the correct number of placeholders (10) for the projects table, and the argument collection order matches the SQL column order perfectly.

go/pkg/db/bulk_keyring_insert.sql.go (3)

1-9: Generated code structure is consistent.

The file header, package declaration, and imports follow the established pattern for generated bulk insert files.


11-12: SQL query structure is correct.

The bulk insert query properly includes all 8 columns for the key_auth table with appropriate backticks.


14-19: Function signature follows the established pattern.

The function uses the correct parameter type for keyring inserts and maintains the same early return optimization.

go/pkg/db/bulk_key_insert.sql.go (4)

1-9: Generated code structure is consistent.

The file header, package declaration, and imports follow the established pattern for generated bulk insert files.


11-12: SQL query structure is comprehensive.

The bulk insert query properly includes all 16 columns for the keys table with appropriate backticks for reserved keywords.


14-19: Function signature follows the established pattern.

The function uses the correct parameter type for key inserts and maintains the same early return optimization.


24-24: Hardcoded null for owner_id is intentional

The InsertKeyParams struct for single-row inserts does not include an OwnerID field, and its generated insertKey SQL omits owner_id as a parameter. The bulk-insert implementation mirrors this design by hardcoding null for owner_id. You can safely ignore this review comment.

Likely an incorrect or invalid review comment.

go/pkg/db/bulk_branch_insert.sql.go (4)

1-9: Generated code structure is consistent.

The file header, package declaration, and imports follow the established pattern for generated bulk insert files.


11-12: SQL query structure is clean and appropriate.

The bulk insert query includes all necessary columns for the branches table without requiring backticks for reserved keywords.


14-19: Function signature follows the established pattern.

The function uses the correct parameter type for branch inserts and maintains the same early return optimization.


21-43: Bulk insert implementation is correct and complete.

The query building uses the correct number of placeholders (6) for the branches table, and the argument collection order matches the SQL column order perfectly without any hardcoded values.

go/pkg/db/bulk_role_insert.sql.go (5)

1-9: Generated code structure looks good.

The file header appropriately indicates this is generated code with clear "DO NOT EDIT" instructions. The imports are minimal and necessary for the bulk insert functionality.


11-12: SQL template correctly structured for bulk insert.

The constant properly defines the base INSERT query with placeholder for dynamic VALUES clause generation.


14-19: Function signature and early return optimization are well-designed.

The function correctly uses the DBTX interface for database operations and includes an efficient early return for empty input slices.


21-27: Query building logic is correctly implemented.

The dynamic generation of value clauses with proper placeholder count (5) matches the column count in the SQL template. The string manipulation is efficient and secure.


29-37: Argument collection correctly uses modern Go conventions.

The code properly uses []any instead of []interface{} and correctly flattens all arguments in the proper order to match the SQL placeholder sequence.

go/pkg/db/bulk_audit_log_insert.sql.go (4)

1-9: Generated code structure is consistent and appropriate.

The file header and imports follow the same pattern as other generated bulk insert files, with proper "DO NOT EDIT" instructions and necessary imports.


11-12: SQL template correctly handles audit_log table structure.

The constant properly defines the bulk insert template with backtick escaping for the table name and comprehensive column listing for the audit_log table.


14-19: Function signature follows established pattern correctly.

The method signature is consistent with other bulk insert methods and includes the efficient early return optimization for empty input slices.


21-27: Query building correctly handles JSON casting and column count.

The dynamic generation of value clauses properly includes 14 placeholders matching the column count, with appropriate CAST(? AS JSON) for the actor_meta field.

go/pkg/db/bulk_audit_log_target_insert.sql.go (4)

1-9: Generated code structure is consistent.

The file header and imports maintain consistency with other generated bulk insert files, properly indicating the generated nature and including necessary imports.


11-12: SQL template correctly structured for audit_log_target table.

The constant properly defines the bulk insert template with appropriate backtick escaping and complete column listing for the audit_log_target table.


14-19: Function signature follows established pattern.

The method signature is consistent with other bulk insert methods and includes the efficient early return optimization for empty input slices.


21-27: Query building correctly handles JSON casting and column count.

The dynamic generation of value clauses properly includes 10 placeholders matching the column count, with appropriate CAST(? AS JSON) for the meta field.

go/pkg/db/bulk_permission_insert.sql.go (5)

1-9: Generated code structure is consistent.

The file header and imports maintain consistency with other generated bulk insert files, properly indicating the generated nature and including necessary imports.


11-12: SQL template correctly structured for permissions table.

The constant properly defines the bulk insert template with complete column listing for the permissions table.


14-19: Function signature follows established pattern.

The method signature is consistent with other bulk insert methods and includes the efficient early return optimization for empty input slices.


21-27: Query building correctly matches column count.

The dynamic generation of value clauses properly includes 6 placeholders matching the column count in the permissions table INSERT statement.


29-38: Argument collection correctly implemented.

The code properly uses []any and correctly flattens all 6 arguments in the proper order to match the SQL column sequence.

go/pkg/db/bulk_ratelimit_namespace_insert.sql.go (5)

1-9: Generated code structure is consistent.

The file header and imports maintain consistency with other generated bulk insert files, properly indicating the generated nature and including necessary imports.


11-12: SQL template correctly structured for ratelimit_namespaces table.

The constant properly defines the bulk insert template with backtick escaping and includes the soft-delete columns (updated_at_m, deleted_at_m) in the column listing.


14-19: Function signature follows established pattern.

The method signature is consistent with other bulk insert methods and includes the efficient early return optimization for empty input slices.


21-27: Query building correctly implements soft-delete pattern.

The value clauses properly include 6 placeholders with the last two hardcoded as NULL for updated_at_m and deleted_at_m, which is appropriate for the soft-delete pattern where these fields are only populated on updates/deletes.


29-36: Argument collection correctly handles soft-delete pattern.

The code properly uses []any and correctly flattens only the 4 non-NULL arguments (id, workspace_id, name, created_at) while the soft-delete columns are handled as NULL in the SQL template.

go/pkg/db/bulk_role_permission_insert.sql.go (1)

15-41: Bulk insert implementation is correct.

The function properly constructs a multi-row INSERT statement with correct placeholder usage and argument ordering that matches the SQL column sequence.

go/pkg/db/bulk_key_role_insert.sql.go (1)

15-41: Bulk insert implementation is correct.

The function properly constructs a multi-row INSERT statement with correct placeholder usage and argument ordering that matches the SQL column sequence.

go/pkg/db/bulk_api_insert.sql.go (1)

15-44: Bulk insert implementation correctly handles soft delete column.

The function properly constructs the bulk insert query with deleted_at_m hardcoded to NULL for new records, which is the expected behavior for soft-delete columns during creation.

go/pkg/db/bulk_ratelimit_override_insert.sql.go (1)

24-24: Hardcoded async value is appropriate.

Setting async to false for all bulk inserted ratelimit overrides is correct behavior for synchronous rate limiting.

go/pkg/db/bulk_key_permission_insert.sql.go (1)

15-41: Bulk insert implementation follows correct pattern.

The function properly constructs a multi-row INSERT statement with correct placeholder usage, assuming the field name verification passes.

go/pkg/db/bulk_build_step_insert.sql.go (1)

1-45: Generated bulk insert implementation looks correct.

The implementation follows the expected pattern with proper parameter handling, SQL construction, and MySQL-specific upsert semantics using ON DUPLICATE KEY UPDATE. The argument flattening matches the SQL column order and the empty slice check prevents unnecessary database calls.

go/pkg/db/bulk_route_insert.sql.go (1)

1-45: Generated bulk insert implementation looks correct.

The implementation follows the expected pattern with proper parameter handling and SQL construction. The 8-parameter structure matches the hostname_routes table schema, and the argument flattening maintains the correct column order.

go/pkg/db/bulk_build_insert.sql.go (1)

1-42: Generated bulk insert implementation with mixed static/dynamic values looks correct.

The implementation correctly handles the builds table structure where some columns have default values ('pending' status, 'docker' build tool, various NULL values) while others are parameterized. The argument flattening matches only the dynamic parameters, which is appropriate for this use case.

go/pkg/db/bulk_identity_insert.sql.go (1)

24-24: JSON casting is appropriately handled.

The CAST(? AS JSON) for the meta column is correctly implemented to ensure proper type handling for JSON data in the database.

go/pkg/db/bulk_key_encryption_insert.sql.go (1)

1-42: Generated bulk insert implementation looks correct.

The implementation follows the expected pattern with proper parameter handling and SQL construction. The 5-parameter structure matches the encrypted_keys table schema, and the argument flattening maintains the correct column order.

go/pkg/db/bulk_version_insert.sql.go (1)

1-49: LGTM! Well-structured bulk insert implementation.

The implementation correctly:

  • Uses parameterized queries to prevent SQL injection
  • Returns early for empty inputs to avoid unnecessary database calls
  • Flattens all arguments into a single slice for efficient execution
  • Maintains proper field ordering consistent with InsertVersionParams
go/pkg/db/bulk_workspace_insert.sql.go (1)

12-12: No action required: created_at_m mapping is correct

The workspaces table schema defines created_at_m (not created_at), and InsertWorkspaceParams.CreatedAt intentionally maps to that column. The bulk insert query and struct field are consistent with the database.

go/pkg/db/plugins/bulk-insert/bulk_insert.go.tmpl (1)

15-19: LGTM! Clean conditional logic for DB argument handling.

The template properly handles both cases - methods with and without explicit DB arguments using clean Go template conditionals.

go/pkg/db/bulk_branch_upsert.sql.go (2)

12-14: LGTM! Proper upsert implementation.

The ON DUPLICATE KEY UPDATE clause correctly updates only the updated_at field using VALUES(updated_at), which ensures the timestamp reflects the actual update time.


22-28: Efficient bulk query construction.

The implementation properly constructs the bulk query by creating the right number of placeholder groups and joining them efficiently.

go/pkg/db/bulk_identity_insert_ratelimit.sql.go (1)

12-15: Proper handling of MySQL reserved words.

Good use of backticks around the table name ratelimits and column name limit to handle MySQL reserved words correctly.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0547a14 and 4299206.

📒 Files selected for processing (1)
  • go/pkg/db/plugins/bulk-insert/utils_test.go (1 hunks)
🧰 Additional context used
🧠 Learnings (1)
go/pkg/db/plugins/bulk-insert/utils_test.go (1)
Learnt from: Flo4604
PR: unkeyed/unkey#3631
File: go/pkg/db/bulk_keyring_insert.sql.go:23-25
Timestamp: 2025-07-17T14:24:20.359Z
Learning: In go/pkg/db/bulk_keyring_insert.sql.go and similar bulk insert generated files, hardcoded zero values for fields like size_approx and size_last_updated_at are intentional and reflect the original SQL query structure, not missing parameters.
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: Test Agent Local / test_agent_local
  • GitHub Check: Test Go API Local / Test
  • GitHub Check: Build / Build
  • GitHub Check: Test API / API Test Local
  • GitHub Check: Test Packages / Test
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (1)
go/pkg/db/plugins/bulk-insert/utils_test.go (1)

7-46: Comprehensive test coverage for the ToCamelCase function.

The test cases provide excellent coverage of the ToCamelCase function, including:

  • Basic snake_case to CamelCase conversion
  • Special abbreviation handling (id → ID)
  • URL suffix handling (url → Url when not at the beginning)
  • Multiple underscores and compound names
  • Edge cases (empty strings, single characters)
  • Numbers in field names

The test structure follows Go testing best practices with table-driven tests and proper subtest naming.

@graphite-app
Copy link

graphite-app bot commented Jul 17, 2025

Graphite Automations

"Notify author when CI fails" took an action on this PR • (07/17/25)

1 teammate was notified to this PR based on Andreas Thomas's automation.

"Post a GIF when PR approved" took an action on this PR • (07/18/25)

1 gif was posted to this PR based on Andreas Thomas's automation.

Copy link
Collaborator

can you make this a draft?
every time I’m starting to look at it you push new stuff 😄

Copy link
Member Author

Flo4604 commented Jul 17, 2025

huh that was just a main

Copy link
Member Author

Flo4604 commented Jul 17, 2025

main update*

Copy link
Member Author

Flo4604 commented Jul 17, 2025

it's fine to review sorry 😅

Copy link
Collaborator

ok cool

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (10)
go/pkg/db/bulk_role_permission_insert.sql.go (1)

39-40: Fix indentation inconsistency.

The database execution logic is correct, but there's an indentation formatting issue that should be addressed.

-    _, err := db.ExecContext(ctx, bulkQuery, allArgs...)
-    return err
+	_, err := db.ExecContext(ctx, bulkQuery, allArgs...)
+	return err
go/pkg/db/bulk_audit_log_target_insert.sql.go (1)

45-46: Fix indentation inconsistency.

The execution logic is correct but has the same indentation formatting issue as other bulk insert files.

-    _, err := db.ExecContext(ctx, bulkQuery, allArgs...)
-    return err
+	_, err := db.ExecContext(ctx, bulkQuery, allArgs...)
+	return err
go/pkg/db/bulk_key_permission_insert.sql.go (1)

35-35: Field name mismatch between SQL column and struct field.

The SQL column is created_at_m but the code accesses arg.CreatedAt, which may cause a field mapping issue.

Based on past review feedback, ensure the struct field matches the SQL column name or update the field access accordingly.

go/pkg/db/bulk_key_role_insert.sql.go (1)

39-40: Fix indentation inconsistency.

The execution logic is correct but has the same indentation formatting issue as other bulk insert files.

-    _, err := db.ExecContext(ctx, bulkQuery, allArgs...)
-    return err
+	_, err := db.ExecContext(ctx, bulkQuery, allArgs...)
+	return err
go/pkg/db/plugins/bulk-insert/utils.go (1)

38-53: CamelCase conversion with proper special case handling.

The function correctly handles sqlc naming conventions with "id" → "ID" and "url" → "Url" (only in compound names). However, the function could be more robust against edge cases.

Consider adding a guard for empty segments to make the function more robust:

 func ToCamelCase(name string) string {
+	if name == "" {
+		return ""
+	}
+	
 	out := ""
 
 	for i, p := range strings.Split(name, "_") {
+		if p == "" {
+			continue
+		}
 		if p == "id" {
 			out += "ID"
 		} else if p == "url" && i > 0 {
go/pkg/db/bulk_identity_insert.sql.go (1)

12-12: Table name escaping with string concatenation.

The table name escaping using string concatenation with backticks is unconventional and reduces readability compared to a raw string literal.

go/pkg/db/bulk_workspace_insert.sql.go (1)

1-42: Implementation looks correct for the generated pattern.

The bulk insert follows the expected structure with the hardcoded default values reflecting the original SQL query design.

go/pkg/db/plugins/bulk-insert/bulk_insert.go.tmpl (1)

1-49: Template structure is well-designed.

The template correctly handles both DB argument and non-DB argument emission patterns, and properly iterates through fields for argument collection.

go/pkg/db/bulk_identity_insert_ratelimit.sql.go (1)

12-17: The updated_at timestamp issue needs attention.

As previously noted, setting updated_at = VALUES(created_at) will use the creation timestamp instead of the actual update time.

go/pkg/db/plugins/bulk-insert/generator.go (1)

106-108: Silent error handling makes debugging difficult.

When parse or render errors occur, the function returns nil without any indication of what went wrong.

Also applies to: 126-128

@chronark chronark enabled auto-merge July 18, 2025 08:00
@graphite-app
Copy link

graphite-app bot commented Jul 18, 2025

Movie gif. Wearing a brown suit jacket, Charlie Day gives us a blank look as several people walk by him in the background. After a second, he raises a hand to give us a thumbs up, then walks out of frame. (Added via Giphy)

@chronark chronark added this pull request to the merge queue Jul 18, 2025
Merged via the queue into main with commit bd4f010 Jul 18, 2025
18 checks passed
@chronark chronark deleted the sqlc_bulk_plugin branch July 18, 2025 08:30
@coderabbitai coderabbitai bot mentioned this pull request Aug 14, 2025
17 tasks
@coderabbitai coderabbitai bot mentioned this pull request Sep 10, 2025
18 tasks
@coderabbitai coderabbitai bot mentioned this pull request Oct 6, 2025
17 tasks
@coderabbitai coderabbitai bot mentioned this pull request Nov 12, 2025
19 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants