Skip to content

feat(postgres): add opt-in unlogged table support#2468

Merged
ReneWerner87 merged 5 commits intogofiber:mainfrom
ofmendi:feat/postgres-unlogged-table
Mar 6, 2026
Merged

feat(postgres): add opt-in unlogged table support#2468
ReneWerner87 merged 5 commits intogofiber:mainfrom
ofmendi:feat/postgres-unlogged-table

Conversation

@ofmendi
Copy link
Copy Markdown

@ofmendi ofmendi commented Feb 8, 2026

Hello everyone,

PostgreSQL offers UNLOGGED tables, which can be great for cache style workloads. While looking through the source, I noticed this isn’t currently configurable, so I thought it would be a nice improvement to support it.

Just a small note: UNLOGGED tables are not crash-safe, so if durability is required.

Summary by CodeRabbit

  • New Features

    • Added a configuration option to create UNLOGGED PostgreSQL tables (defaults to false), allowing reduced write overhead when durability/replication is not required.
  • Documentation

    • Updated docs and examples to describe the unlogged-table option and warn about crash-safety and replication implications.
  • Tests

    • Added tests to verify default (logged) behavior and the unlogged-table option.

Allow creating the storage table as UNLOGGED for
write-heavy cache workloads while preserving
logged tables as the default behavior.
@ofmendi ofmendi requested a review from a team as a code owner February 8, 2026 17:21
@ofmendi ofmendi requested review from ReneWerner87, efectn, gaby and sixcolors and removed request for a team February 8, 2026 17:21
@gemini-code-assist
Copy link
Copy Markdown

Summary of Changes

Hello @ofmendi, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces an opt-in feature to create PostgreSQL tables as UNLOGGED. This functionality is beneficial for cache-style workloads where durability and replication are not critical, offering potential performance improvements. The change provides a new configuration option to control this behavior, ensuring that users can leverage unlogged tables when appropriate, while also being aware of their non-crash-safe nature.

Highlights

  • New Configuration Option: Introduced an Unlogged boolean field to the Config struct, allowing users to specify whether a PostgreSQL table should be created as UNLOGGED.
  • Conditional Table Creation: Modified the table creation logic to dynamically include the UNLOGGED keyword in the CREATE TABLE statement based on the new configuration option.
  • Documentation and Defaults: Updated the README.md with an example of the new Unlogged option and set its default value to false in ConfigDefault.
  • New Test Cases: Added new test cases to verify that tables are created as logged by default and correctly created as unlogged when the Unlogged option is enabled.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • postgres/README.md
    • Added Unlogged field to the example postgres.Config struct.
    • Documented the Unlogged option, explaining its purpose and warning about its non-crash-safe nature.
  • postgres/config.go
    • Introduced Unlogged bool field to the Config struct with documentation.
    • Set Unlogged: false as the default in ConfigDefault.
  • postgres/postgres.go
    • Refactored initQuery into createTableQuery and createIndexQuery constants for clarity.
    • Added createTableTypeClause function to return 'UNLOGGED ' or an empty string based on the Unlogged config.
    • Modified the table creation logic in New to use createTableTypeClause and createTableQuery, allowing for conditional UNLOGGED table creation.
  • postgres/postgres_test.go
    • Added a new helper function tablePersistence to query the relpersistence of a table, indicating if it's permanent ('p') or unlogged ('u').
    • Introduced Test_Postgres_DefaultTableIsLogged to verify that tables are logged by default.
    • Added Test_Postgres_UnloggedTable to confirm that tables are created as unlogged when the Unlogged option is enabled.
Activity
  • The pull request was created by ofmendi to add support for opt-in unlogged PostgreSQL tables.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an opt-in feature for creating UNLOGGED tables in PostgreSQL, which is a valuable addition for cache-like workloads. The changes are well-structured, including updates to the configuration, documentation, and the addition of relevant tests to verify the new functionality. My review includes one critical security-related comment regarding a potential SQL injection vulnerability in how database queries are constructed, along with a suggested fix.

