refactor: extract CQL into feature/cql (part of #778)#782
Conversation
Move the CQL (Cassandra interface) statement family out of core internal/mycli into internal/mycli/feature/cql behind the Feature registration seam, so core no longer imports github.com/gocql/gocql or github.com/googleapis/go-spanner-cassandra. This mirrors the BIGQUERY extraction (#781). - New package internal/mycli/feature/cql: CQLStatement, the fail-closed firstCQLKeyword/cqlStatementMutates classifier, formatCassandraTypeName, and Execute. The statement embeds mycli.MutationClassifier (Classify wired in newCQLStatement over its own CQL text) and carries its own compile-time ConditionallyMutatingStatement assertion. It is NOT detached-compatible (the Cassandra adapter binds to the Spanner database), so it does not mark MarksDetachedCompatible. - Feature() contributes only the "CQL" statement def (EARLY EXPERIMENTAL); no vars, no flags, fresh instances per call, no package-level state. - The lazy CQL session is reimplemented on mycli.FeatureState (key "cql.session") as a mutex-guarded, build-once io.Closer holder, preserving today's spancql.NewCluster/DatabaseUri/Timeout build and error messages. Session sheds its cqlCluster/cqlSession fields and the Close block; the core assertion for CQLStatement is removed. - feature/all.All() now returns {cql.Feature(), bigquery.Feature()}, keeping the merged order LLM, CQL, BIGQUERY so generated docs stay byte-identical. - Tests: classifier and dispatch/classification tests move into feature/cql; the dispatch-level READONLY guard for mutating CQL moves into an external package mycli_test file (cql_readonly_guard_test.go). core no longer imports gocql / go-spanner-cassandra (verified via go list -deps ./internal/mycli); the root main still pulls them through feature/all. Part of #778 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V28qKRVwUCkpT5pepTksgr
There was a problem hiding this comment.
Code Review
This pull request extracts the CQL (Cassandra Query Language) statement family and its Cassandra adapter integration from the core mycli package into a dedicated optional feature package under internal/mycli/feature/cql. This refactoring decouples the core package from Cassandra-specific dependencies such as gocql and go-spanner-cassandra. The PR registers the new CQL feature, migrates the statement classification and execution logic, and moves the corresponding unit and integration tests (including the READONLY guard tests) to the new package structure. There are no review comments provided, so I have no additional feedback to offer.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
Code Metrics Report📊 View detailed coverage report (available for 7 days)
Details | | main (09e4d81) | #782 (e579ec3) | +/- |
|---------------------|----------------|----------------|-------|
- | Coverage | 73.3% | 73.2% | -0.2% |
| Files | 91 | 93 | +2 |
| Lines | 7917 | 7933 | +16 |
+ | Covered | 5805 | 5808 | +3 |
+ | Code to Test Ratio | 1:1.3 | 1:1.3 | +0.0 |
| Code | 18473 | 18517 | +44 |
+ | Test | 25062 | 25128 | +66 |
+ | Test Execution Time | 1m38s | 1m31s | -7s |Code coverage of files in pull request scope (73.5% → 72.7%)
Reported by octocov |
|
Internal review results (orchestrator line review + independent adversarial review): both conclude SAFE TO MERGE. Verified under attack: classifier and formatCassandraTypeName byte-identical to the pre-move code; Execute identical modulo exported wrappers (including the empty-header Two honest notes for the record, neither blocking:
|
apstndb
left a comment
There was a problem hiding this comment.
APPROVE-WITH-NIT: I found no blocking behavior issues in the CQL extraction.
One non-blocking test coverage gap is worth tightening before or after merge:
- [P3]
internal/mycli/cql_readonly_guard_test.go:79: the dispatch-built SELECT allow-path test only asserts that the returned statement is not a staticMutationStatement. The wired classifier is asserted infeature/cqlthroughnewCQLStatement, and the productionHandleGroupscurrently does call that constructor, so the implementation is correct. Still, the dispatch test (orTestFeatureDefDispatch) should also assert that the built*cql.CQLStatementclassifiesSELECTas non-mutating. That would catch a futureHandleGroupsregression that accidentally returns a zero-valueCQLStatementand causes READONLY mode to fail-closed for CQL SELECT.
Validation I ran:
go test ./internal/mycli ./internal/mycli/feature/cql -run 'TestCQL|TestReadOnlyGuard.*CQL|TestFeatureDefDispatch|TestClientSideStatementDefsNonShadowing|TestClientSideStatementDefsCompletionConsistency|TestFeature'go list -deps ./internal/myclicontains nogocql/go-spanner-cassandraimportsgo list -deps .still contains the CQL dependencies through the full binary feature set
Review nit on #782: the SELECT allow-path test only proved the statement is not a static MutationStatement; the wired classifier was asserted only on constructor-built statements in feature/cql. Assert the classification on the DISPATCH-BUILT statement via a ClassifyForTest bridge, so a future HandleGroups regression returning a zero-value CQLStatement (nil Classify fails closed) would be caught as "SELECT wrongly classified as mutating" instead of silently blocking CQL SELECT in READONLY mode. Also asserts the dispatch-built DELETE classifies as mutating. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V28qKRVwUCkpT5pepTksgr
|
Addressed the APPROVE-WITH-NIT [P3] in 2bdf95d: |
Summary
PR2 of #778: extract the CQL (Cassandra interface) statement family out of the core
internal/myclipackage intointernal/mycli/feature/cql, behind the Feature registration seam. After this change core no longer importsgithub.com/gocql/gocqlorgithub.com/googleapis/go-spanner-cassandra. Follows the BIGQUERY extraction pattern (#781).Scope
internal/mycli/feature/cql: movesCQLStatement, the fail-closedfirstCQLKeyword/cqlStatementMutatesclassifier,formatCassandraTypeName, andExecute. Thezapcore.WarnLevel.String()usage moves with it (zap stays a core dep for gRPC-interceptor logging; only the gocql/spancql import lines leave core).CQLStatementembedsmycli.MutationClassifierwithClassifywired innewCQLStatementtocqlStatementMutatesover its own CQL text (mirrorsnewBigQueryStatement), carries its ownvar _ mycli.ConditionallyMutatingStatement = (*CQLStatement)(nil)assertion, and the core assertion is removed. It is deliberately notMarksDetachedCompatible(the Cassandra adapter binds to the Spanner database, so CQL can't run in detached mode).Feature(): contributes the "CQL" def only (EARLY EXPERIMENTAL note preserved), no vars, no flags, fresh instances per call, no package-level mutable state.cqlCluster/cqlSessionfields and thecqlSessionClose block. The lazy CQL session is reimplemented onmycli.FeatureState(keycql.session) as a mutex-guarded, build-onceio.Closerholder that closes the gocql session (the cluster is not separately closed, same as today), preserving today'sspancql.NewClusterbuild (DatabaseUri: session.DatabasePath(),Timeout = 5s) and error messages exactly. No rebuild-on-key-change rule (DatabasePath can't change on a live Session).feature/all.All()returns{cql.Feature(), bigquery.Feature()}; merged order stays LLM, CQL, BIGQUERY (LLM still in core at the end of the core table).TestCQLStatementMutates) and dispatch/classification tests move intofeature/cql; the dispatch-level READONLY guard for mutating CQL moves into an externalpackage mycli_testfile (cql_readonly_guard_test.go).Byte-identity proof
make docs-updatethengit diff --exit-code README.md docs/system_variables.md-> exit 0 (byte-identical). The CQL statement-help row keeps its position because the merged order (llm, cql, bigquery) matches the pre-extraction ordering already established by #780/#781.Dependency check
Root main still pulls them via
feature/all(correct):Validation
make check— pass (0 lint issues, formatting correct)make check-race— passgo vet ./...— cleanDeviation from the task spec (allowed-SELECT guard test)
The task asked the external guard test to mirror
bigquery_readonly_guard_test.go"exactly", including running an allowedCQL SELECTthroughSession.ExecuteStatementand expecting it to "fail later for a non-READONLY reason". That is not safe for CQL and is intentionally not done:Executehas a pre-network guard (effectiveProjectreturns""-> immediate "project not configured" error), so the allowed SELECT fails fast with no I/O.Executehas no such guard; a passing SELECT reachesspancql.NewCluster, which panics (NewClusterpanics whenNewTCPProxy/newAdapterClientfails) whenever the Cassandra adapter client cannot be built — i.e. always in unit tests, with or without ADC. Running it throughExecutewould crash the test binary, not return an error. This is the same reason the original in-coreTestReadOnlyGuardAllowsReadOnlyCQLasserted classification directly instead of callingExecute.Coverage is preserved without weakening the #757 guarantees:
cql_readonly_guard_test.goruns the blocked mutating cases through real dispatch +ExecuteStatement(short-circuit at the guard, no adapter built) and asserts the READONLY sentinel; the allowed SELECT is asserted at dispatch level to be a non-staticMutationStatement.feature/cql/cql_test.go(TestCQLStatementConditionalMutation) asserts the wired classifier directly (read-only SELECT -> non-mutating, DELETE -> mutating) via the exportedClassifyfield.feature/cql/classifier_test.go(TestCQLStatementMutates) keeps the exhaustive fail-closed keyword coverage.Part of #778
🤖 Generated with Claude Code
https://claude.ai/code/session_01V28qKRVwUCkpT5pepTksgr