Skip to content

[TASK-FIX-COMMENTED-BIND-PARAMS-V1.18.4] Pembersihan Parameter Bind dalam Komentar SQL, Normalisasi Undefined Parameter, dan Observabilitas Query Warnings ​

Modul: Pengangkut | Tipe: Bugfix / Hardening | Stage Saat Ini: STAGING | Tanggal Dokumen: 2026-09-26

STAGING STAGE (UAT VERIFICATION)

Dokumen ini sedang berada dalam tahap STAGING. Perubahan logika backend, hardening adapter database, dan penambahan observabilitas query warnings telah diterapkan di lingkungan Staging (UAT) untuk proses verifikasi QA.


1. Informasi Tugas & Referensi Bisnis ​

  • Task ID / Nomor Referensi: TASK-FIX-COMMENTED-BIND-PARAMS-V1.18.4

  • Sumber Instruksi: Perbaikan Bug Raw SQL Comment Bind Parameter (POST /summary/status), Normalisasi Parameter Undefined, dan Observabilitas Diagnostik Database

  • Dokumen Referensi / Lampiran: Tidak ada

  • Task / Dependensi Terkait (Cross-Service): Tidak ada

  • Modul Terkait: Pengangkut

  • Service / Repository: pengangkut-backend

  • PIC Pengembang: Hermansyah Efendi

  • Latar Belakang & Ringkasan Kebutuhan: Pasca unifikasi connection pool ke Sequelize ORM pada core database adapter, ditemukan dua anomali kritis pada eksekusi raw SQL query di lingkungan Staging:

    1. Commented Bind Parameters: Eksekusi raw SQL query yang memuat parameter bind di dalam baris komentar SQL (seperti -- AND flag = $4 atau -- VALUES ($1, $2, $3, $4, $5) pada model superset_summary_models.js di endpoint POST /summary/status) mengalami kegagalan dengan pesan error "Named bind parameter $4 has no value in the given object". Hal ini terjadi karena parser internal Sequelize memvalidasi parameter bind menggunakan regex global tanpa mengindahkan konteks sintaks komentar SQL.
    2. Undefined Parameter Crash vs Native PG Compatibility: Pada driver native pg terdahulu, nilai bind parameter bernilai undefined diperlakukan toleran oleh Postgres protocol (dikonversi/diabaikan sebagai NULL). Namun, Sequelize ORM secara ketat melempar pengecualian saat mendeteksi nilai parameter undefined (contoh pada pemanggilan getManifestInaportContainer saat variabel idBongkarContainer bernilai undefined di warta_kapal_controller.js:1687).

    Untuk mengatasi kedua anomali tersebut secara menyeluruh tanpa merusak stabilitas puluhan file model lama, dilakukan perbaikan transparan pada adapter database (app/connection.js) yang mencakup pembersihan bind parameter di dalam baris komentar via state-machine lexer stripCommentedBindParams, normalisasi otomatis parameter undefined menjadi null, pemasangan defensive guard pada query model item manifest, serta pembangunan suite observabilitas diagnostik query-warning.service.js beserta endpoint admin /health/query-warnings.

  • Ruang Lingkup & Rincian Sub-Pekerjaan (Technical Scope & Milestones):

    • [x] Implementasi lexer scanner stripCommentedBindParams dan helper modular (scanQuotedString, scanLineComment, scanBlockComment) di app/connection.js
    • [x] Proteksi string literal kutip tunggal ('...', escape '' dan \') serta identifier kutip ganda ("...") agar tidak terhapus
    • [x] Integrasi pembersihan komentar SQL ke normalizeQueryInput pada adapter app/connection.js (berlaku untuk master, replica, dan transaction client)
    • [x] Normalisasi otomatis parameter bind bernilai undefined menjadi null pada normalizeQueryInput untuk memulihkan kompatibilitas driver pg
    • [x] Pemasangan defensive guard check if (!id_header) return []; pada item_models.js (getManifestInaportContainer dan getManifestInaportCargo) guna mencegah query database tanpa ID
    • [x] Pembangunan service observabilitas app/services/query-warning.service.js dengan persistensi Redis list (monitoring:query_warnings, TTL 7 hari), in-memory fallback ring buffer, dan ekstraksi otomatis lokasi caller (extractCallerFromStack)
    • [x] Penyediaan endpoint admin GET /health/query-warnings (dengan filter date dan limit) serta DELETE /health/query-warnings dengan validasi Zod
    • [x] Standardisasi seluruh controller pada app/controllers/health-controller.js mengikuti konvensi arsitektur asyncHandler, res.sendJson, dan semantic error InternalServerError
    • [x] Registrasi 2 variabel lingkungan baru DB_QUERY_WARNINGS_KEY dan DB_QUERY_WARNINGS_MAX di app/utils/env-validator.js dan .env.example
    • [x] Penambahan unit test dan integration test komprehensif (99 skenario passed) pada tests/connection.test.js, tests/services/query-warning.service.test.js, tests/controllers/health-controller.test.js, dan tests/validations/health.validation.test.js
    • [x] Bump versi rilis ke v1.18.4 pada package.json, package-lock.json, dan dokumentasi changelog di CHANGELOG.md

2. Deployment Lifecycle ​

#EnvironmentTanggal & WaktuPICStatusCatatan
1Development2026-09-26 12:27Hermansyah EfendiDEPLOYEDBranch: development
2Staging (UAT)2026-09-26 12:44Hermansyah EfendiDEPLOYEDRelease Candidate v1.18.4 via MR Staging. Co-deployed: [TASK-FIX-COMMENTED-BIND-PARAMS-V1.18.4, TASK-UNIFY-DB-POOL-AND-HEALTH-OBSERVABILITY, TASK-DRAGONFLYDB-MIGRATION, TASK-SPLIT-BILLING-202608, FEAT-MANUAL-BILLING-REQUEST, MAINT-SUPERSET-SUMMARY-SHIP-SYNC-SECURITY, REFACTOR-DEPRECATE-POST-STATUS-SERVICE-MIGRATION, TASK-PINO-LOGGER-REFACTOR, ND-304-LNSW.34-2026, ND-256-LNSW.34-2026, ND-183-LNSW.34-2026]
3Production——PENDING—

Riwayat Update Log ​

TanggalEnvironmentPerubahanPIC
2026-09-26StagingPromosi ke STAGING (Release Candidate v1.18.4)Hermansyah Efendi
2026-09-26DevelopmentInitial log created & updated with query warnings monitoring & undefined parameter normalizationHermansyah Efendi

3. Analisis Teknis & Rincian Perubahan ​

  • Alur Logika & Bisnis:

    1. State-Machine Lexer stripCommentedBindParams:
      • Menghapus bind parameters ($1, $2, ... atau $name) yang berada di dalam komentar baris (-- ...) maupun blok komentar (/* ... */) sebelum dikirim ke engine validasi Sequelize.
      • Melindungi literal string kutip tunggal ('...', escape '' dan \') dan identifier kutip ganda ("...") agar konten data bisnis (misal parameter pencarian berkode 'ABC--123') tidak terpotong.
      • Membagi kompleksitas scanner ke sub-fungsi modular (scanQuotedString, scanLineComment, scanBlockComment) sesuai batas ambang SonarQube (< 15).
    2. Normalisasi Parameter undefined ke null:
      • Pada normalizeQueryInput, array bindParams dipetakan secara terpusat: jika ditemukan elemen bernilai undefined, nilainya otomatis dikonversi menjadi null.
      • Konversi ini mereplikasi perilaku native driver PostgreSQL (di mana WHERE column = NULL mengevaluasi ke falsy tanpa error crash runtime).
    3. Perekaman Diagnostik Query Warnings:
      • Saat terdeteksi parameter undefined, sistem membangkitkan Error('Undefined parameter').stack dan memanggil recordQueryWarning({ sql, paramIndex, errorStack }).
      • Helper extractCallerFromStack memfilter stack frame internal adapter (connection.js) dan Node.js internals untuk mengidentifikasi file dan nomor baris pemanggil asli di application layer (misal: app/controllers/warta_kapal_controller.js:1687).
      • Data warning disimpan ke Redis list (monitoring:query_warnings, TTL 7 hari, max buffer 100 entri) dan dilengkapi in-memory ring-buffer fallback jika Redis tidak tersedia.
    4. Defensive Guard item_models.js:
      • Pada fungsi getManifestInaportContainer dan getManifestInaportCargo, ditambahkan pengecekan if (!id_header) return []; guna mengeliminasi roundtrip query database jika ID induk tidak tersedia.
    5. Standardisasi Controller Kesehatan (health-controller.js):
      • Seluruh endpoint observabilitas dibungkus oleh asyncHandler dan divalidasi via Zod middleware parseWithZod.
      • Format respons diseragamkan menggunakan envelope standar res.sendJson({ status: 200, message, data }).
  • Perubahan Database (DDL / DML):

    sql
    -- Tidak ada migration file yang terdeteksi.
    -- Perubahan murni internal engine level pada adapter query database (app/connection.js) dan service observabilitas.
  • Konfigurasi Variabel Lingkungan Baru (.env.example & ENV_SCHEMA):

    NoVariabel LingkunganTipe / DefaultKategoriDeskripsi / Fungsi Teknis
    1DB_QUERY_WARNINGS_KEYString (monitoring:query_warnings)Query DiagnosticsKey Redis List untuk buffer pencatatan query yang menerima bind parameter undefined.
    2DB_QUERY_WARNINGS_MAXNumber (100)Query DiagnosticsKapasitas maksimum buffer riwayat warning query yang disimpan di Redis list.
  • Kontrak API & Response Payload:

    GET /health/query-warnings ​

    • Deskripsi: Mengambil riwayat query SQL yang terdeteksi menerima bind parameter undefined beserta file caller aslinya.
    • Autentikasi / Guard: Bearer Token (doIntrospection, requireAdmin)
    • Request Header / Query / Params:
      • Header: Authorization: Bearer <admin_token>
      • Query: date=YYYY-MM-DD (opsional), limit=50 (opsional)
    • Response Payload (200 OK):
      json
      {
        "status": 200,
        "message": "Query warnings retrieved successfully",
        "data": {
          "status": "UP",
          "count": 1,
          "filter_date": "2026-09-26",
          "data": [
            {
              "id": "warn-e05edfc1509f",
              "timestamp": "2026-09-26T12:27:49.000Z",
              "source_caller": "app/controllers/warta_kapal_controller.js:1687:25",
              "param_index": 1,
              "query_sample": "SELECT * FROM pengangkut.inaport_container WHERE id_header = $1",
              "normalized_to": "NULL"
            }
          ]
        }
      }

    DELETE /health/query-warnings ​

    • Deskripsi: Membersihkan buffer Redis dan in-memory cache riwayat query warning.
    • Autentikasi / Guard: Bearer Token (doIntrospection, requireAdmin)
    • Request Header / Query / Params:
      • Header: Authorization: Bearer <admin_token>
    • Response Payload (200 OK):
      json
      {
        "status": 200,
        "message": "Query warnings buffer cleared successfully",
        "data": {
          "cleared": true
        }
      }

4. Bukti Teknis (Technical Proof & Audit Trail) ​

  • Repository: pengangkut-backend
  • Branch Utama: development
  • Merge Request / PR: — (Draft MR v1.18.4)

Daftar Commit Terkait (Multi-Commit Trail) ​

Commit IDBranchAuthorTanggal & JamRingkasan Perubahan
fe6f854f4879f1965b0b316581be13575860d479developmentHermansyah Efendi2026-09-26 12:27feat(monitoring): track undefined query parameters and standardize health controller
cea0193a04a250ff6a62fb0c48bb7e643a652891developmentHermansyah Efendi2026-09-24 16:58chore(release): v1.18.4
0f1509814accb05bd0e1f30f0a1d6423466aa3b0developmentHermansyah Efendi2026-09-24 16:56fix(database): strip commented bind parameters from queries before validation

Cuplikan Kode Kunci (Real Code) ​

javascript
// app/connection.js - Normalisasi Parameter Undefined & Perekaman Query Warning
export const normalizeQueryInput = (queryTextOrConfig, params) => {
  let sql = queryTextOrConfig;
  let bindParams = params;

  if (typeof queryTextOrConfig === 'object' && queryTextOrConfig !== null) {
    sql = queryTextOrConfig.text;
    bindParams = queryTextOrConfig.values !== undefined ? queryTextOrConfig.values : params;
  }

  if (bindParams === undefined || bindParams === null) {
    bindParams = [];
  } else if (!Array.isArray(bindParams)) {
    bindParams = [bindParams];
  }

  // Normalize undefined parameter values to null (matches native pg driver behavior)
  // and record diagnostic warning for tracking in /health/query-warnings
  let hasCapturedStack = false;
  let errorStack = '';
  bindParams = bindParams.map((param, index) => {
    if (param === undefined) {
      if (!hasCapturedStack) {
        errorStack = new Error('Undefined parameter').stack || '';
        hasCapturedStack = true;
      }
      recordQueryWarning({ sql, paramIndex: index + 1, errorStack }).catch(() => {
        // Fire-and-forget: fail silently to prevent monitoring failures from interrupting queries
      });
      return null;
    }
    return param;
  });

  if (typeof sql === 'string') {
    sql = stripCommentedBindParams(sql);
  }

  return { sql, bindParams };
};

// app/models/item_models.js - Defensive Guard untuk Mencegah Query Tanpa Header ID
const getManifestInaportContainer = async (id_header) => {
  if (!id_header) {
    return [];
  }
  let queryText = `    
  select
      id.id_barang "idBarang", 
      id.id_manifest "idManifest"
  from pengangkut.inaport_container ic 
  ...`;
  ...
};

5. Bukti Verifikasi Pengujian (QA / Unit Test / API Verification) ​

A. Verifikasi Unit Test (Development Stage) ​

  • Test Suite Files:
    • tests/connection.test.js
    • tests/services/query-warning.service.test.js
    • tests/controllers/health-controller.test.js
    • tests/validations/health.validation.test.js
  • Status Eksekusi CLI: PASSED (99 tests passed, 100% coverage pada modul terkait)
text
 RUN  v4.1.11 D:/WORK/LNSW/pengangkut-backend

 ✓ tests/connection.test.js (55 tests) 170ms
 ✓ tests/services/query-warning.service.test.js (13 tests) 497ms
 ✓ tests/controllers/health-controller.test.js (21 tests) 82ms
 ✓ tests/validations/health.validation.test.js (10 tests) 35ms

 Test Files  4 passed (4)
      Tests  99 passed (99)
   Start at  12:30:24
   Duration  3.00s (transform 1.01s, setup 0ms, import 3.72s, tests 928ms, environment 1ms)

B. Panduan Pengujian QA & Verifikasi Staging (QA Playbook) ​

  • Cakupan / Surface Pengujian: API / Postman Only
  • Kebutuhan Akses / Role Akun: PPJK / Agent Pengangkut (Bearer Token) dan Administrator (Role 00 / 07 / 20)
  • Hasil Pengujian UAT: PENDING
  • Catatan QA: —
Skenario Pengujian QA (Test Scenarios) ​
  1. Skenario 1 — Update Status Ringkasan (Happy Path dengan Komentar SQL Warisan):

    • Pre-kondisi: Record history_status_header tersedia pada database untuk service_number terkait.
    • Trigger / Request: POST /summary/status?showAll=true&type=m
      json
      {
        "service_number": "101019B85DBC3",
        "service_type": "wkk",
        "status_id": 2,
        "status_code": 2,
        "status_info": "Updated via QA Test"
      }
    • Ekspektasi Response: HTTP 200 OK (atau 201 Created), proses query database berhasil dieksekusi tanpa error Named bind parameter "$4" has no value in the given object.
    • Verifikasi Database / Log: Record baru bertambah pada tabel pengangkut.history_status_detail dengan service_type = 'wkk' dan status_id = 2.
  2. Skenario 2 — Eksekusi Raw Query dengan String yang Mengandung Karakter Dash:

    • Pre-kondisi: Parameter pencarian mengandung karakter tanda hubung ganda (misal kode registrasi 'ABC--123').
    • Trigger / Request: Request query melalui endpoint yang memfilter kode tersebut.
    • Ekspektasi Response: HTTP 200 OK, string literal 'ABC--123' tidak terpotong oleh parser lexer dan nilai bind tetap utuh.
    • Verifikasi Database / Log: Tidak ada log database query error pada aplikasi.
  3. Skenario 3 — Query dengan Parameter undefined (Normalisasi ke null & Perekaman Warning):

    • Pre-kondisi: Server berjalan dan terhubung ke Redis.
    • Trigger / Request: Request endpoint warta kapal yang memanggil getManifestInaportContainer tanpa menyertakan ID bongkar.
    • Ekspektasi Response: HTTP 200 OK (mengembalikan array kosong []), tidak terjadi crash runtime 500.
    • Verifikasi Database / Log: Terbit log peringatan WARN: Query warning: undefined parameter detected at position $1 and converted to NULL.
  4. Skenario 4 — Monitoring & Pembersihan Buffer Query Warnings (Admin Only):

    • Pre-kondisi: Token admin valid tersedia.
    • Trigger / Request 1: GET /health/query-warnings
    • Ekspektasi Response 1: HTTP 200 OK, memuat daftar objek warning beserta source_caller, param_index, dan query_sample.
    • Trigger / Request 2: DELETE /health/query-warnings
    • Ekspektasi Response 2: HTTP 200 OK, cleared: true.
Bukti Response Verifikasi Staging (Diisi saat UAT) ​
json
{
  "status": "PENDING",
  "note": "Menunggu deployment ke environment Staging untuk eksekusi skenario uji QA."
}

Single Source of Truth (SSOT) Arsitektur & Perubahan Teknis Backend