Comment thread postgres/postgres.go Outdated
@ReneWerner87
Copy link
Copy Markdown
Member

@ofmendi can you check the ai comments

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 8, 2026

Walkthrough

Adds a public Unlogged bool to Postgres storage Config, uses that option when creating tables (optionally CREATE UNLOGGED TABLE), switches init logic to sanitized, parameterized table/index creation, and adds tests verifying table persistence ("p" vs "u").

Changes

Cohort / File(s) Summary
Configuration & Documentation
postgres/README.md, postgres/config.go
Add exported Unlogged bool to Config with docs; include Unlogged: false in ConfigDefault; update example usage to show the field.
Initialization & SQL
postgres/postgres.go
Introduce deterministic table type helper and index-name builder; replace init-loop with explicit, parameterized create/drop/exists queries; sanitize fullTableName and indexName; update storage SQL to reference fullTableName.
Tests
postgres/postgres_test.go
Add tablePersistence test helper and two tests asserting default tables are logged ("p") and that cfg.Unlogged = true creates unlogged tables ("u").

Sequence Diagram(s)

(Skipped)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • gaby
  • sixcolors
  • ReneWerner87
  • efectn

Poem

🐰 I nudged the schema with a careful paw,
Whispered "UNLOGGED" — no journal to gnaw.
Rows skip their journals and dance in the sun,
Indexes hum as the migrations run.
Hop — a small change, a lighter run.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'feat(postgres): add opt-in unlogged table support' directly and clearly summarizes the main change: adding optional support for PostgreSQL unlogged tables.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
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

🤖 Fix all issues with AI agents
In `@postgres/postgres.go`:
- Around line 36-41: The hardcoded index name causes collisions; change
createIndexQuery so it accepts a dynamic index name (e.g. `CREATE INDEX IF NOT
EXISTS %s ON %s (e);`) and, where the index is created, construct a unique index
name from the table (e.g. take cfg.Table, replace dots/illegal chars with
underscores and prefix with "e_") then call fmt.Sprintf(createIndexQuery,
indexName, cfg.Table). Update references to createIndexQuery and the code path
that executes the index creation so each table gets its own index name.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4234e12 and 1130be3.

📒 Files selected for processing (4)
  • postgres/README.md
  • postgres/config.go
  • postgres/postgres.go
  • postgres/postgres_test.go
🧰 Additional context used
🧠 Learnings (5)
📚 Learning: 2025-01-30T12:29:41.774Z
Learnt from: the-real-i9
Repo: gofiber/storage PR: 1562
File: neo4j/neo4j.go:50-52
Timestamp: 2025-01-30T12:29:41.774Z
Learning: Storage drivers in the gofiber/storage repository intentionally panic on connection errors as a design choice to fail fast when storage is unavailable.

Applied to files:

  • postgres/config.go
  • postgres/README.md
📚 Learning: 2025-02-12T11:24:31.153Z
Learnt from: ReneWerner87
Repo: gofiber/storage PR: 0
File: :0-0
Timestamp: 2025-02-12T11:24:31.153Z
Learning: The storage package in gofiber/storage repository can be used independently of the Fiber web framework.

Applied to files:

  • postgres/config.go
  • postgres/README.md
📚 Learning: 2025-05-01T11:46:33.057Z
Learnt from: gaby
Repo: gofiber/storage PR: 1638
File: surrealdb/surrealdb.go:34-43
Timestamp: 2025-05-01T11:46:33.057Z
Learning: In the gofiber/storage package, storage drivers should panic when encountering database connection failures during initialization rather than returning errors. This is an intentional design pattern to ensure applications fail fast and explicitly when critical database dependencies are unavailable.

Applied to files:

  • postgres/config.go
  • postgres/README.md
📚 Learning: 2024-07-01T15:47:48.701Z
Learnt from: luk3skyw4lker
Repo: gofiber/storage PR: 1342
File: clickhouse/clickhouse.go:114-115
Timestamp: 2024-07-01T15:47:48.701Z
Learning: The pattern of returning `nil` for empty keys or values is consistent across different storage drivers in the gofiber/storage repository, including Redis, Rueidis, MongoDB, and Clickhouse.

