Query jarang gagal saat ditulis — galatnya muncul justru saat aplikasi berjalan dan server mencoba mengeksekusi filter yang salah bentuk. Kejanggalannya: MongoDB terkenal fleksibel terhadap bentuk document, tetapi sintaks query tetap ketat; operator yang salah tulis ditolak keras, sementara kesalahan tipe data justru diterima diam-diam dan mengembalikan hasil kosong. Chapter ini menguji lima bentuk galat query yang paling sering muncul — semuanya dengan pesan asli dari MongoDB 9.0 — lalu menutup dengan tabel rujukan gejala-perbaikan.

Peta materi Chapter ini:

  • Operator yang tidak dikenal dan field yang berawalan $.
  • Pola $regex yang tidak valid.
  • Kebalikannya: kesalahan tipe yang tidak menghasilkan galat sama sekali.
  • Projection yang mencampur mode inclusion dan exclusion.

Data Latihan #

Kita pakai collection buku kecil sebagai sasaran uji:

js
use latihan_trouble
db.buku.drop()
db.buku.insertMany([
  { judul: "Laut Bercerita", penulis: "Leila S. Chudori", harga: 98000, stok: 12 },
  { judul: "Bumi", penulis: "Tere Liye", harga: 89000, stok: 7 },
  { judul: "Filosofi Teras", penulis: "Henry Manampiring", harga: 105000, stok: 25 }
])

Output yang Dihasilkan:

text
switched to db latihan_trouble
true
{
  acknowledged: true,
  insertedIds: {
    '0': ObjectId('6ad401ee65492c702cd6cc9c'),
    '1': ObjectId('6ad401ee65492c702cd6cc9d'),
    '2': ObjectId('6ad401ee65492c702cd6cc9e')
  }
}

Dua hal berbeda antar eksekusi dan wajar untuk tutorial: ObjectId dihasilkan otomatis, dan nilai balik drop() bernilai true baik collection ada maupun tidak — mongosh memang mengembalikan true untuk keduanya.

Operator yang Tidak Dikenal #

Satu huruf yang salah pada nama operator sudah cukup untuk menolak seluruh query:

js
db.buku.find({ $badOperator: 1 }).itcount()

Output yang Dihasilkan:

text
Uncaught 
MongoServerError[BadValue]: unknown top level operator: $badOperator. If you have a field name that starts with a '$' symbol, consider using $getField or $setField.

Pesan ini perlu dibaca dua bagian. Bagian pertama menyebut operator asing $badOperator — server memperlakukan setiap kunci berawalan $ pada level teratas filter sebagai operator, lalu mencari namanya di daftar operator query. Bagian kedua adalah petunjuk aktif: bila maksud kita memang menanyakan field yang namanya berawalan $, MongoDB menyediakan $getField dan $setField sebagai jalurnya. Kasus nyata paling sering: $gte tertulis $get, atau $in tertulis $ini.

Kasus kedua memperlihatkan petunjuk itu bekerja. Filter berikut bermaksud mencari field bernama $harga:

js
db.buku.find({ "$harga": 98000 }).itcount()

Output yang Dihasilkan:

text
Uncaught 
MongoServerError[BadValue]: unknown top level operator: $harga. If you have a field name that starts with a '$' symbol, consider using $getField or $setField.

Meski niatnya field, penulisan "$harga" sebagai kunci filter membuat server mengiranya operator — pesan yang sama persis muncul. Solusi terbaik adalah menghindari nama field berawalan $ sejak desain skema, tetapi bila data seperti itu sudah ada (misalnya hasil migrasi dari sistem lain), jalur resminya adalah $expr dengan $getField. Kita tambahkan satu document yang memang memuat field $harga:

js
db.buku.insertOne({ judul: "Kamus Dwibahasa", "$harga": 120000 })

Output yang Dihasilkan:

text
{
  acknowledged: true,
  insertedId: ObjectId('6ad401ee65492c702cd6cc9f')
}

Lalu tanyakan field itu secara eksplisit — $literal menjaga nama $harga tidak dianggap path field, dan $$ROOT menunjuk document yang sedang diperiksa:

js
db.buku.find(
  { $expr: { $eq: [{ $getField: { field: { $literal: "$harga" }, input: "$$ROOT" } }, 120000] } },
  { judul: 1, _id: 0 }
).toArray()

Output yang Dihasilkan:

text
[ { judul: 'Kamus Dwibahasa' } ]

Pola Regex yang Tidak Valid #

Operator $regex meneruskan pola ke mesin regular expression di sisi server, sehingga pola yang rusak ditolak saat eksekusi:

js
db.buku.find({ judul: { $regex: "[" } }).itcount()

Output yang Dihasilkan:

text
Uncaught 
MongoServerError[Location51091]: Regular expression is invalid: missing terminating ] for character class

Pesan galat dari PCRE cukup komunikatif: [ membuka character class yang tidak pernah ditutup. Perbaikannya tentu pada polanya ("\\[" bila maksudnya karakter kurung siku literal), dan karena pola biasa datang dari input pengguna, validasi di sisi aplikasi mencegah pola rusak sampai ke server.

Galat Diam-Diam: Tipe yang Tidak Cocok #

Bentuk paling licik dari query bermasalah tidak menghasilkan galat sama sekali. Bandingkan dua filter hampir identik — satu memakai string, satu memakai angka:

js
db.buku.find({ harga: { $gt: "50000" } }).itcount()
db.buku.find({ harga: { $gt: 50000 } }).itcount()
typeof db.buku.findOne().harga

Output yang Dihasilkan:

text
0
3
number

Baris pertama mengembalikan 0 padahal tiga buku jelas lebih mahal dari 50000. Penyebabnya ada di urutan tipe BSON: perbandingan $gt hanya berlaku antar nilai dengan urutan tipe yang sama, dan string "50000" tidak pernah dibandingkan dengan angka 98000 — keduanya berada di “dunia” pengurutan yang berbeda. Baris kedua dengan angka mengembalikan 3 (seluruh buku), dan typeof menegaskan field harga memang bertipe number di database. Diagnosis untuk gejala “query hasilnya kosong padahal datanya ada” hampir selalu berakhir di sini: periksa tipe nilai di filter versus tipe field di document.

Projection yang Tidak Konsisten #

Projection hanya boleh bekerja dalam satu mode: menyertakan field (inclusion) atau mengecualikan field (exclusion) — tidak boleh keduanya sekaligus:

js
db.buku.find({}, { judul: 1, harga: 0 }).toArray()

Output yang Dihasilkan:

text
Uncaught 
MongoServerError[Location31254]: Cannot do exclusion on field harga in inclusion projection

Pesan galatnya menyebut field harga sebagai penyebab: projection dimulai sebagai inclusion (karena judul: 1) lalu menemukan perintah exclusion harga: 0. Satu-satunya pengecualian yang diizinkan adalah _id: 0 — field _id memang selalu ikut secara bawaan sehingga boleh dikeluarkan dari mode mana pun. Bentuk yang benar untuk “judul saja tanpa _id”:

js
db.buku.find({}, { judul: 1, _id: 0 }).toArray()

Output yang Dihasilkan:

text
[
  { judul: 'Laut Bercerita' },
  { judul: 'Bumi' },
  { judul: 'Filosofi Teras' },
  { judul: 'Kamus Dwibahasa' }
]

Tabel Rujukan Cepat #

GejalaPenyebabPerbaikan
unknown top level operator: $xNama operator salah tulisPeriksa ejaan operator di dokumentasi; $gte, $in, $ne
unknown top level operator padahal maksudnya fieldField berawalan $ ditulis sebagai kunci filterGanti nama field, atau $expr + $getField
Regular expression is invalidPola $regex rusakPerbaiki pola; escape karakter khusus; validasi input pengguna
Hasil kosong padahal data adaNilai filter bertipe berbeda dari fieldSamakan tipe (angka vs string); cek dengan typeof
Cannot do exclusion ... in inclusion projectionProjection campur 1 dan 0Pilih satu mode; pengecualian hanya untuk _id
Query lambat setelah “diperbaiki”Filter cocok tipe tapi tak didukung indexLanjut ke diagnosis Chapter Slow Queries

Rangkuman #

  • Kunci berawalan $ di level teratas filter selalu diperlakukan operator; field berawalan $ harus lewat $getField.
  • Pola $regex divalidasi di sisi server — pesan PCRE langsung menunjuk karakter bermasalah.
  • Kesalahan tipe tidak menghasilkan galat: hasil kosong adalah gejalanya; bandingkan tipe filter dengan tipe field.
  • Projection bekerja satu mode; _id adalah satu-satunya pengecualian yang boleh dicampur.