Skip to content

Commit f856ae1

Browse files
authored
Integration tests for unquoting keyspace. Return original error of USE keyspace result message. Enhance logging of forwarded and not-forwarded requests
patch by Bret McGuire and Lukasz Antoniak
1 parent 0dea6ff commit f856ae1

3 files changed

Lines changed: 63 additions & 14 deletions

File tree

parser/lexer_test.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,15 @@ func TestLexerIdentifiers(t *testing.T) {
8686
{`"system"`, tkIdentifier, "system"},
8787
{`"system"`, tkIdentifier, "system"},
8888
{`"System"`, tkIdentifier, "System"},
89-
{`""""`, tkIdentifier, "\""},
90-
{`""""""`, tkIdentifier, "\"\""},
91-
{`"A"""""`, tkIdentifier, "A\"\""},
92-
{`"""A"""`, tkIdentifier, "\"A\""},
93-
{`"""""A"`, tkIdentifier, "\"\"A"},
89+
// below test verify correct escaping double quote character as per CQL definition:
90+
// identifier ::= unquoted_identifier | quoted_identifier
91+
// unquoted_identifier ::= re('[a-zA-Z][link:[a-zA-Z0-9]]*')
92+
// quoted_identifier ::= '"' (any character where " can appear if doubled)+ '"'
93+
{`""""`, tkIdentifier, "\""}, // outermost quotes indicate quoted string, inner two double quotes shall be treated as single quote
94+
{`""""""`, tkIdentifier, "\"\""}, // same as above, but 4 inner quotes result in 2 quotes
95+
{`"A"""""`, tkIdentifier, "A\"\""}, // outermost quotes indicate quoted string, 4 quotes after A result in 2 quotes
96+
{`"""A"""`, tkIdentifier, "\"A\""}, // outermost quotes indicate quoted string, 2 quotes before and after A result in single quotes
97+
{`"""""A"`, tkIdentifier, "\"\"A"}, // analogical to previous tests
9498
{`";`, tkInvalid, ""},
9599
{`"""`, tkIdentifier, ""},
96100
}

proxy/proxy.go

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -667,18 +667,17 @@ func (c *client) handleExecute(raw *frame.RawFrame, msg *partialExecute, customP
667667
}
668668

669669
func (c *client) handleQuery(raw *frame.RawFrame, msg *partialQuery, customPayload map[string][]byte) {
670-
c.proxy.logger.Debug("handling query", zap.String("query", msg.query), zap.Int16("stream", raw.Header.StreamId))
671-
672670
handled, stmt, err := parser.IsQueryHandled(parser.IdentifierFromString(c.keyspace), msg.query)
673-
674671
if handled {
672+
c.proxy.logger.Debug("Query handled by proxy", zap.String("query", msg.query), zap.Int16("stream", raw.Header.StreamId))
675673
if err != nil {
676674
c.proxy.logger.Error("error parsing query to see if it's handled", zap.Error(err))
677675
c.send(raw.Header, &message.Invalid{ErrorMessage: err.Error()})
678676
} else {
679677
c.interceptSystemQuery(raw.Header, stmt)
680678
}
681679
} else {
680+
c.proxy.logger.Debug("Query not handled by proxy, forwarding", zap.String("query", msg.query), zap.Int16("stream", raw.Header.StreamId))
682681
c.execute(raw, c.getDefaultIdempotency(customPayload), c.keyspace, msg)
683682
}
684683
}
@@ -813,9 +812,20 @@ func (c *client) interceptSystemQuery(hdr *frame.Header, stmt interface{}) {
813812
}
814813
case *parser.UseStatement:
815814
if _, err := c.proxy.maybeCreateSession(hdr.Version, s.Keyspace); err != nil {
816-
c.send(hdr, &message.ServerError{ErrorMessage: "Proxy unable to create new session for keyspace"})
815+
errMsg := "Proxy unable to create new session for keyspace"
816+
var cqlError *proxycore.CqlError
817+
if errors.As(err, &cqlError) {
818+
// copy detailed error reason from downstream message
819+
errMsg = cqlError.Message.GetErrorMessage()
820+
}
821+
c.send(hdr, &message.ServerError{ErrorMessage: errMsg})
817822
} else {
818823
c.keyspace = s.Keyspace
824+
// We might have received a quoted keyspace name in the UseStatement so remove any
825+
// quotes before sending back this result message. This keeps us consistent with
826+
// how Cassandra implements the same functionality and avoids any issues with
827+
// drivers sending follow-on "USE" requests after wrapping the keyspace name in
828+
// quotes.
819829
ks := parser.IdentifierFromString(s.Keyspace)
820830
c.send(hdr, &message.SetKeyspaceResult{Keyspace: ks.ID()})
821831
}

proxy/proxy_test.go

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,48 @@ func TestProxy_UseKeyspace(t *testing.T) {
206206

207207
cl := connectTestClient(t, ctx, proxyContactPoint)
208208

209-
resp, err := cl.SendAndReceive(ctx, frame.NewFrame(primitive.ProtocolVersion4, 0, &message.Query{Query: "USE system"}))
209+
testKeyspaces := []string{"system", "\"system\""}
210+
for _, testKeyspace := range testKeyspaces {
211+
212+
resp, err := cl.SendAndReceive(ctx, frame.NewFrame(primitive.ProtocolVersion4, 0, &message.Query{Query: "USE " + testKeyspace}))
213+
require.NoError(t, err)
214+
215+
assert.Equal(t, primitive.OpCodeResult, resp.Header.OpCode)
216+
res, ok := resp.Body.Message.(*message.SetKeyspaceResult)
217+
require.True(t, ok, "expected set keyspace result")
218+
assert.Equal(t, "system", res.Keyspace)
219+
}
220+
}
221+
222+
func TestProxy_UseKeyspace_Error(t *testing.T) {
223+
ctx, cancel := context.WithCancel(context.Background())
224+
tester, proxyContactPoint, err := setupProxyTest(ctx, 1, proxycore.MockRequestHandlers{
225+
primitive.OpCodeQuery: func(cl *proxycore.MockClient, frm *frame.Frame) message.Message {
226+
qry := frm.Body.Message.(*message.Query)
227+
if qry.Query == "USE non_existing" {
228+
return &message.ServerError{
229+
ErrorMessage: "Keyspace 'non_existing' does not exist",
230+
}
231+
}
232+
return cl.InterceptQuery(frm.Header, frm.Body.Message.(*message.Query))
233+
}})
234+
defer func() {
235+
cancel()
236+
tester.shutdown()
237+
}()
210238
require.NoError(t, err)
211239

212-
assert.Equal(t, primitive.OpCodeResult, resp.Header.OpCode)
213-
res, ok := resp.Body.Message.(*message.SetKeyspaceResult)
214-
require.True(t, ok, "expected set keyspace result")
215-
assert.Equal(t, "system", res.Keyspace)
240+
cl := connectTestClient(t, ctx, proxyContactPoint)
241+
242+
resp, err := cl.SendAndReceive(ctx, frame.NewFrame(primitive.ProtocolVersion4, 0, &message.Query{Query: "USE non_existing"}))
243+
require.NoError(t, err)
244+
245+
assert.Equal(t, primitive.OpCodeError, resp.Header.OpCode)
246+
res, ok := resp.Body.Message.(*message.ServerError)
247+
require.True(t, ok)
248+
// make sure that CQL Proxy returns the same error of 'USE keyspace' command
249+
// as backend C* cluster has and does not wrap it inside a custom one
250+
assert.Equal(t, "Keyspace 'non_existing' does not exist", res.ErrorMessage)
216251
}
217252

218253
func TestProxy_NegotiateProtocolV5(t *testing.T) {

0 commit comments

Comments
 (0)