Applied to files:

  • postgres/config.go
  • postgres/README.md
📚 Learning: 2024-07-26T21:00:12.902Z
Learnt from: luk3skyw4lker
Repo: gofiber/storage PR: 1342
File: clickhouse/clickhouse.go:57-58
Timestamp: 2024-07-26T21:00:12.902Z
Learning: The pattern of returning `nil` for empty keys or values is consistent across different storage drivers in the gofiber/storage repository, including Redis, Rueidis, and MongoDB.

Applied to files:

  • postgres/config.go
  • postgres/README.md
🧬 Code graph analysis (1)
postgres/postgres_test.go (1)
postgres/postgres.go (1)
  • Storage (16-26)
🔇 Additional comments (6)
postgres/config.go (1)

64-69: Well-documented opt-in field with a safe default.

The Unlogged field correctly defaults to false (Go zero value), the warning about crash safety and replication is helpful, and no override in configDefault() is needed since the zero value is the desired default.

postgres/postgres.go (2)

54-60: Clean helper with correct spacing.

The trailing space in "UNLOGGED " correctly produces CREATE UNLOGGED TABLE when combined with the format string on line 36.


109-118: Initialization logic for unlogged tables looks correct.

The table-exists check guards creation, createTableTypeClause is applied only at creation time (matching the documented "applies only when the table is created" behavior), and the panic-on-error pattern is consistent with the repository's design conventions.

postgres/postgres_test.go (2)

67-88: Well-structured test helper.

Uses parameterized queries (avoiding SQL injection), correctly parses schema-qualified names with SplitN, and directly queries the catalog for an authoritative answer.


229-248: Good test coverage for both logged and unlogged table creation.

Both tests use unique table names for isolation and correctly assert the expected relpersistence values ("p" for logged, "u" for unlogged).

postgres/README.md (1)

121-127: Documentation accurately mirrors the code.

The config block, default values, and warnings about crash safety and replication are consistent with the implementation in config.go.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment thread postgres/postgres.go Outdated
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds an opt-in configuration flag for creating PostgreSQL storage tables as UNLOGGED, enabling better performance for cache-like workloads while documenting the durability tradeoff.

Changes:

  • Introduces Config.Unlogged (default false) to create tables as UNLOGGED on initialization.
  • Updates table/index creation logic to use a generated, sanitized full table name and a per-table index name.
  • Adds tests asserting default (logged) vs. unlogged table persistence, and updates README/config docs.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
postgres/postgres.go Adds UNLOGGED clause support; refactors table/index creation and SQL strings to use sanitized identifiers.
postgres/config.go Adds Unlogged config field and default value.
postgres/postgres_test.go Adds helper + tests verifying relpersistence for logged vs. unlogged tables.
postgres/README.md Documents Unlogged option, defaults, and warning.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread postgres/postgres.go
Comment thread postgres/postgres.go Outdated
Comment thread postgres/postgres.go Outdated
Ömer Faruk Mendi added 3 commits February 19, 2026 12:13
Use query arguments instead of string interpolation for schema and table filters when checking table existence and validating table schema.
Generate a deterministic hashed index name to avoid identifier truncation collisions for long table names while keeping CREATE INDEX IF NOT EXISTS behavior stable.
Use explicit TABLE and UNLOGGED TABLE clauses to avoid whitespace-sensitive SQL assembly when building CREATE TABLE statements.
Copy link
Copy Markdown
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.

🧹 Nitpick comments (1)
postgres/postgres.go (1)

118-128: Consider wrapping both DDL statements in a single transaction to avoid a partially-initialised table.

If CREATE TABLE succeeds but CREATE INDEX fails (e.g. out-of-disk, lock timeout), the table is persisted. On the next restart tableExists will be true, so the init block is skipped entirely and the table is left permanently un-indexed. Every GC run (DELETE … WHERE e <= $1 AND e != 0) will then do a full sequential scan for the lifetime of that table.

PostgreSQL supports transactional DDL, so both statements can be rolled back atomically on failure:

♻️ Proposed fix: wrap DDL in an explicit transaction
-	// Init database queries
-	if !tableExists {
-		for _, query := range []string{
-			fmt.Sprintf(createTableQuery, createTableType(cfg.Unlogged), fullTableName),
-			fmt.Sprintf(createIndexQuery, indexName, fullTableName),
-		} {
-			if _, err := db.Exec(context.Background(), query); err != nil {
-				db.Close()
-				panic(err)
-			}
-		}
-	}
+	// Init database queries
+	if !tableExists {
+		tx, err := db.Begin(context.Background())
+		if err != nil {
+			db.Close()
+			panic(err)
+		}
+		for _, query := range []string{
+			fmt.Sprintf(createTableQuery, createTableType(cfg.Unlogged), fullTableName),
+			fmt.Sprintf(createIndexQuery, indexName, fullTableName),
+		} {
+			if _, err := tx.Exec(context.Background(), query); err != nil {
+				_ = tx.Rollback(context.Background())
+				db.Close()
+				panic(err)
+			}
+		}
+		if err := tx.Commit(context.Background()); err != nil {
+			db.Close()
+			panic(err)
+		}
+	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@postgres/postgres.go` around lines 118 - 128, Wrap the two DDL statements
(the fmt.Sprintf(createTableQuery, createTableType(cfg.Unlogged), fullTableName)
and fmt.Sprintf(createIndexQuery, indexName, fullTableName)) in a single
explicit transaction so they commit or roll back together: when tableExists is
false, start a transaction on db (use the pool/conn's Begin/BeginTx to get tx),
execute both queries via tx.Exec (not db.Exec), and if any Exec returns an error
call tx.Rollback and handle/propagate the error; only call tx.Commit on success
and avoid leaving the table created without its index.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a2eb538 and 839e02b.

📒 Files selected for processing (1)
  • postgres/postgres.go
🔇 Additional comments (3)
postgres/postgres.go (3)

56-62: LGTM – createTableType cleanly encodes the DDL variant.

Returning the full "UNLOGGED TABLE" / "TABLE" fragment avoids any whitespace-sensitive string assembly at the call site, exactly as described in the commit message. Both produce valid PostgreSQL DDL when combined with the sanitised fullTableName.


34-48: Parameterized information_schema lookups are a solid security improvement.

Replacing fmt.Sprintf string interpolation in checkTableExistsQuery and checkSchemaQuery with $1/$2 bind parameters eliminates any residual injection risk and is idiomatic pgx v5. LGTM.


96-97: pgx.Identifier.Sanitize() usage is correct for both identifiers.

  • fullTableName (2-element slice) → "schema"."table": proper schema-qualified DDL/DML.
  • indexName (1-element slice) → "idx_e_…": no schema prefix is needed — PostgreSQL automatically creates the index in the table's own schema.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@postgres/postgres.go`:
- Around line 64-67: The buildIndexName change correctly replaces the hardcoded
"e" with a SHA-256-derived suffix; leave the implementation of buildIndexName
as-is (it uses sha256.Sum256 and hex.EncodeToString(hash[:6]) to produce
"idx_e_"+12-hex-char suffix), no further changes required since the resulting
identifier length is well under PostgreSQL's 63-char limit and collision risk is
negligible.

---

Nitpick comments:
In `@postgres/postgres.go`:
- Around line 118-128: Wrap the two DDL statements (the
fmt.Sprintf(createTableQuery, createTableType(cfg.Unlogged), fullTableName) and
fmt.Sprintf(createIndexQuery, indexName, fullTableName)) in a single explicit
transaction so they commit or roll back together: when tableExists is false,
start a transaction on db (use the pool/conn's Begin/BeginTx to get tx), execute
both queries via tx.Exec (not db.Exec), and if any Exec returns an error call
tx.Rollback and handle/propagate the error; only call tx.Commit on success and
avoid leaving the table created without its index.

@ReneWerner87 ReneWerner87 merged commit fd8aa05 into gofiber:main Mar 6, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants