From a1d7684fc1f793db624e26887707834523c0dd3f Mon Sep 17 00:00:00 2001 From: go-jet Date: Mon, 4 May 2026 12:34:29 +0200 Subject: [PATCH] Make sure mysql select json object keys are escaped. --- internal/jet/sql_builder.go | 3 +- internal/jet/sql_builder_test.go | 8 ++++++ tests/mysql/select_json_test.go | 44 ++++++++++++++++++++++++++++++ tests/postgres/select_json_test.go | 42 ++++++++++++++++++++++++++++ 4 files changed, 96 insertions(+), 1 deletion(-) diff --git a/internal/jet/sql_builder.go b/internal/jet/sql_builder.go index 9471911..a7a6abc 100644 --- a/internal/jet/sql_builder.go +++ b/internal/jet/sql_builder.go @@ -112,7 +112,8 @@ func (s *SQLBuilder) WriteString(str string) { // WriteJsonObjKey serializes json object key func (s *SQLBuilder) WriteJsonObjKey(key string) { - s.WriteString(fmt.Sprintf(`'%s', `, key)) + s.WriteString(stringQuote(key)) + s.WriteString(", ") } // WriteIdentifier adds identifier to output SQL diff --git a/internal/jet/sql_builder_test.go b/internal/jet/sql_builder_test.go index fc6e326..71424f7 100644 --- a/internal/jet/sql_builder_test.go +++ b/internal/jet/sql_builder_test.go @@ -45,6 +45,14 @@ func TestArgToString(t *testing.T) { }() } +func TestWriteJsonObjKeyEscapesStringLiteral(t *testing.T) { + s := &SQLBuilder{Dialect: defaultDialect} + + s.WriteJsonObjKey("author's name") + + require.Equal(t, "'author''s name', ", s.Buff.String()) +} + func TestFallTrough(t *testing.T) { require.Equal(t, FallTrough([]SerializeOption{ShortName}), []SerializeOption{ShortName}) require.Equal(t, FallTrough([]SerializeOption{SkipNewLine}), []SerializeOption(nil)) diff --git a/tests/mysql/select_json_test.go b/tests/mysql/select_json_test.go index d2994db..d18623c 100644 --- a/tests/mysql/select_json_test.go +++ b/tests/mysql/select_json_test.go @@ -505,3 +505,47 @@ func TestSelectJson_ProjectionNotAliased(t *testing.T) { }, "jet: expression need to be aliased when used as SELECT JSON projection.") }) } + +func TestSelectJsonObject_EscapesJsonKeys(t *testing.T) { + stmt := SELECT_JSON_OBJ( + String("value").AS("author"), + String("value").AS("author's name"), + String("value").AS("author''s name"), + String("value").AS("author \"name\""), + String("value").AS(`C:\tmp\file`), + String("value").AS("hello\nworld"), + String("value").AS("a'b\\\\c\\nd\\r\\x00e\\x1af"), + String("value").AS("žika 😀"), + ) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT JSON_OBJECT( + 'author', 'value', + 'author''s name', 'value', + 'author''''s name', 'value', + 'author "name"', 'value', + 'C:\tmp\file', 'value', + 'hello +world', 'value', + 'a''b\\c\nd\r\x00e\x1af', 'value', + 'žika 😀', 'value' + ) AS "json"; +`) + + var dest map[string]any + + err := stmt.QueryContext(ctx, db, &dest) + require.NoError(t, err) + testutils.AssertJSON(t, dest, ` +{ + "C:\tmpfile": "value", + "a'b\\c\nd\rx00ex1af": "value", + "author": "value", + "author \"name\"": "value", + "author''s name": "value", + "author's name": "value", + "hello\nworld": "value", + "žika 😀": "value" +} +`) +} diff --git a/tests/postgres/select_json_test.go b/tests/postgres/select_json_test.go index 634f93a..6c19ad3 100644 --- a/tests/postgres/select_json_test.go +++ b/tests/postgres/select_json_test.go @@ -914,3 +914,45 @@ func TestSelectJson_InvalidJson(t *testing.T) { err := stmt.QueryContext(ctx, db, &dest) require.ErrorContains(t, err, "invalid json") } + +func TestSelectJsonObject_EscapesJsonKeys(t *testing.T) { + stmt := SELECT_JSON_OBJ( + String("value").AS("author"), + String("value").AS("author's name"), + String("value").AS("author''s name"), + String("value").AS(`C:\tmp\file`), + String("value").AS("hello\nworld"), + String("value").AS("a'b\\\\c\\nd\\r\\x00e\\x1af"), + String("value").AS("žika 😀"), + ) + + testutils.AssertDebugStatementSql(t, stmt, ` +SELECT row_to_json(records) AS "json" +FROM ( + SELECT 'value'::text AS "author", + 'value'::text AS "author's name", + 'value'::text AS "author''s name", + 'value'::text AS "C:\tmp\file", + 'value'::text AS "hello +world", + 'value'::text AS "a'b\\c\nd\r\x00e\x1af", + 'value'::text AS "žika 😀" + ) AS records; +`) + + var dest map[string]any + + err := stmt.QueryContext(ctx, db, &dest) + require.NoError(t, err) + testutils.AssertJSON(t, dest, ` +{ + "C:\\tmp\\file": "value", + "a'b\\\\c\\nd\\r\\x00e\\x1af": "value", + "author": "value", + "author''s name": "value", + "author's name": "value", + "hello\nworld": "value", + "žika 😀": "value" +} +`) +}