Skip to content

Commit f15b9a5

Browse files
h3n4lclaude
andauthored
feat: support pretty() no-op and Int32/NumberInt string args (#25)
* feat: support deprecated mongosh methods and Int32 string args (BYT-9080) Fix 5 mongosh compatibility issues that caused unnecessary fallbacks: - pretty(): treat as no-op cursor method (output already formatted) - insert(): translate to insertOne/insertMany based on argument type - count(): translate to countDocuments - update(): translate to updateOne/updateMany based on multi option - Int32("string")/NumberInt("string"): accept string arguments in parser Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat: support pretty() no-op and Int32 string args (BYT-9080) Fix 2 mongosh compatibility issues that caused unnecessary fallbacks: - pretty(): treat as no-op cursor method (output already formatted) - Int32("string")/NumberInt("string"): accept string arguments in parser Deprecated methods (insert, count, update) intentionally left as unsupported to keep gomongo simple — they fall back to mongosh. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: move tests to proper files, mark replace as temporary - Move TestPrettyNoOp to collection_test.go (cursor method behavior) - Move TestInt32StringArg to bson_helpers_test.go (BSON helper behavior) - Delete compat_test.go - Add TODO comment on replace directive (remove after parser published) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore: upgrade parser to 3b4d6e7, remove replace directive Update github.com/bytebase/parser to include Int32/NumberInt string argument support (parser#64). Remove temporary local replace directive. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * test: strengthen assertions per review feedback - Assert NumberInt("456") stores as int32(456), not just no-error - Assert NumberLong("1774250313") stores as int64(1774250313) - Insert in reverse order in pretty() sort test so assertion is meaningful Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d2ea344 commit f15b9a5

File tree

7 files changed

+94
-6
lines changed

7 files changed

+94
-6
lines changed

collection_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2262,3 +2262,38 @@ func TestCursorMinMethod(t *testing.T) {
22622262
require.Equal(t, 2, len(result.Value))
22632263
})
22642264
}
2265+
2266+
func TestPrettyNoOp(t *testing.T) {
2267+
testutil.RunOnAllDBs(t, func(t *testing.T, db testutil.TestDB) {
2268+
dbName := fmt.Sprintf("testdb_pretty_%s", db.Name)
2269+
defer testutil.CleanupDatabase(t, db.Client, dbName)
2270+
2271+
ctx := context.Background()
2272+
collection := db.Client.Database(dbName).Collection("users")
2273+
// Insert in reverse alphabetical order so sort assertions are meaningful.
2274+
_, err := collection.InsertMany(ctx, []any{
2275+
bson.M{"name": "bob", "age": 25},
2276+
bson.M{"name": "alice", "age": 30},
2277+
})
2278+
require.NoError(t, err)
2279+
2280+
gc := gomongo.NewClient(db.Client)
2281+
2282+
// pretty() should be a no-op cursor method
2283+
result, err := gc.Execute(ctx, dbName, `db.users.find().pretty()`)
2284+
require.NoError(t, err)
2285+
require.Equal(t, 2, len(result.Value))
2286+
2287+
// pretty() chained after sort — inserted bob first, sort should put alice first
2288+
result, err = gc.Execute(ctx, dbName, `db.users.find().sort({name: 1}).pretty()`)
2289+
require.NoError(t, err)
2290+
require.Equal(t, 2, len(result.Value))
2291+
rows := valuesToStrings(result.Value)
2292+
require.Contains(t, rows[0], `"alice"`)
2293+
2294+
// aggregate().pretty()
2295+
result, err = gc.Execute(ctx, dbName, `db.users.aggregate([{$match: {name: "alice"}}]).pretty()`)
2296+
require.NoError(t, err)
2297+
require.Equal(t, 1, len(result.Value))
2298+
})
2299+
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.24.5
44

55
require (
66
github.com/antlr4-go/antlr/v4 v4.13.1
7-
github.com/bytebase/parser v0.0.0-20260130090605-effef73942d9
7+
github.com/bytebase/parser v0.0.0-20260324035613-3b4d6e704551
88
github.com/google/uuid v1.6.0
99
github.com/stretchr/testify v1.11.1
1010
github.com/testcontainers/testcontainers-go v0.40.0

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
88
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
99
github.com/bytebase/antlr/v4 v4.0.0-20240827034948-8c385f108920 h1:IfmPt5o5R70NKtOrs+QHOoCgViYZelZysGxVBvV4ybA=
1010
github.com/bytebase/antlr/v4 v4.0.0-20240827034948-8c385f108920/go.mod h1:ykhjIPiv0IWpu3OGXCHdz2eUSe8UNGGD6baqjs8jSuU=
11-
github.com/bytebase/parser v0.0.0-20260130090605-effef73942d9 h1:q5MnVPWlV/p3MPe60SysVoUaEnyeS6+OOOk0F3DLLK8=
12-
github.com/bytebase/parser v0.0.0-20260130090605-effef73942d9/go.mod h1:jeak/EfutSOAuWKvrFIT2IZunhWprM7oTFBRgZ9RCxo=
11+
github.com/bytebase/parser v0.0.0-20260324035613-3b4d6e704551 h1:jmOIMyIEXoMWZ+TK91STbBMPRom7S/tWxosxYZDg9jk=
12+
github.com/bytebase/parser v0.0.0-20260324035613-3b4d6e704551/go.mod h1:jeak/EfutSOAuWKvrFIT2IZunhWprM7oTFBRgZ9RCxo=
1313
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
1414
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
1515
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=

internal/translator/bson_helpers.go

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,18 +178,22 @@ func convertLongHelper(ctx mongodb.ILongHelperContext) (int64, error) {
178178
return strconv.ParseInt(numStr, 10, 64)
179179
}
180180

181-
// convertInt32Helper converts Int32(123) or NumberInt(123) to int32.
181+
// convertInt32Helper converts Int32(123), Int32("123"), NumberInt(123), or NumberInt("123") to int32.
182182
func convertInt32Helper(ctx mongodb.IInt32HelperContext) (int32, error) {
183183
helper, ok := ctx.(*mongodb.Int32HelperContext)
184184
if !ok {
185185
return 0, fmt.Errorf("invalid Int32 helper context")
186186
}
187187

188-
if helper.NUMBER() == nil {
188+
var numStr string
189+
if helper.NUMBER() != nil {
190+
numStr = helper.NUMBER().GetText()
191+
} else if helper.StringLiteral() != nil {
192+
numStr = unquoteString(helper.StringLiteral().GetText())
193+
} else {
189194
return 0, nil
190195
}
191196

192-
numStr := helper.NUMBER().GetText()
193197
i, err := strconv.ParseInt(numStr, 10, 32)
194198
if err != nil {
195199
return 0, err

internal/translator/bson_helpers_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,49 @@ func TestDecimal128Helper(t *testing.T) {
204204
require.NotNil(t, price)
205205
})
206206
}
207+
208+
func TestInt32StringArg(t *testing.T) {
209+
testutil.RunOnAllDBs(t, func(t *testing.T, db testutil.TestDB) {
210+
dbName := "testdb_int32str_" + db.Name
211+
defer testutil.CleanupDatabase(t, db.Client, dbName)
212+
213+
gc := gomongo.NewClient(db.Client)
214+
ctx := context.Background()
215+
216+
// Int32("123") should parse the string as int32
217+
_, err := gc.Execute(ctx, dbName, `db.test.insertOne({val: Int32("123")})`)
218+
require.NoError(t, err)
219+
220+
result, err := gc.Execute(ctx, dbName, `db.test.findOne({})`)
221+
require.NoError(t, err)
222+
require.Equal(t, 1, len(result.Value))
223+
doc, ok := result.Value[0].(bson.D)
224+
require.True(t, ok)
225+
val := getDocField(doc, "val")
226+
require.Equal(t, int32(123), val)
227+
228+
// NumberInt("456") should also work
229+
_, err = gc.Execute(ctx, dbName, `db.test.insertOne({val2: NumberInt("456")})`)
230+
require.NoError(t, err)
231+
232+
result, err = gc.Execute(ctx, dbName, `db.test.findOne({val2: {$exists: true}})`)
233+
require.NoError(t, err)
234+
require.Equal(t, 1, len(result.Value))
235+
doc, ok = result.Value[0].(bson.D)
236+
require.True(t, ok)
237+
val2 := getDocField(doc, "val2")
238+
require.Equal(t, int32(456), val2)
239+
240+
// NumberLong("1774250313") — from real user report
241+
_, err = gc.Execute(ctx, dbName, `db.test.insertOne({val3: NumberLong("1774250313")})`)
242+
require.NoError(t, err)
243+
244+
result, err = gc.Execute(ctx, dbName, `db.test.findOne({val3: {$exists: true}})`)
245+
require.NoError(t, err)
246+
require.Equal(t, 1, len(result.Value))
247+
doc, ok = result.Value[0].(bson.D)
248+
require.True(t, ok)
249+
val3 := getDocField(doc, "val3")
250+
require.Equal(t, int64(1774250313), val3)
251+
})
252+
}

internal/translator/collection.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2225,3 +2225,4 @@ func (v *visitor) extractCreateIndexesArgs(ctx mongodb.ICreateIndexesMethodConte
22252225
return
22262226
}
22272227
}
2228+

internal/translator/visitor.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,8 @@ func (v *visitor) visitCursorMethodCall(ctx mongodb.ICursorMethodCallContext) {
281281
v.extractMax(mc.MaxMethod())
282282
case mc.MinMethod() != nil:
283283
v.extractMin(mc.MinMethod())
284+
case mc.PrettyMethod() != nil:
285+
// pretty() is a no-op — output is already formatted.
284286
default:
285287
methodName := extractMethodNameFromText(mc.GetText())
286288
if methodName != "" {

0 commit comments

Comments
 (0)