fix: address high and medium priority code review issues#19
fix: address high and medium priority code review issues#19devin-ai-integration[bot] wants to merge 12 commits intomainfrom
Conversation
- Fix find_top_level_from underscore bug: correctly reject identifiers like FROM_TABLE - Fix rewrite_mysql_quotes: preserve backticks inside string literals - Add overflow check for BigUnsigned -> i64 conversion (proxy, executor, transaction) - Extract shared bind_value/convert_statement into src/bind.rs to eliminate ~500 lines of duplication - Add tracing::warn on no-op begin/commit/rollback to alert users - Replace filter_map with fallible map in bind_array to error on invalid/null elements Co-Authored-By: hyx <devgony@gmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
…mpatibility chrono::DateTime<Utc> does not implement ToKind for gcloud-spanner directly. Use SpannerTimestamp, SpannerOptionalTimestamp, SpannerNaiveDateTime, and SpannerOptionalNaiveDateTime wrapper types from chrono_support module. Co-Authored-By: hyx <devgony@gmail.com>
Co-Authored-By: hyx <devgony@gmail.com>
Co-Authored-By: hyx <devgony@gmail.com>
NaiveDateTime and DateTime<Utc> are Copy types, so **v works to copy from &Box<T> without method resolution auto-deref ambiguity. Co-Authored-By: hyx <devgony@gmail.com>
Reverts to the same approach used in the original executor.rs/transaction.rs code that was passing CI. DateTime<Utc> implements ToKind natively, so no wrapper types are needed for the executor/transaction path. Co-Authored-By: hyx <devgony@gmail.com>
Uses SpannerTimestamp/SpannerOptionalTimestamp wrappers (needed for ToKind) with auto-deref method calls (and_utc/to_utc) to extract owned values from Box references without direct deref operators. Co-Authored-By: hyx <devgony@gmail.com>
Rust 1.94 requires explicit type annotation for String::as_ref() since String implements AsRef for multiple types (str, [u8], OsStr, Path). Co-Authored-By: hyx <devgony@gmail.com>
Co-Authored-By: hyx <devgony@gmail.com>
Co-Authored-By: hyx <devgony@gmail.com>
…-spanner API - Add mod declarations for connection, executor, query_result, transaction modules that existed as files but were never compiled, causing bind.rs functions to appear as dead code - Update read_write_transaction closure signature (1-arg, no cancel token) to match current gcloud-spanner 1.8.0 API - Fix connection.rs close() to handle Arc<Client> ownership correctly - Remove obsolete gcloud_gax::cancel::CancellationToken import - Run make fmt for consistent formatting Co-Authored-By: hyx <devgony@gmail.com>
There was a problem hiding this comment.
Pull request overview
This PR refines the SeaORM ↔ Google Cloud Spanner adapter by fixing SQL parsing/binding bugs, improving type-safety for parameter conversion, deduplicating binding logic, and updating code to the current gcloud-spanner transaction API.
Changes:
- Fixes SQL parsing/rewriting edge cases (e.g.,
FROM_TABLEdetection and backticks inside string literals). - Adds overflow-safe
u64→i64conversion forBigUnsigned(including inside arrays) and makesbind_arrayfail fast on invalid elements. - Deduplicates executor/transaction binding into a shared
src/bind.rsmodule and registers previously-orphaned modules inlib.rs.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/bind.rs | New shared statement conversion + value binding logic (dedup + overflow checks). |
| src/connection.rs | Updates close semantics and adapts read_write_transaction callback signature to newer gcloud-spanner. |
| src/executor.rs | Switches to shared binder and updates transaction closure signature. |
| src/transaction.rs | Switches to shared binder and removes duplicated bind logic. |
| src/proxy.rs | Makes MySQL backtick rewrite string-literal-aware; improves array binding strictness; fixes FROM detection; adds begin/commit/rollback warnings. |
| src/lib.rs | Declares modules (incl. new bind) and publicly exports connection/executor/query_result/transaction modules. |
| src/query_result.rs | Import formatting-only change. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pub async fn close(self) { | ||
| Arc::try_unwrap(self.client) | ||
| .ok() | ||
| .expect("Cannot close: other references to Client exist") | ||
| .close() | ||
| .await; |
There was a problem hiding this comment.
SpannerConnection::close(self) will panic at runtime if the Arc<Client> has been cloned anywhere else (Arc::try_unwrap(...).expect(...)). Since SpannerConnection is Clone and now publicly exported, this is easy to trigger unintentionally and would crash consumer applications. Consider returning a Result<(), DbErr> (or logging and returning) when try_unwrap fails instead of panicking, or provide a separate infallible close_if_unique(self) helper if you want to preserve strict behavior.
| pub async fn close(self) { | |
| Arc::try_unwrap(self.client) | |
| .ok() | |
| .expect("Cannot close: other references to Client exist") | |
| .close() | |
| .await; | |
| pub async fn close(self) -> Result<(), DbErr> { | |
| match Arc::try_unwrap(self.client) { | |
| Ok(client) => { | |
| client.close().await; | |
| Ok(()) | |
| } | |
| Err(_) => Err(DbErr::Custom( | |
| "Cannot close: other references to Client exist".to_owned(), | |
| )), | |
| } |
| async fn begin(&self) { | ||
| // Spanner uses callback-based transactions, handled differently | ||
| tracing::warn!( | ||
| "SpannerProxy::begin() is a no-op. Spanner uses callback-based transactions via \ | ||
| SpannerConnection::read_write_transaction() instead of begin/commit/rollback." | ||
| ); | ||
| } |
There was a problem hiding this comment.
begin() logs at warn! level despite being called through ProxyDatabaseTrait, which can be part of normal control flow if callers use SeaORM’s transaction APIs. This can create very noisy logs in production. Consider lowering to debug!/info!, emitting the warning only once (e.g., via a OnceLock), or gating it behind a feature flag so users can opt in to the observability.
fix: address high and medium priority code review issues
Summary
Fixes 6 issues identified during code review, covering bug fixes, safety improvements, and code deduplication:
Bug fix:
find_top_level_fromunderscore handling — The condition|| bytes[next_idx] == b'_'incorrectly matchedFROM_TABLEas a SQLFROMkeyword. Fixed to&& bytes[next_idx] != b'_'so underscored identifiers are no longer misrecognized.Bug fix:
rewrite_mysql_quotesstring literal awareness — The oldsql.replace('', "")stripped backticks inside string literals, corrupting data values. Replaced with a state-machine parser (same pattern asrewrite_placeholders`) that only removes backticks outside string literals.Safety:
BigUnsigned→i64overflow check — Previously*v as i64silently overflowed foru64values >i64::MAX. Now usesi64::try_from()and returnsSpannerDbErr::TypeConversion. Applied inproxy.rs,executor.rs,transaction.rs, and the new sharedbind.rs.Deduplication: shared
bind.rsmodule — Extracted identicalbind_value/convert_statementlogic fromexecutor.rsandtransaction.rs(3 copies totaling ~500 lines) into a singlesrc/bind.rsmodule. Net -123 lines. Note:proxy.rsretains its ownbind_valuebecause it uses different Spanner wrapper types (e.g.,SpannerNaiveDateTime,SpannerUuid).Observability:
begin/commit/rollbackwarnings — These are no-ops since Spanner uses callback-based transactions. Addedtracing::warn!messages explaining the correct transaction API to use, since the upstream trait signature (async fn begin(&self)) doesn't allow returning errors.Strictness:
bind_arrayerror handling — Replacedfilter_map(which silently droppedNoneand type-mismatched elements) with falliblemapthat returnsSpannerDbErr::TypeConversionwith the element index. Also addedBigUnsignedoverflow checking inside arrays.Updates since last revision
maininto the branch (v0.1.0 release changes)lib.rs:connection,executor,query_result, andtransactionexisted as source files but were never declared as modules, which causedbind.rsfunctions to appear as dead code (the original CI lint failure). All four are now declared aspub mod.gcloud-spannerAPI: The orphaned modules used a stale API. Key fixes:read_write_transactionclosure now takes 1 argument (noCancellationToken), matching currentgcloud-spanner1.8.0 APIconnection.rs::close()changed from&selftoselfto handleArc<Client>ownership viaArc::try_unwrapgcloud_gax::cancel::CancellationTokenimportmake fmtapplied across all changed filescargo clippy --all-features -- -D warningsnow passes cleanlyReview & Testing Checklist for Human
Verify
close(self)panic safety:SpannerConnection::close()now usesArc::try_unwrap(...).expect(...)which panics at runtime if otherArc<Client>references exist. Verify all call sites ensure the connection is the sole owner before callingclose(), or consider changing to a graceful error return.Verify
bind_arraybehavior change is acceptable: The switch from silently dropping invalid/null array elements to returning errors is a breaking behavioral change. If any existing callers rely on the old filter-and-drop behavior (e.g., passing arrays withNoneelements), they will now get errors. Check existing usage patterns.Review
find_top_level_fromlogic carefully: The condition was inverted from|| bytes[next_idx] == b'_'to&& bytes[next_idx] != b'_'. Verify the original bug claim is correct (thatFROM_TABLEwas incorrectly matching as aFROMkeyword).Test
rewrite_mysql_quoteswith edge cases: The new state machine should preserve backticks inside string literals (e.g.,SELECT 'foo`bar' FROM `table`). Test with escaped quotes ('',"") and mixed quote types.Verify public API surface of newly-exported modules:
connection,executor,query_result, andtransactionare nowpub mod. Confirm that exposingSpannerConnection,SpannerExecutor,SpannerReadWriteTransaction,SpannerReadOnlyTransaction, andSpannerQueryResultas public types is intentional and the API is stable enough.Test Plan
cargo clippy --all-features -- -D warnings(already passing in CI)cargo test)find_top_level_from("SELECT * FROM_TABLE")should NOT matchrewrite_mysql_quotes("SELECT 'foo`bar' FROM `table`")should preserve the backtick inside the stringbind_valuewithBigUnsigned(u64::MAX)should return an errorbind_arraywith[Some(1), None, Some(2)]should return an error (not[1, 2])SpannerConnection::close()when other Arc references exist (should it panic or error?)Notes
bind.rsmodule usesSpannerTimestamp/SpannerOptionalTimestampwrapper types for chrono conversion, while the old inline code used barechrono::DateTime<Utc>. This is a pre-existing architectural difference between the proxy path and the executor/transaction path.Link to Devin session: https://app.devin.ai/sessions/0efd4aabb4604cf18b3663ff8daefd67
Requested by: @devgony