Skip to content

GO-3153 sqlite spacestore #1092

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 20 commits into from
Apr 18, 2024
Merged

GO-3153 sqlite spacestore #1092

merged 20 commits into from
Apr 18, 2024

Conversation

cheggaaa
Copy link
Member

@cheggaaa cheggaaa commented Apr 8, 2024


  • I understand that contributing to this repository will require me to agree with the CLA

Description

What type of PR is this? (check all applicable)

  • 🍕 Feature
  • 🐛 Bug Fix
  • 📝 Documentation Update
  • 🎨 Style
  • 🧑‍💻 Code Refactor
  • 🔥 Performance Improvements
  • ✅ Test
  • 🤖 Build
  • 🔁 CI

Related Tickets & Documents

Mobile & Desktop Screenshots/Recordings

Added tests?

  • 👍 yes
  • 🙅 no, because they aren't needed
  • 🙋 no, because I need help

Added to documentation?

  • 📜 README.md
  • 📓 tech-docs
  • 🙅 no documentation needed

[optional] Are there any post-deployment tasks we need to perform?

Summary by CodeRabbit

  • New Features
    • Introduced a new storage management system supporting both SQLite and Badger databases.
    • Added SQLite functionality for creating, updating, and managing spaces and trees.
    • Implemented storage service for spaces in SQLite, including operations such as space creation, deletion, and existence checks.
  • Refactor
    • Renamed the storage package to enhance clarity and organization.
  • Tests
    • Added tests to verify SQLite storage services and operations.
  • Documentation
    • Deprecated the SpaceStorage() method in favor of more specific storage implementations.
  • Chores
    • Modified workflow scripts to enable CGO during mock generation.
  • Bug Fixes
    • Improved error handling in file synchronization processes to manage specific datastore errors more effectively.

Copy link

github-actions bot commented Apr 8, 2024

New Coverage 43.7% of statements
Patch Coverage 69.6% of changed statements (343/493)

Coverage provided by https://github.com/seriousben/go-patch-cover-action

@cheggaaa cheggaaa force-pushed the GO-3153-sqlite-spacestore branch from 6397658 to 519313b Compare April 8, 2024 17:08
Copy link

coderabbitai bot commented Apr 15, 2024

Walkthrough

The update encompasses a significant overhaul in storage management within the system. Key changes include the renaming of the storage package to badgerstorage, introduction of a new storage.go file to handle storage across SQLite and Badger databases, implementation of SQLite for space storage management, extensive testing of these functionalities, and adjustments in error handling and configuration to accommodate these enhancements.

Changes

