-
Notifications
You must be signed in to change notification settings - Fork 2
Implement BEGIN [RW] ISOLATION LEVEL #174
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello @apstndb, 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!
Summary of Changes
This pull request implements the ISOLATION LEVEL clause for BEGIN and BEGIN RW statements in spanner-cli. It modifies the client-side statement definitions to include the isolation level option and updates the session management to handle the specified isolation level when starting transactions. The supported isolation levels are SERIALIZABLE and REPEATABLE READ.
Highlights
- New Feature: Implements the
ISOLATION LEVELclause forBEGINandBEGIN RWstatements. - Statement Parsing: Updates the regular expressions and handling logic for
BEGINandBEGIN RWstatements to parse theISOLATION LEVELclause. - Transaction Management: Modifies the session management to incorporate the specified isolation level when starting read-write transactions.
- Testing: Adds tests to verify the correct behavior of the isolation level feature.
Changelog
Click here to see the changelog
- README.md
- Adds documentation for setting session-level default isolation level and transaction-level isolation level using
SET DEFAULT_ISOLATION_LEVELandBEGIN ISOLATION LEVEL.
- Adds documentation for setting session-level default isolation level and transaction-level isolation level using
- client_side_statement_def.go
- Adds import for
cloud.google.com/go/spanner/apiv1/spannerpb. - Updates the syntax for
BEGIN RWto includeISOLATION LEVEL {SERIALIZABLE|REPEATABLE READ}. - Updates the regular expression for
BEGIN RWto match the new syntax. - Updates the
HandleSubmatchfunction forBEGIN RWto parse the isolation level. - Updates the syntax for
BEGINto includeISOLATION LEVEL {SERIALIZABLE|REPEATABLE READ}. - Updates the regular expression for
BEGINto match the new syntax. - Updates the
HandleSubmatchfunction forBEGINto parse the isolation level. - Adds the
parseIsolationLevelfunction to convert the isolation level string to the corresponding enum value.
- Adds import for
- session.go
- Adds
isolationLevelfield to thetransactionContextstruct. - Modifies the
BeginPendingTransactionfunction to accept an isolation level and store it in thetransactionContext. - Modifies the
BeginPendingTransactionfunction to use the session's default isolation level if none is specified in theBEGINstatement. - Modifies the
BeginReadWriteTransactionfunction to accept an isolation level. - Modifies the
BeginReadWriteTransactionfunction to use the session's default isolation level if none is specified in theBEGINstatement. - Passes the isolation level to
spanner.NewReadWriteStmtBasedTransactionWithOptions. - Stores the isolation level in the
transactionContext. - Modifies the
RunInNewOrExistRwTxfunction to pass a default isolation level of 0 toBeginReadWriteTransaction.
- Adds
- session_slow_test.go
- Modifies
TestRequestPriorityto pass a default isolation level of 0 toBeginReadWriteTransaction. - Adds
transactionIsolationLevelto the test struct. - Adds new test cases for different isolation levels.
- Modifies
TestIsolationLevelto pass the isolation level toBeginReadWriteTransaction.
- Modifies
- statement_processing_test.go
- Adds test cases for
BEGINandBEGIN RWstatements with different isolation levels and priorities. - Adds test cases for
BEGINandBEGIN RWstatements withSERIALIZABLEisolation level. - Adds test cases for
BEGINandBEGIN RWstatements withREPEATABLE READisolation level. - Adds test cases for
BEGINandBEGIN RWstatements withREPEATABLE READisolation level andPRIORITY.
- Adds test cases for
- statements_transaction.go
- Adds
IsolationLevelfield to theBeginRwStatementstruct. - Modifies the
BeginRwStatement.Executefunction to pass the isolation level tosession.BeginReadWriteTransaction. - Adds
IsolationLevelfield to theBeginStatementstruct. - Modifies the
BeginStatement.Executefunction to pass the isolation level tosession.BeginPendingTransaction. - Modifies the
BeginStatement.Executefunction to pass the isolation level of 0 tosession.BeginReadWriteTransaction.
- Adds
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 in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.
| 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 issue 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 is currently in preview and 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 to provide feedback.
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.
Trivia time!
What are the four standard isolation levels defined by the ANSI SQL standard?
Click here for the answer
The four standard isolation levels defined by the ANSI SQL standard are:
- Read Uncommitted
- Read Committed
- Repeatable Read
- Serializable
Footnotes
-
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. ↩
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This PR implements the ISOLATION LEVEL clause for BEGIN and BEGIN RW statements in spanner-mycli. The changes look good overall, but there are a few areas that could be improved for clarity and correctness.
Summary of Findings
- Regex submatch indexing: The indexing of submatches in the regular expressions for
BEGINandBEGIN RWstatements is fragile. If the order of optional clauses changes, the indexes will need to be manually updated, which is error-prone. Consider using named capture groups to improve maintainability. - Default isolation level for
SET TRANSACTION: TheSET TRANSACTIONstatement does not currently support setting the isolation level. It might be useful to extend this statement to allow users to explicitly set the isolation level for the current transaction. - Missing test case: There is no test case for setting the default isolation level to
REPEATABLE_READand then starting a transaction without specifying an isolation level. This scenario should be tested to ensure that the default isolation level is correctly applied.
Merge Readiness
The pull request is almost ready for merging. However, the issues related to regex submatch indexing and the missing test case should be addressed before merging. I am unable to directly approve this pull request, and recommend that others review and approve this code before merging. Given the medium severity of the issues found, I recommend that the pull request not be merged until those are addressed.
| }, | ||
| Pattern: regexp.MustCompile(`(?is)^BEGIN\s+RW(?:\s+TRANSACTION)?(?:\s+PRIORITY\s+(HIGH|MEDIUM|LOW))?$`), | ||
| Pattern: regexp.MustCompile(`(?is)^BEGIN\s+RW(?:\s+TRANSACTION)?(?:\s+ISOLATION\s+LEVEL\s+(SERIALIZABLE|REPEATABLE\s+READ))?(?:\s+PRIORITY\s+(HIGH|MEDIUM|LOW))?$`), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The regex (?is)^BEGIN\s+RW(?:\s+TRANSACTION)?(?:\s+ISOLATION\s+LEVEL\s+(SERIALIZABLE|REPEATABLE\s+READ))?(?:\s+PRIORITY\s+(HIGH|MEDIUM|LOW))?$ uses numeric indexing for submatches. If the order of the optional clauses changes, the indexes in HandleSubmatch will need to be manually updated. Consider using named capture groups to improve maintainability. For example, (?is)^BEGIN\s+RW(?:\s+TRANSACTION)?(?:\s+ISOLATION\s+LEVEL\s+(?P<isolation>SERIALIZABLE|REPEATABLE\s+READ))?(?:\s+PRIORITY\s+(?P<priority>HIGH|MEDIUM|LOW))?$
| HandleSubmatch: func(matched []string) (Statement, error) { | ||
| priority, err := parsePriority(matched[1]) | ||
| isolationLevel, err := parseIsolationLevel(matched[1]) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| return &BeginRwStatement{Priority: priority}, nil | ||
| priority, err := parsePriority(matched[2]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since you're using numeric indexing for the regex submatches, it's important to ensure that the indexes are correct. It looks like parsePriority is using matched[2] but it's not clear that the second submatch will always be the priority. Consider using named capture groups in the regex to avoid this issue.
| Pattern: regexp.MustCompile(`(?is)^BEGIN(?:\s+TRANSACTION)?(?:\s+ISOLATION\s+LEVEL\s+(SERIALIZABLE|REPEATABLE\s+READ))?(?:\s+PRIORITY\s+(HIGH|MEDIUM|LOW))?$`), | ||
| HandleSubmatch: func(matched []string) (Statement, error) { | ||
| priority, err := parsePriority(matched[1]) | ||
| isolationLevel, err := parseIsolationLevel(matched[1]) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
|
|
||
| priority, err := parsePriority(matched[2]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
session.go
Outdated
| if !s.InReadWriteTransaction() { | ||
| // Start implicit transaction. | ||
| if err := s.BeginReadWriteTransaction(ctx, s.currentPriority()); err != nil { | ||
| if err := s.BeginReadWriteTransaction(ctx, 0, s.currentPriority()); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The isolation level is hardcoded to 0 here. It would be better to use the session's default isolation level, or to pass the isolation level from the SetTransactionStatement if it was set explicitly.
| if err := s.BeginReadWriteTransaction(ctx, 0, s.currentPriority()); err != nil { | |
| if err := s.BeginReadWriteTransaction(ctx, s.systemVariables.DefaultIsolationLevel, s.currentPriority()); err != nil { |
statements_transaction.go
Outdated
| return result, nil | ||
| } else { | ||
| err := session.BeginReadWriteTransaction(ctx, session.tc.priority) | ||
| err := session.BeginReadWriteTransaction(ctx, 0, session.tc.priority) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The isolation level is hardcoded to 0 here. It would be better to use the session's default isolation level, or to pass the isolation level from the SetTransactionStatement if it was set explicitly.
| err := session.BeginReadWriteTransaction(ctx, 0, session.tc.priority) | |
| err := session.BeginReadWriteTransaction(ctx, session.tc.isolationLevel, session.tc.priority) |
| for _, test := range []struct { | ||
| desc string | ||
| defaultIsolationLevel sppb.TransactionOptions_IsolationLevel | ||
| want sppb.TransactionOptions_IsolationLevel | ||
| desc string | ||
| defaultIsolationLevel sppb.TransactionOptions_IsolationLevel | ||
| transactionIsolationLevel sppb.TransactionOptions_IsolationLevel | ||
| want sppb.TransactionOptions_IsolationLevel | ||
| }{ | ||
| { | ||
| desc: "use default isolation level", | ||
| defaultIsolationLevel: sppb.TransactionOptions_ISOLATION_LEVEL_UNSPECIFIED, | ||
| want: sppb.TransactionOptions_ISOLATION_LEVEL_UNSPECIFIED, | ||
| desc: "use default isolation level", | ||
| defaultIsolationLevel: sppb.TransactionOptions_ISOLATION_LEVEL_UNSPECIFIED, | ||
| transactionIsolationLevel: sppb.TransactionOptions_ISOLATION_LEVEL_UNSPECIFIED, | ||
| want: sppb.TransactionOptions_ISOLATION_LEVEL_UNSPECIFIED, | ||
| }, | ||
| { | ||
| desc: "use serializable isolation level", | ||
| defaultIsolationLevel: sppb.TransactionOptions_SERIALIZABLE, | ||
| want: sppb.TransactionOptions_SERIALIZABLE, | ||
| desc: "use default serializable isolation level", | ||
| defaultIsolationLevel: sppb.TransactionOptions_SERIALIZABLE, | ||
| transactionIsolationLevel: sppb.TransactionOptions_ISOLATION_LEVEL_UNSPECIFIED, | ||
| want: sppb.TransactionOptions_SERIALIZABLE, | ||
| }, | ||
| { | ||
| desc: "use repeatable_read isolation level", | ||
| defaultIsolationLevel: sppb.TransactionOptions_REPEATABLE_READ, | ||
| want: sppb.TransactionOptions_REPEATABLE_READ, | ||
| desc: "use default repeatable read isolation level", | ||
| defaultIsolationLevel: sppb.TransactionOptions_REPEATABLE_READ, | ||
| transactionIsolationLevel: sppb.TransactionOptions_ISOLATION_LEVEL_UNSPECIFIED, | ||
| want: sppb.TransactionOptions_REPEATABLE_READ, | ||
| }, | ||
| { | ||
| desc: "use override serializable isolation level", | ||
| defaultIsolationLevel: sppb.TransactionOptions_REPEATABLE_READ, | ||
| transactionIsolationLevel: sppb.TransactionOptions_SERIALIZABLE, | ||
| want: sppb.TransactionOptions_SERIALIZABLE, | ||
| }, | ||
| { | ||
| desc: "use override repeatable read isolation level", | ||
| defaultIsolationLevel: sppb.TransactionOptions_SERIALIZABLE, | ||
| transactionIsolationLevel: sppb.TransactionOptions_REPEATABLE_READ, | ||
| want: sppb.TransactionOptions_REPEATABLE_READ, | ||
| }, | ||
| } { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR implements
ISOLATION LEVELclause forBEGINandBEGIN RW.Note:
REPEATABLE READseems not yet to be enabled.Reference