Files Change Summary
space/spacecore/storage/*.go Renamed package from "storage" to "badgerstorage".
space/spacecore/storage/storage.go, .../sqlitestorage/*.go, .../sqlitestorage/service_test.go Introduces SQLite storage management for spaces and trees, covering CRUD operations and testing.
core/block/cache/mock_cache/mock_ObjectGetterComponent.go Provides mock implementations for ObjectGetterComponent.
core/filestorage/filesync/filesync.go Includes enhanced error handling with new imports.
.github/workflows/test.yml Enables CGO during mock generation.
pkg/lib/datastore/datastore.go Deprecates SpaceStorage() method.
core/anytype/config/config.go Adds method and logic for determining SpaceStorageMode.

🐰✨🌟
In the world of code, changes are a must,
From storage old to new, in SQLite we trust.
Trees and spaces, neatly align,
With tests to ensure, all is divine.
Hop and skip, the rabbit's cheer,
For cleaner code, we hold dear! 🌲📦🚀
🐰✨🌟


Recent Review Details

Configuration used: CodeRabbit UI

Commits Files that changed from the base of the PR and between 6b4f892 and d08e3ae.
Files ignored due to path filters (2)
  • go.mod is excluded by !**/*.mod
  • go.sum is excluded by !**/*.sum
Files selected for processing (2)
  • pkg/lib/datastore/clientds/clientds.go (7 hunks)
  • pkg/lib/datastore/clientds/syncer.go (1 hunks)
Files skipped from review as they are similar to previous changes (2)
  • pkg/lib/datastore/clientds/clientds.go
  • pkg/lib/datastore/clientds/syncer.go

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

Share
Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate interesting stats about this repository and render them as a table.
    • @coderabbitai show all the console.log statements in this repository.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

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 as PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger a review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai help to get help.

Additionally, you can add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.

CodeRabbit Configration 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 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 for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@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: 7

Comment on lines 69 to 150
func createSpaceStorage(s *storageService, payload spacestorage.SpaceStorageCreatePayload) (spacestorage.SpaceStorage, error) {
tx, err := s.writeDb.Begin()
if err != nil {
return nil, err
}

if _, err = tx.Stmt(s.stmt.createSpace).Exec(
// space(id, header, settingsId, aclId)
payload.SpaceHeaderWithId.Id,
payload.SpaceHeaderWithId.RawHeader,
payload.SpaceSettingsWithId.Id,
payload.AclWithId.Id,
); err != nil {
_ = tx.Rollback()
if isUniqueConstraint(err) {
return nil, spacestorage.ErrSpaceStorageExists
}
return nil, err
}

if _, err = tx.Stmt(s.stmt.createTree).Exec(
// settings tree (id, spaceId, heads, type)
payload.SpaceSettingsWithId.Id,
payload.SpaceHeaderWithId.Id,
treestorage.CreateHeadsPayload([]string{payload.SpaceSettingsWithId.Id}),
treeTypeSettings,
); err != nil {
_ = tx.Rollback()
return nil, err
}

if _, err = tx.Stmt(s.stmt.createTree).Exec(
// acl list tree (id, spaceId, heads, type)
payload.AclWithId.Id,
payload.SpaceHeaderWithId.Id,
payload.AclWithId.Id,
treeTypeList,
); err != nil {
_ = tx.Rollback()
return nil, err
}

if _, err = tx.Stmt(s.stmt.createChange).Exec(
// settings change (id, spaceId, treeId, data)
payload.SpaceSettingsWithId.Id,
payload.SpaceHeaderWithId.Id,
payload.SpaceSettingsWithId.Id,
payload.SpaceSettingsWithId.RawChange,
); err != nil {
_ = tx.Rollback()
return nil, err
}

if _, err = tx.Stmt(s.stmt.createChange).Exec(
// acl change (id, spaceId, treeId, data)
payload.AclWithId.Id,
payload.SpaceHeaderWithId.Id,
payload.AclWithId.Id,
payload.AclWithId.Payload,
); err != nil {
_ = tx.Rollback()
return nil, err
}

if err = tx.Commit(); err != nil {
return nil, err
}

ss := &spaceStorage{
spaceId: payload.SpaceHeaderWithId.Id,
service: s,
spaceSettingsId: payload.SpaceSettingsWithId.Id,
header: payload.SpaceHeaderWithId,
aclId: payload.AclWithId.Id,
}

if ss.aclStorage, err = newListStorage(ss, ss.aclId); err != nil {
_ = tx.Rollback()
return nil, err
}

return ss, nil
Copy link

Choose a reason for hiding this comment

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

Optimize error handling in createSpaceStorage by consolidating the repeated tx.Rollback() calls into a deferred function. This will ensure that rollback is attempted in all error scenarios without repeating the code.

- if _, err = tx.Stmt(s.stmt.createSpace).Exec(
+ defer func() {
+   if err != nil {
+     _ = tx.Rollback()
+   }
+ }()
+ if _, err = tx.Stmt(s.stmt.createSpace).Exec(

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
func createSpaceStorage(s *storageService, payload spacestorage.SpaceStorageCreatePayload) (spacestorage.SpaceStorage, error) {
tx, err := s.writeDb.Begin()
if err != nil {
return nil, err
}
if _, err = tx.Stmt(s.stmt.createSpace).Exec(
// space(id, header, settingsId, aclId)
payload.SpaceHeaderWithId.Id,
payload.SpaceHeaderWithId.RawHeader,
payload.SpaceSettingsWithId.Id,
payload.AclWithId.Id,
); err != nil {
_ = tx.Rollback()
if isUniqueConstraint(err) {
return nil, spacestorage.ErrSpaceStorageExists
}
return nil, err
}
if _, err = tx.Stmt(s.stmt.createTree).Exec(
// settings tree (id, spaceId, heads, type)
payload.SpaceSettingsWithId.Id,
payload.SpaceHeaderWithId.Id,
treestorage.CreateHeadsPayload([]string{payload.SpaceSettingsWithId.Id}),
treeTypeSettings,
); err != nil {
_ = tx.Rollback()
return nil, err
}
if _, err = tx.Stmt(s.stmt.createTree).Exec(
// acl list tree (id, spaceId, heads, type)
payload.AclWithId.Id,
payload.SpaceHeaderWithId.Id,
payload.AclWithId.Id,
treeTypeList,
); err != nil {
_ = tx.Rollback()
return nil, err
}
if _, err = tx.Stmt(s.stmt.createChange).Exec(
// settings change (id, spaceId, treeId, data)
payload.SpaceSettingsWithId.Id,
payload.SpaceHeaderWithId.Id,
payload.SpaceSettingsWithId.Id,
payload.SpaceSettingsWithId.RawChange,
); err != nil {
_ = tx.Rollback()
return nil, err
}
if _, err = tx.Stmt(s.stmt.createChange).Exec(
// acl change (id, spaceId, treeId, data)
payload.AclWithId.Id,
payload.SpaceHeaderWithId.Id,
payload.AclWithId.Id,
payload.AclWithId.Payload,
); err != nil {
_ = tx.Rollback()
return nil, err
}
if err = tx.Commit(); err != nil {
return nil, err
}
ss := &spaceStorage{
spaceId: payload.SpaceHeaderWithId.Id,
service: s,
spaceSettingsId: payload.SpaceSettingsWithId.Id,
header: payload.SpaceHeaderWithId,
aclId: payload.AclWithId.Id,
}
if ss.aclStorage, err = newListStorage(ss, ss.aclId); err != nil {
_ = tx.Rollback()
return nil, err
}
return ss, nil
func createSpaceStorage(s *storageService, payload spacestorage.SpaceStorageCreatePayload) (spacestorage.SpaceStorage, error) {
tx, err := s.writeDb.Begin()
if err != nil {
return nil, err
}
defer func() {
if err != nil {
_ = tx.Rollback()
}
}()
if _, err = tx.Stmt(s.stmt.createSpace).Exec(
// space(id, header, settingsId, aclId)
payload.SpaceHeaderWithId.Id,
payload.SpaceHeaderWithId.RawHeader,
payload.SpaceSettingsWithId.Id,
payload.AclWithId.Id,
); err != nil {
if isUniqueConstraint(err) {
return nil, spacestorage.ErrSpaceStorageExists
}
return nil, err
}
if _, err = tx.Stmt(s.stmt.createTree).Exec(
// settings tree (id, spaceId, heads, type)
payload.SpaceSettingsWithId.Id,
payload.SpaceHeaderWithId.Id,
treestorage.CreateHeadsPayload([]string{payload.SpaceSettingsWithId.Id}),
treeTypeSettings,
); err != nil {
return nil, err
}
if _, err = tx.Stmt(s.stmt.createTree).Exec(
// acl list tree (id, spaceId, heads, type)
payload.AclWithId.Id,
payload.SpaceHeaderWithId.Id,
payload.AclWithId.Id,
treeTypeList,
); err != nil {
return nil, err
}
if _, err = tx.Stmt(s.stmt.createChange).Exec(
// settings change (id, spaceId, treeId, data)
payload.SpaceSettingsWithId.Id,
payload.SpaceHeaderWithId.Id,
payload.SpaceSettingsWithId.Id,
payload.SpaceSettingsWithId.RawChange,
); err != nil {
return nil, err
}
if _, err = tx.Stmt(s.stmt.createChange).Exec(
// acl change (id, spaceId, treeId, data)
payload.AclWithId.Id,
payload.SpaceHeaderWithId.Id,
payload.AclWithId.Id,
payload.AclWithId.Payload,
); err != nil {
return nil, err
}
if err = tx.Commit(); err != nil {
return nil, err
}
ss := &spaceStorage{
spaceId: payload.SpaceHeaderWithId.Id,
service: s,
spaceSettingsId: payload.SpaceSettingsWithId.Id,
header: payload.SpaceHeaderWithId,
aclId: payload.AclWithId.Id,
}
if ss.aclStorage, err = newListStorage(ss, ss.aclId); err != nil {
return nil, err
}
return ss, nil
}

Comment on lines +12 to +34
func TestListStorage_AddRawRecord(t *testing.T) {
fx := newFixture(t)
defer fx.finish(t)

payload := spaceTestPayload()
ss, err := fx.CreateSpaceStorage(payload)
require.NoError(t, err)
defer func() { _ = ss.Close(ctx) }()

ls, err := ss.AclStorage()
require.NoError(t, err)

var rec = &consensusproto.RawRecordWithId{
Id: "id",
Payload: []byte("data"),
}

require.NoError(t, ls.AddRawRecord(ctx, rec))

res, err := ls.GetRawRecord(ctx, rec.Id)
require.NoError(t, err)
assert.Equal(t, rec, res)
}
Copy link

Choose a reason for hiding this comment

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

Ensure proper context management in deferred function calls.

- defer func() { _ = ss.Close(ctx) }()
+ defer func() { _ = ss.Close() }()

The ctx variable is not defined within the scope of the deferred function, which might lead to a runtime error. It seems like the Close method does not require a context parameter based on typical Go context usage in similar functions.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
func TestListStorage_AddRawRecord(t *testing.T) {
fx := newFixture(t)
defer fx.finish(t)
payload := spaceTestPayload()
ss, err := fx.CreateSpaceStorage(payload)
require.NoError(t, err)
defer func() { _ = ss.Close(ctx) }()
ls, err := ss.AclStorage()
require.NoError(t, err)
var rec = &consensusproto.RawRecordWithId{
Id: "id",
Payload: []byte("data"),
}
require.NoError(t, ls.AddRawRecord(ctx, rec))
res, err := ls.GetRawRecord(ctx, rec.Id)
require.NoError(t, err)
assert.Equal(t, rec, res)
}
func TestListStorage_AddRawRecord(t *testing.T) {
fx := newFixture(t)
defer fx.finish(t)
payload := spaceTestPayload()
ss, err := fx.CreateSpaceStorage(payload)
require.NoError(t, err)
defer func() { _ = ss.Close() }()
ls, err := ss.AclStorage()
require.NoError(t, err)
var rec = &consensusproto.RawRecordWithId{
Id: "id",
Payload: []byte("data"),
}
require.NoError(t, ls.AddRawRecord(ctx, rec))
res, err := ls.GetRawRecord(ctx, rec.Id)
require.NoError(t, err)
assert.Equal(t, rec, res)
}

Comment on lines +14 to +27
func newListStorage(ss *spaceStorage, treeId string) (liststorage.ListStorage, error) {
ts := &listStorage{
listId: treeId,
spaceStorage: ss,
service: ss.service,
}
if err := ss.service.stmt.loadTreeHeads.QueryRow(treeId).Scan(&ts.head); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, treestorage.ErrUnknownTreeId
}
return nil, err
}
return ts, nil
}
Copy link

@coderabbitai coderabbitai bot Apr 15, 2024

Choose a reason for hiding this comment

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

Handle potential SQL injection vulnerabilities.

- if err := ss.service.stmt.loadTreeHeads.QueryRow(treeId).Scan(&ts.head); err != nil {
+ if err := ss.service.stmt.loadTreeHeads.QueryRow(sql.EscapeString(treeId)).Scan(&ts.head); err != nil {

The treeId parameter is directly used in a SQL query, which could lead to SQL injection if not properly sanitized. Consider using parameterized queries or properly escaping inputs to mitigate this risk.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
func newListStorage(ss *spaceStorage, treeId string) (liststorage.ListStorage, error) {
ts := &listStorage{
listId: treeId,
spaceStorage: ss,
service: ss.service,
}
if err := ss.service.stmt.loadTreeHeads.QueryRow(treeId).Scan(&ts.head); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, treestorage.ErrUnknownTreeId
}
return nil, err
}
return ts, nil
}
func newListStorage(ss *spaceStorage, treeId string) (liststorage.ListStorage, error) {
ts := &listStorage{
listId: treeId,
spaceStorage: ss,
service: ss.service,
}
if err := ss.service.stmt.loadTreeHeads.QueryRow(sql.EscapeString(treeId)).Scan(&ts.head); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, treestorage.ErrUnknownTreeId
}
return nil, err
}
return ts, nil
}

Copy link
Member Author

Choose a reason for hiding this comment

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

Escape is not needed because there is a prepared statement. No way to do SQL injection

Copy link

Choose a reason for hiding this comment

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

@cheggaaa, thank you for your clarification. You are correct that the use of prepared statements effectively mitigates the risk of SQL injection. The QueryRow method in this context is indeed safe as it operates on a prepared statement, ensuring that treeId is handled securely. I appreciate your attention to detail and ensuring the security of the code.


New Learnings Added
User: cheggaaa"
URL: https://github.com/anyproto/anytype-heart/pull/1092

Timestamp: 2024-04-15T11:56:47.495Z
Learning: The `QueryRow` method used with `treeId` in the `newListStorage` function of the `sqlitestorage` package is safe from SQL injection due to the use of prepared statements.

Note: Learnings are effective only in the context of specific code segments. To apply general coding guidelines, please configure review instructions.

Comment on lines +39 to +118
func initStmts(s *storageService) (err error) {
if s.stmt.createSpace, err = s.writeDb.Prepare(`INSERT INTO spaces(id, header, settingsId, aclId) VALUES (?, ?, ?, ?)`); err != nil {
return
}
if s.stmt.createTree, err = s.writeDb.Prepare(`INSERT INTO trees(id, spaceId, heads, type) VALUES(?, ?, ?, ?)`); err != nil {
return
}
if s.stmt.createChange, err = s.writeDb.Prepare(`INSERT INTO changes(id, spaceId, treeId, data) VALUES(?, ?, ?, ?)`); err != nil {
return
}
if s.stmt.updateSpaceHash, err = s.writeDb.Prepare(`UPDATE spaces SET hash = ? WHERE id = ?`); err != nil {
return
}
if s.stmt.updateSpaceOldHash, err = s.writeDb.Prepare(`UPDATE spaces SET oldHash = ? WHERE id = ?`); err != nil {
return
}
if s.stmt.updateSpaceIsCreated, err = s.writeDb.Prepare(`UPDATE spaces SET isCreated = ? WHERE id = ?`); err != nil {
return
}
if s.stmt.updateSpaceIsDeleted, err = s.writeDb.Prepare(`UPDATE spaces SET isDeleted = ? WHERE id = ?`); err != nil {
return
}
if s.stmt.treeIdsBySpace, err = s.readDb.Prepare(`SELECT id FROM trees WHERE spaceId = ? AND type != 1`); err != nil {
return
}
if s.stmt.updateTreeDelStatus, err = s.writeDb.Prepare(`UPDATE trees SET deleteStatus = ? WHERE id = ?`); err != nil {
return
}
if s.stmt.treeDelStatus, err = s.readDb.Prepare(`SELECT deleteStatus FROM trees WHERE id = ?`); err != nil {
return
}
if s.stmt.change, err = s.readDb.Prepare(`SELECT data FROM changes WHERE id = ? AND spaceId = ?`); err != nil {
return
}
if s.stmt.hasTree, err = s.readDb.Prepare(`SELECT COUNT(*) FROM trees WHERE id = ? AND spaceId = ?`); err != nil {
return
}
if s.stmt.hasChange, err = s.readDb.Prepare(`SELECT COUNT(*) FROM changes WHERE id = ? AND treeId = ?`); err != nil {
return
}
if s.stmt.updateTreeHeads, err = s.writeDb.Prepare(`UPDATE trees SET heads = ? WHERE id = ?`); err != nil {
return
}
if s.stmt.deleteTree, err = s.writeDb.Prepare(`DELETE FROM trees WHERE id = ?`); err != nil {
return
}
if s.stmt.deleteChangesByTree, err = s.writeDb.Prepare(`DELETE FROM changes WHERE treeId = ?`); err != nil {
return
}
if s.stmt.loadTreeHeads, err = s.readDb.Prepare(`SELECT heads FROM trees WHERE id = ?`); err != nil {
return
}
if s.stmt.loadSpace, err = s.readDb.Prepare(`SELECT header, settingsId, aclId, hash, oldHash, isDeleted FROM spaces WHERE id = ?`); err != nil {
return
}
if s.stmt.spaceIds, err = s.readDb.Prepare(`SELECT id FROM spaces`); err != nil {
return
}
if s.stmt.spaceIsCreated, err = s.readDb.Prepare(`SELECT isCreated FROM spaces WHERE id = ?`); err != nil {
return
}
if s.stmt.getBind, err = s.readDb.Prepare(`SELECT spaceId FROM binds WHERE objectId = ?`); err != nil {
return
}
if s.stmt.upsertBind, err = s.writeDb.Prepare(`INSERT INTO binds (objectId, spaceId) VALUES (?, ?) ON CONFLICT (objectId) DO UPDATE SET spaceId = ?`); err != nil {
return
}
if s.stmt.deleteSpace, err = s.writeDb.Prepare(`DELETE FROM spaces WHERE id = ?`); err != nil {
return
}
if s.stmt.deleteTreesBySpace, err = s.writeDb.Prepare(`DELETE FROM trees WHERE spaceId = ?`); err != nil {
return
}
if s.stmt.deleteChangesBySpace, err = s.writeDb.Prepare(`DELETE FROM changes WHERE spaceId = ?`); err != nil {
return
}
if s.stmt.deleteBindsBySpace, err = s.writeDb.Prepare(`DELETE FROM binds WHERE spaceId = ?`); err != nil {
return
}
return
Copy link

Choose a reason for hiding this comment

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

Ensure robust error handling in initStmts.

The function initStmts initializes various SQL statements and returns immediately upon encountering an error. This approach can lead to resource leaks if some statements are prepared successfully before an error occurs. Consider deferring the closure of all successfully prepared statements upon an error to ensure that no database connections are leaked.

Comment on lines +13 to +28
func newTreeStorage(ss *spaceStorage, treeId string) (treestorage.TreeStorage, error) {
ts := &treeStorage{
treeId: treeId,
spaceStorage: ss,
service: ss.service,
}
var headsPayload []byte
if err := ss.service.stmt.loadTreeHeads.QueryRow(treeId).Scan(&headsPayload); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, treestorage.ErrUnknownTreeId
}
return nil, err
}
ts.heads = treestorage.ParseHeads(headsPayload)
return ts, nil
}
Copy link

Choose a reason for hiding this comment

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

Ensure proper error handling and SQL injection prevention in newTreeStorage.

- if err := ss.service.stmt.loadTreeHeads.QueryRow(treeId).Scan(&headsPayload); err != nil {
+ if err := ss.service.stmt.loadTreeHeads.QueryRow(sql.EscapeString(treeId)).Scan(&headsPayload); err != nil {

Similar to the list storage, the treeId parameter is directly used in a SQL query in newTreeStorage. This could lead to SQL injection if not properly sanitized. Use parameterized queries or proper escaping to mitigate this risk.


Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
func newTreeStorage(ss *spaceStorage, treeId string) (treestorage.TreeStorage, error) {
ts := &treeStorage{
treeId: treeId,
spaceStorage: ss,
service: ss.service,
}
var headsPayload []byte
if err := ss.service.stmt.loadTreeHeads.QueryRow(treeId).Scan(&headsPayload); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, treestorage.ErrUnknownTreeId
}
return nil, err
}
ts.heads = treestorage.ParseHeads(headsPayload)
return ts, nil
}
func newTreeStorage(ss *spaceStorage, treeId string) (treestorage.TreeStorage, error) {
ts := &treeStorage{
treeId: treeId,
spaceStorage: ss,
service: ss.service,
}
var headsPayload []byte
if err := ss.service.stmt.loadTreeHeads.QueryRow(sql.EscapeString(treeId)).Scan(&headsPayload); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, treestorage.ErrUnknownTreeId
}
return nil, err
}
ts.heads = treestorage.ParseHeads(headsPayload)
return ts, nil
}

Comment on lines 30 to 81
func createTreeStorage(ss *spaceStorage, payload treestorage.TreeStorageCreatePayload) (ts treestorage.TreeStorage, err error) {
ts = &treeStorage{
treeId: payload.RootRawChange.Id,
spaceStorage: ss,
service: ss.service,
heads: []string{payload.RootRawChange.Id},
}

tx, err := ss.service.writeDb.Begin()
if err != nil {
return nil, err
}
if _, err := tx.Stmt(ss.service.stmt.createTree).Exec(
payload.RootRawChange.Id,
ss.spaceId,
treestorage.CreateHeadsPayload(payload.Heads),
treeTypeTree,
); err != nil {
_ = tx.Rollback()
if isUniqueConstraint(err) {
return nil, treestorage.ErrTreeExists
}
return nil, err
}
if _, err = tx.Stmt(ss.service.stmt.createChange).Exec(
// root change (id, spaceId, treeId, data)
payload.RootRawChange.Id,
ss.spaceId,
payload.RootRawChange.Id,
payload.RootRawChange.RawChange,
); err != nil {
_ = tx.Rollback()
return nil, err
}

for _, change := range payload.Changes {
_, chErr := tx.Stmt(ss.service.stmt.createChange).Exec(change.Id, ss.spaceId, ts.Id(), change.RawChange)
if chErr != nil {
if isUniqueConstraint(chErr) {
continue
} else {
_ = tx.Rollback()
return nil, chErr
}
}
}

if err = tx.Commit(); err != nil {
return nil, err
}
return ts, nil
}
Copy link

Choose a reason for hiding this comment

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

Review transaction handling in createTreeStorage.

The function createTreeStorage uses transactions to ensure atomicity of multiple database operations. However, the rollback mechanism is manually handled in multiple places. Consider using a deferred rollback check that only triggers if the commit hasn't been successful. This would simplify the code and reduce the possibility of errors in transaction management.

Comment on lines 74 to 107
func New() ClientStorage {
return &storageService{}
}

func (s *storageService) Init(a *app.App) (err error) {
s.dbPath = a.MustComponent("config").(configGetter).GetSpaceStorePath()
s.lockedSpaces = map[string]*lockSpace{}
return
}

func (s *storageService) Run(ctx context.Context) (err error) {
connectionUrlParams := make(url.Values)
connectionUrlParams.Add("_txlock", "immediate")
connectionUrlParams.Add("_journal_mode", "WAL")
connectionUrlParams.Add("_busy_timeout", "5000")
connectionUrlParams.Add("_synchronous", "NORMAL")
connectionUrlParams.Add("_cache_size", "10000000")
connectionUrlParams.Add("_foreign_keys", "true")
connectionUri := s.dbPath + "?" + connectionUrlParams.Encode()
if s.writeDb, err = sql.Open("sqlite3", connectionUri); err != nil {
return
}
s.writeDb.SetMaxOpenConns(1)
if _, err = s.writeDb.Exec(sqlCreateTables); err != nil {
return
}

if s.readDb, err = sql.Open("sqlite3", connectionUri); err != nil {
return
}
s.readDb.SetMaxOpenConns(10)

return initStmts(s)
}
Copy link

Choose a reason for hiding this comment

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

Review the initialization and running of the storage service.

The methods Init and Run of storageService set up the database connections and prepare SQL statements. It is crucial to ensure that resources are properly managed and errors are handled appropriately. Consider using a more robust error handling and resource management strategy, such as using defer to clean up resources in case of initialization failures.

Copy link

@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

Comment on lines 157 to 167
// check if sqlite db exists
if _, err2 := os.Stat(filepath.Join(repoPath, SpaceStoreSqlitePath)); err2 == nil {
// already have sqlite db
c.SpaceStorageMode = storage.SpaceStorageModeSqlite
} else if _, err2 = os.Stat(filepath.Join(repoPath, SpaceStoreBadgerPath)); err2 == nil {
// old account repos
c.SpaceStorageMode = storage.SpaceStorageModeBadger
} else {
// new account repos
c.SpaceStorageMode = storage.SpaceStorageModeSqlite
}
Copy link

@coderabbitai coderabbitai bot Apr 15, 2024

Choose a reason for hiding this comment

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

Ensure proper error handling when checking file existence.

Currently, the existence checks for the SQLite and Badger database files do not handle possible errors other than the file not being found (e.g., permission issues). Consider refining the error handling to cover these cases to avoid potential runtime issues.

- if _, err2 := os.Stat(filepath.Join(repoPath, SpaceStoreSqlitePath)); err2 == nil {
+ if _, err2 := os.Stat(filepath.Join(repoPath, SpaceStoreSqlitePath)); err2 != nil {
+   if !os.IsNotExist(err2) {
+     return fmt.Errorf("failed to check SQLite DB file: %w", err2)
+   }
+ } else {
    c.SpaceStorageMode = storage.SpaceStorageModeSqlite
+ }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
// check if sqlite db exists
if _, err2 := os.Stat(filepath.Join(repoPath, SpaceStoreSqlitePath)); err2 == nil {
// already have sqlite db
c.SpaceStorageMode = storage.SpaceStorageModeSqlite
} else if _, err2 = os.Stat(filepath.Join(repoPath, SpaceStoreBadgerPath)); err2 == nil {
// old account repos
c.SpaceStorageMode = storage.SpaceStorageModeBadger
} else {
// new account repos
c.SpaceStorageMode = storage.SpaceStorageModeSqlite
}
// check if sqlite db exists
if _, err2 := os.Stat(filepath.Join(repoPath, SpaceStoreSqlitePath)); err2 != nil {
if !os.IsNotExist(err2) {
return fmt.Errorf("failed to check SQLite DB file: %w", err2)
}
} else {
c.SpaceStorageMode = storage.SpaceStorageModeSqlite
}
if _, err2 = os.Stat(filepath.Join(repoPath, SpaceStoreBadgerPath)); err2 == nil {
// old account repos
c.SpaceStorageMode = storage.SpaceStorageModeBadger
} else {
// new account repos
c.SpaceStorageMode = storage.SpaceStorageModeSqlite
}

Copy link
Contributor

Choose a reason for hiding this comment

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

we need this if statements only in case db exists. So don't need to check other errors

Copy link

Choose a reason for hiding this comment

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

@requilence, I understand your point about focusing on the existence checks for the database files and not handling other errors in this specific context. This approach can indeed simplify the code if we are confident that other error types (e.g., permission issues) are unlikely or managed elsewhere in the application.

Just a reminder, while this approach is streamlined, it's important to be cautious about scenarios where other errors might occur and ensure they won't negatively impact the application's stability or data integrity. If you're confident these cases are adequately handled or are very unlikely due to the environment setup, then your approach is sound.

If there's anything else you'd like to discuss or need further assistance with, feel free to let me know!

@@ -104,7 +113,7 @@ func (s *clientServer) prepareListener(port int) (net.Listener, error) {
}
}
// otherwise listening to new port
//nolint: gosec
// nolint: gosec
Copy link

Choose a reason for hiding this comment

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

Consider specifying a more restrictive IP address instead of 0.0.0.0 to reduce the risk of exposing the server publicly on all interfaces.

- return net.Listen("tcp", ":")
+ return net.Listen("tcp", "127.0.0.1:")

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
// nolint: gosec
// nolint: gosec

s.stmt.deleteTreesBySpace,
} {
if _, err = tx.Stmt(stmt).Exec(spaceId); err != nil {
_ = tx.Rollback()
Copy link

Choose a reason for hiding this comment

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

Check rollback error handling in deleteSpace.

The error from tx.Rollback() is not checked, which could lead to unnoticed failures in rolling back the transaction.

- _ = tx.Rollback()
+ if err := tx.Rollback(); err != nil {
+     return fmt.Errorf("failed to rollback: %w", err)
+ }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
_ = tx.Rollback()
if err := tx.Rollback(); err != nil {
return fmt.Errorf("failed to rollback: %w", err)
}

Comment on lines +119 to +122
r.spaceStorageMode = cfgGetter.GetSpaceStorageMode()
} else {
return fmt.Errorf("ds config is missing")
return fmt.Errorf("" +
"ds config is missing")
Copy link

Choose a reason for hiding this comment

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

Handling of missing datastore configuration.

The error handling for missing datastore configuration could be improved by providing more specific error messages or suggestions for resolution.

- return fmt.Errorf("" +
-     "ds config is missing")
+ return fmt.Errorf("datastore configuration is missing, please ensure it is correctly provided")

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
r.spaceStorageMode = cfgGetter.GetSpaceStorageMode()
} else {
return fmt.Errorf("ds config is missing")
return fmt.Errorf("" +
"ds config is missing")
r.spaceStorageMode = cfgGetter.GetSpaceStorageMode()
} else {
return fmt.Errorf("datastore configuration is missing, please ensure it is correctly provided")

if mode == SpaceStorageModeBadger {
// for already existing account repos
b := badgerstorage.New()
fmt.Println(b.Name())
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we shouldn't use fmt.Println

if chErr != nil {
if isUniqueConstraint(chErr) {
continue
} else {
Copy link
Contributor

Choose a reason for hiding this comment

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

I suppose else is redundant

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

CREATE TABLE IF NOT EXISTS trees (
id text not null primary key,
spaceId text not null,
type int not null default 0,
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe it should be some kind of enum or constraints?

Copy link
Member Author

Choose a reason for hiding this comment

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

Sqlite doesn't have enums. Constraint I didn't add because we don't have some migration mechanism and will not be easy to change this constraint in the future.

aclId text not null,
hash text not null default '',
oldHash text not null default '',
isCreated boolean not null default false,
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please explain why we need this field? I don't understand, because from my point of view, if space exist in this table and field IsDeleted=false, then it's considered as existing 😅

Copy link
Member Author

Choose a reason for hiding this comment

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

I don't know, I've implemented the existing interface :) As @mcrakhman said, it's hard to explain :)

&ss.oldHash,
&ss.isDeleted,
)
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could be 1 if statement
if err != nil && errors.Is(err, sql.ErrNoRows)

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks! There was another bug, I ignored an err in case it was not sql.NoRows


func isUniqueConstraint(err error) bool {
var sqliteErr sqlite3.Error
if errors.As(err, &sqliteErr) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Could be 1 if statement

Copy link
Member Author

Choose a reason for hiding this comment

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

fixed

Copy link

@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

Comment on lines 67 to 71
func (s *storageService) Init(a *app.App) (err error) {
s.dbPath = a.MustComponent("config").(configGetter).GetSpaceStorePath()
s.lockedSpaces = map[string]*lockSpace{}
return
}
Copy link

Choose a reason for hiding this comment

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

Ensure proper error handling during initialization.

Consider adding error handling for the initialization process to ensure that any issues with setting the database path or initializing the locked spaces map are properly managed. This could involve adding error checks and potentially logging or handling the errors appropriately.

Comment on lines +76 to +88
if _, err = tx.Stmt(s.stmt.createSpace).Exec(
// space(id, header, settingsId, aclId)
payload.SpaceHeaderWithId.Id,
payload.SpaceHeaderWithId.RawHeader,
payload.SpaceSettingsWithId.Id,
payload.AclWithId.Id,
); err != nil {
_ = tx.Rollback()
if isUniqueConstraint(err) {
return nil, spacestorage.ErrSpaceStorageExists
}
return nil, err
}
Copy link

Choose a reason for hiding this comment

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

Optimize error handling in createSpaceStorage.

Consolidate the repeated tx.Rollback() calls into a deferred function to ensure that rollback is attempted in all error scenarios without repeating the code. This will make the code cleaner and less error-prone.

- if _, err = tx.Stmt(s.stmt.createSpace).Exec(
+ defer func() {
+   if err != nil {
+     _ = tx.Rollback()
+   }
+ }()
+ if _, err = tx.Stmt(s.stmt.createSpace).Exec(

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
if _, err = tx.Stmt(s.stmt.createSpace).Exec(
// space(id, header, settingsId, aclId)
payload.SpaceHeaderWithId.Id,
payload.SpaceHeaderWithId.RawHeader,
payload.SpaceSettingsWithId.Id,
payload.AclWithId.Id,
); err != nil {
_ = tx.Rollback()
if isUniqueConstraint(err) {
return nil, spacestorage.ErrSpaceStorageExists
}
return nil, err
}
defer func() {
if err != nil {
_ = tx.Rollback()
}
}()
if _, err = tx.Stmt(s.stmt.createSpace).Exec(
// space(id, header, settingsId, aclId)
payload.SpaceHeaderWithId.Id,
payload.SpaceHeaderWithId.RawHeader,
payload.SpaceSettingsWithId.Id,
payload.AclWithId.Id,
); err != nil {
if isUniqueConstraint(err) {
return nil, spacestorage.ErrSpaceStorageExists
}
return nil, err
}

Comment on lines +90 to +99
if _, err = tx.Stmt(s.stmt.createTree).Exec(
// settings tree (id, spaceId, heads, type)
payload.SpaceSettingsWithId.Id,
payload.SpaceHeaderWithId.Id,
treestorage.CreateHeadsPayload([]string{payload.SpaceSettingsWithId.Id}),
treeTypeSettings,
); err != nil {
_ = tx.Rollback()
return nil, err
}
Copy link

Choose a reason for hiding this comment

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

Ensure rollback is checked for errors in createSpaceStorage.

The error return value of tx.Rollback is not checked after a failed execution of createTree. This could lead to unnoticed failures in rolling back the transaction.

- _ = tx.Rollback()
+ if err := tx.Rollback(); err != nil {
+     return fmt.Errorf("failed to rollback: %w", err)
+ }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
if _, err = tx.Stmt(s.stmt.createTree).Exec(
// settings tree (id, spaceId, heads, type)
payload.SpaceSettingsWithId.Id,
payload.SpaceHeaderWithId.Id,
treestorage.CreateHeadsPayload([]string{payload.SpaceSettingsWithId.Id}),
treeTypeSettings,
); err != nil {
_ = tx.Rollback()
return nil, err
}
if _, err = tx.Stmt(s.stmt.createTree).Exec(
// settings tree (id, spaceId, heads, type)
payload.SpaceSettingsWithId.Id,
payload.SpaceHeaderWithId.Id,
treestorage.CreateHeadsPayload([]string{payload.SpaceSettingsWithId.Id}),
treeTypeSettings,
); err != nil {
if err := tx.Rollback(); err != nil {
return fmt.Errorf("failed to rollback: %w", err)
}
return nil, err
}

Copy link

@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: 4

Comment on lines +82 to +91
func (s *storageService) Init(a *app.App) (err error) {
s.dbPath = a.MustComponent("config").(configGetter).GetSpaceStorePath()
s.lockedSpaces = map[string]*lockSpace{}
if s.checkpointAfterWrite == 0 {
s.checkpointAfterWrite = time.Second
}
if s.checkpointForce == 0 {
s.checkpointForce = time.Minute
}
return
Copy link

@coderabbitai coderabbitai bot Apr 17, 2024

Choose a reason for hiding this comment

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

Initialization method lacks robust error handling.

Consider enhancing the error handling in the Init method to manage potential issues during the initialization phase, such as problems with setting the database path or initializing the locked spaces map. This could involve adding error checks and potentially logging or handling the errors appropriately.

Copy link
Member Author

Choose a reason for hiding this comment

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

it's okay because in case of err on Init or Run Close will be called

Copy link

Choose a reason for hiding this comment

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

@cheggaaa, thank you for clarifying the error handling strategy involving the Close method. It's good to know that there's a mechanism in place to manage any issues that might arise during the initialization or runtime phases.

Just to ensure robustness, it might be beneficial to double-check that the Close method comprehensively addresses all potential error conditions and properly cleans up resources. This is crucial especially in scenarios where initialization fails partway, to prevent any resource leaks or inconsistent states.

If you need further assistance in reviewing the Close method or any other part of the implementation, feel free to ask!

Comment on lines 94 to 133
func (s *storageService) Run(ctx context.Context) (err error) {
driverName := bson.NewObjectId().Hex()
sql.Register(driverName,
&sqlite3.SQLiteDriver{
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
conn.RegisterUpdateHook(func(op int, db string, table string, rowid int64) {
s.lastWrite.Store(time.Now())
})
return nil
},
})

connectionUrlParams := make(url.Values)
connectionUrlParams.Add("_txlock", "immediate")
connectionUrlParams.Add("_journal_mode", "WAL")
connectionUrlParams.Add("_busy_timeout", "5000")
connectionUrlParams.Add("_synchronous", "NORMAL")
connectionUrlParams.Add("_cache_size", "10000000")
connectionUrlParams.Add("_foreign_keys", "true")
connectionUri := s.dbPath + "?" + connectionUrlParams.Encode()
if s.writeDb, err = sql.Open(driverName, connectionUri); err != nil {
return
}
s.writeDb.SetMaxOpenConns(1)
if _, err = s.writeDb.Exec(sqlCreateTables); err != nil {
return
}

if s.readDb, err = sql.Open(driverName, connectionUri); err != nil {
return
}
s.readDb.SetMaxOpenConns(10)

if err = initStmts(s); err != nil {
return
}
s.ctx, s.ctxCancel = context.WithCancel(context.Background())
go s.checkpointLoop()
return
}
Copy link

Choose a reason for hiding this comment

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

Tip

Codebase Verification

The review of the Run method in the storageService class reveals that while database connections are established and checked for errors, there is a potential issue with resource management. Specifically, the method does not use defer to ensure that resources are cleaned up in case of initialization failures. This could lead to resource leaks if an error occurs after some resources have been allocated but before the method completes successfully.

The database connections (writeDb and readDb) are established using sql.Open and are immediately checked for errors, which aligns with the review comment's requirement to verify that the connections are correctly established. However, the preparation of SQL statements and their error handling could not be verified from the provided script output and would require further investigation or access to additional parts of the code.

Given these observations, it is recommended to introduce defer statements for proper resource cleanup and to further verify the preparation of SQL statements.

Analysis chain

Review the setup and execution of the storage service.

Ensure that resources are properly managed and errors are handled appropriately in the Run method. Consider using defer to clean up resources in case of initialization failures. Additionally, verify that the database connections are correctly established and the SQL statements are prepared without errors.

Scripts Executed

The following scripts were executed for the analysis:


Script:

#!/bin/bash
# Verify that the database connections are correctly established and the SQL statements are prepared without errors.
rg --type go "sql.Open"

Length of output: 262

Copy link

@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: 4

Comment on lines +82 to +91
func (s *storageService) Init(a *app.App) (err error) {
s.dbPath = a.MustComponent("config").(configGetter).GetSpaceStorePath()
s.lockedSpaces = map[string]*lockSpace{}
if s.checkpointAfterWrite == 0 {
s.checkpointAfterWrite = time.Second
}
if s.checkpointForce == 0 {
s.checkpointForce = time.Minute
}
return
Copy link

Choose a reason for hiding this comment

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

Initialization lacks robust error handling for database path retrieval.

- s.dbPath = a.MustComponent("config").(configGetter).GetSpaceStorePath()
+ s.dbPath, ok = a.MustComponent("config").(configGetter).GetSpaceStorePath()
+ if !ok {
+     return fmt.Errorf("failed to get space store path")
+ }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
func (s *storageService) Init(a *app.App) (err error) {
s.dbPath = a.MustComponent("config").(configGetter).GetSpaceStorePath()
s.lockedSpaces = map[string]*lockSpace{}
if s.checkpointAfterWrite == 0 {
s.checkpointAfterWrite = time.Second
}
if s.checkpointForce == 0 {
s.checkpointForce = time.Minute
}
return
func (s *storageService) Init(a *app.App) (err error) {
s.dbPath, ok = a.MustComponent("config").(configGetter).GetSpaceStorePath()
if !ok {
return fmt.Errorf("failed to get space store path")
}
s.lockedSpaces = map[string]*lockSpace{}
if s.checkpointAfterWrite == 0 {
s.checkpointAfterWrite = time.Second
}
if s.checkpointForce == 0 {
s.checkpointForce = time.Minute
}
return

Comment on lines +94 to +137
func (s *storageService) Run(ctx context.Context) (err error) {
driverName := bson.NewObjectId().Hex()
sql.Register(driverName,
&sqlite3.SQLiteDriver{
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
conn.RegisterUpdateHook(func(op int, db string, table string, rowid int64) {
s.lastWrite.Store(time.Now())
})
return nil
},
})

connectionUrlParams := make(url.Values)
connectionUrlParams.Add("_txlock", "immediate")
connectionUrlParams.Add("_journal_mode", "WAL")
connectionUrlParams.Add("_busy_timeout", "5000")
connectionUrlParams.Add("_synchronous", "NORMAL")
connectionUrlParams.Add("_cache_size", "10000000")
connectionUrlParams.Add("_foreign_keys", "true")
connectionUri := s.dbPath + "?" + connectionUrlParams.Encode()
if s.writeDb, err = sql.Open(driverName, connectionUri); err != nil {
log.With(zap.String("db", "spacestore_sqlite"), zap.String("type", "write"), zap.Error(err)).Error("failed to open db")
return
}
s.writeDb.SetMaxOpenConns(1)
if _, err = s.writeDb.Exec(sqlCreateTables); err != nil {
log.With(zap.String("db", "spacestore_sqlite"), zap.String("type", "createtable"), zap.Error(err)).Error("failed to open db")
return
}

if s.readDb, err = sql.Open(driverName, connectionUri); err != nil {
log.With(zap.String("db", "spacestore_sqlite"), zap.String("type", "read"), zap.Error(err)).Error("failed to open db")
return
}
s.readDb.SetMaxOpenConns(10)

if err = initStmts(s); err != nil {
log.With(zap.String("db", "spacestore_sqlite"), zap.String("type", "init"), zap.Error(err)).Error("failed to open db")
return
}
s.ctx, s.ctxCancel = context.WithCancel(context.Background())
go s.checkpointLoop()
return
}
Copy link

Choose a reason for hiding this comment

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

Ensure proper resource cleanup in the Run method using defer.

Consider using defer statements to ensure that resources such as database connections are properly cleaned up in case of initialization failures. This is crucial to prevent resource leaks.

Comment on lines +174 to +175
_ = s.stmt.spaceIsCreated.QueryRow(id).Scan(&created)
return
Copy link

Choose a reason for hiding this comment

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

Error handling in IsSpaceCreated needs improvement.

- _ = s.stmt.spaceIsCreated.QueryRow(id).Scan(&created)
+ err = s.stmt.spaceIsCreated.QueryRow(id).Scan(&created)
+ if err != nil {
+     return false
+ }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
_ = s.stmt.spaceIsCreated.QueryRow(id).Scan(&created)
return
err = s.stmt.spaceIsCreated.QueryRow(id).Scan(&created)
if err != nil {
return false
}
return

Comment on lines +244 to +245
_ = tx.Rollback()
return err
Copy link

Choose a reason for hiding this comment

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

Check rollback error handling in deleteSpace.

- _ = tx.Rollback()
+ if err := tx.Rollback(); err != nil {
+     return fmt.Errorf("failed to rollback: %w", err)
+ }

Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
_ = tx.Rollback()
return err
if err := tx.Rollback(); err != nil {
return fmt.Errorf("failed to rollback: %w", err)
}
return err

@requilence requilence merged commit 2620736 into main Apr 18, 2024
@requilence requilence deleted the GO-3153-sqlite-spacestore branch April 18, 2024 12:35
@github-actions github-actions bot locked and limited conversation to collaborators Apr 18, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants