1944 schema migration pkgdb #2414

Merged
mfreeman451 merged 10 commits from refs/pull/2414/head into main 2025-11-16 06:24:20 +00:00
mfreeman451 commented 2025-11-16 05:40:14 +00:00 (Migrated from github.com)
Owner

Imported from GitHub pull request.

Original GitHub pull request: #1945
Original author: @mfreeman451
Original URL: https://github.com/carverauto/serviceradar/pull/1945
Original created: 2025-11-16T05:40:14Z
Original updated: 2025-11-16T06:25:01Z
Original head: carverauto/serviceradar:1944-schema-migration-pkgdb
Original base: main
Original merged: 2025-11-16T06:24:20Z by @mfreeman451

User description

IMPORTANT: Please sign the Developer Certificate of Origin

Thank you for your contribution to ServiceRadar. Please note, when contributing, the developer must include
a DCO sign-off statement indicating the DCO acceptance in one commit message. Here
is an example DCO Signed-off-by line in a commit message:

Signed-off-by: J. Doe <j.doe@domain.com>

Describe your changes

Code checklist before requesting a review

  • I have signed the DCO?
  • The build completes without errors?
  • All tests are passing when running make test?

PR Type

Enhancement


Description

  • Complete migration from Proton to CloudNativePG (CNPG) PostgreSQL backend across the entire codebase

  • Replaced Proton driver (proton-go-driver) with pgx/pgxpool for PostgreSQL connectivity

  • Refactored database layer with new CNPG-specific implementations for metrics, registry, events, discovery, and device operations

  • Introduced CompatConn wrapper to emulate Proton batch API on top of PostgreSQL

  • Migrated all metric storage and retrieval operations to use CNPG batch inserts and parameterized queries

  • Updated service registry with CNPG read/write paths including upsert operations for pollers, agents, and checkers

  • Simplified database configuration model from ProtonSettings/ProtonDatabase to CNPGDatabase with PostgreSQL connection parameters

  • Added new CNPG migration tool (cnpg-migrate) for schema setup and initialization

  • Refactored event writer consumer to use CNPG backend with generic processOTELTable helper

  • Implemented identifier resolution with CNPG fallback logic for Armis IDs, Netbox IDs, MACs, and IPs

  • Added comprehensive test infrastructure for CNPG query operations

  • Removed 813+ lines of Proton-specific batch processing and query logic

  • Deleted legacy Proton migration files and configuration


Diagram Walkthrough

flowchart LR
  Proton["Proton Driver<br/>proton-go-driver"]
  CNPG["CloudNativePG<br/>pgx/pgxpool"]
  CompatConn["CompatConn Wrapper<br/>Batch API Emulation"]
  Metrics["Metrics Operations<br/>cnpg_metrics.go"]
  Registry["Registry Operations<br/>cnpg_registry.go"]
  Events["Events Operations<br/>events.go"]
  Discovery["Discovery Operations<br/>cnpg_discovery.go"]
  Devices["Device Operations<br/>cnpg_unified_devices.go"]
  
  Proton -- "Replace with" --> CNPG
  CNPG -- "Wrapped by" --> CompatConn
  CompatConn -- "Powers" --> Metrics
  CompatConn -- "Powers" --> Registry
  CompatConn -- "Powers" --> Events
  CompatConn -- "Powers" --> Discovery
  CompatConn -- "Powers" --> Devices

File Walkthrough

Relevant files
Enhancement
18 files
metrics.go
Migrate metrics module from Proton to CNPG backend             

pkg/db/metrics.go

  • Removed Apache license header and unused imports (errors, sort,
    strings, proton driver)
  • Refactored metric storage functions to delegate to CNPG helper methods
    (cnpgInsertTimeseriesMetrics, cnpgInsertCPUMetrics, etc.)
  • Simplified storeRperfMetrics to build metric series and call CNPG
    insert instead of direct batch operations
  • Replaced all metric read functions to call corresponding CNPG query
    helpers (cnpgGetCPUMetrics, cnpgGetAllDiskMetrics, etc.)
  • Removed large blocks of query logic and batch handling code,
    consolidating into thin wrapper functions
+119/-1198
db.go
Replace Proton database driver with CloudNativePG/PostgreSQL

pkg/db/db.go

  • Replaced Proton driver imports with pgx/pgxpool
    (CloudNativePG/PostgreSQL)
  • Removed TLS configuration functions and Proton-specific connection
    setup
  • Refactored DB struct to use pgxpool.Pool instead of Proton connection
  • Introduced CompatConn wrapper to emulate Proton batch API on top of
    CNPG
  • Added placeholder rewriting logic to convert Proton ? placeholders to
    PostgreSQL $n format
  • Simplified database initialization and removed write buffer/streaming
    connection logic
  • Replaced ExecuteQuery to use pgx rows and normalize CNPG values
+266/-521
service_registry_queries.go
Add CNPG read path support to service registry queries     

pkg/registry/service_registry_queries.go

  • Added CNPG read path checks in query methods (GetPoller, GetAgent,
    GetChecker, ListPollers, etc.)
  • Extracted metadata decoding into decodeServiceMetadata helper function
  • Added CNPG-specific query implementations (getPollerCNPG,
    getAgentCNPG, listPollersCNPG, etc.)
  • Updated UpdateServiceStatus to call CNPG upsert methods after Proton
    writes
  • Added isKnownPollerCNPG and refreshPollerCacheCNPG for CNPG-backed
    caching
  • Refactored status update logic to set status before marshaling and use
    dedicated CNPG upsert functions
+609/-84
processor.go
Schema migration from Proton to CNPG database backend       

pkg/consumers/db-event-writer/processor.go

  • Migrated from Proton driver to CNPG (PostgreSQL) database backend by
    replacing proton.Conn with *db.DB
  • Removed Proton-specific row type definitions (LogRow, MetricsRow,
    TracesRow) and replaced with models.OTELLogRow, models.OTELMetricRow,
    models.OTELTraceRow
  • Refactored batch processing functions to use generic processOTELTable
    helper function, eliminating code duplication
  • Updated all database insert operations to use new CNPG methods
    (InsertEvents, InsertOTELLogs, InsertOTELMetrics, InsertOTELTraces)
+130/-320
cnpg_metrics_reads.go
CNPG metrics read operations implementation                           

pkg/db/cnpg_metrics_reads.go

  • New file implementing CNPG read operations for timeseries, CPU, disk,
    memory, and process metrics
  • Provides query functions for metric retrieval with filtering, time
    range support, and device-based queries
  • Includes helper functions for column sanitization, filter clause
    building, and metric row scanning
  • Supports chunked queries for large device ID lists to manage query
    complexity
+888/-0 
edge_onboarding.go
Edge onboarding schema migration to CNPG PostgreSQL           

pkg/db/edge_onboarding.go

  • Migrated from Proton driver to CNPG (PostgreSQL) by replacing
    proton-go-driver imports with pgx/v5
  • Simplified query building from complex arg_max aggregations to
    standard PostgreSQL DISTINCT ON pattern
  • Refactored ListEdgeOnboardingPackages to use parameterized queries
    with proper argument indexing
  • Updated scanEdgeOnboardingPackage to work with PostgreSQL row scanning
    instead of Proton-specific interface
+174/-362
service_registry.go
Service registry CNPG write operations integration             

pkg/registry/service_registry.go

  • Added CNPG write support with SQL constants for upsert operations on
    pollers, agents, and checkers tables
  • Introduced helper methods for CNPG client detection and conditional
    write execution
  • Refactored registration and heartbeat methods to use new
    upsertCNPGPoller, upsertCNPGAgent, upsertCNPGChecker functions
  • Added deleteServiceCNPG method to handle service deletion in CNPG
    backend
+385/-213
cnpg_metrics.go
CNPG metrics write operations implementation                         

pkg/db/cnpg_metrics.go

  • New file implementing CNPG write operations for timeseries, CPU, disk,
    memory, and process metrics
  • Provides batch insert functions with argument builders for each metric
    type
  • Includes timestamp sanitization and JSON normalization utilities
  • Uses pgx.Batch for efficient bulk inserts with error handling
+503/-0 
events.go
CNPG CloudEvents insertion implementation                               

pkg/db/events.go

  • New file implementing CNPG-backed CloudEvents insertion with upsert
    semantics
  • Handles batch insertion of event rows with automatic timestamp
    defaulting
  • Uses PostgreSQL ON CONFLICT clause for idempotent event storage
+89/-0   
pollers.go
Migrate poller queries from Proton to CNPG delegation       

pkg/db/pollers.go

  • Refactored poller operations to delegate to CNPG-specific
    implementations via wrapper functions
  • Removed direct Proton driver queries and batch operations
  • Added input validation for pollerID and limit parameters
  • Simplified public API methods to call corresponding cnpg* helper
    functions
+46/-470
cnpg_registry.go
New CNPG registry implementation for poller/service data 

pkg/db/cnpg_registry.go

  • New file implementing CNPG-specific poller and service registry
    operations
  • Added SQL constants for upsert/insert operations on pollers,
    poller_history, service_status, and services tables
  • Implemented functions to build argument lists and execute batch
    queries against PostgreSQL
  • Added helper functions for timestamp sanitization and JSON
    normalization
+565/-0 
unified_devices.go
Simplify unified devices to CNPG delegation layer               

pkg/db/unified_devices.go

  • Removed 467 lines of Proton-specific device query logic and helper
    utilities
  • Simplified all public methods to delegate to cnpg* implementations
  • Removed batch processing, chunking, and deduplication logic from this
    file
  • Removed copyright header and complex query building code
+13/-436
discovery.go
Simplify discovery publishing to CNPG delegation                 

pkg/db/discovery.go

  • Removed 346 lines of Proton batch insert logic for interfaces and
    topology events
  • Simplified PublishDiscoveredInterface and
    PublishTopologyDiscoveryEvent to delegate to CNPG functions
  • Removed metadata marshaling and batch timeout handling code
  • Removed copyright header and complex batch preparation logic
+13/-324
cnpg_unified_devices.go
New CNPG unified devices implementation with scanning       

pkg/db/cnpg_unified_devices.go

  • New file implementing CNPG-specific unified device queries and
    operations
  • Added functions to scan and gather device records from PostgreSQL with
    proper null handling
  • Implemented device deduplication and sorting by last_seen timestamp
  • Added support for device updates with metadata JSON serialization
+369/-0 
cnpg_sweep.go
New CNPG sweep host state operations                                         

pkg/db/cnpg_sweep.go

  • New file implementing CNPG sweep host state operations
  • Added SQL constant for inserting sweep host states with comprehensive
    field mapping
  • Implemented argument builders for sweep states with JSON field
    normalization
  • Added query functions to retrieve sweep host states with proper null
    type handling
+338/-0 
main.go
New CNPG migration command-line tool                                         

cmd/tools/cnpg-migrate/main.go

  • New migration tool for applying CNPG schema migrations
  • Implements flag parsing for database connection parameters (host,
    port, database, credentials)
  • Supports TLS configuration and runtime parameters
  • Calls db.RunCNPGMigrations to execute schema setup
+264/-0 
registry.go
Add CNPG identifier resolution with fallback logic             

pkg/registry/registry.go

  • Added cnpgRegistryClient interface for CNPG read operations
  • Implemented CNPG-specific identifier resolution functions
    (resolveArmisIDsCNPG, resolveNetboxIDsCNPG, resolveMACsCNPG,
    resolveIPsToCanonicalCNPG)
  • Added chunked query execution with configurable chunk size
    (cnpgIdentifierChunkSize)
  • Updated existing resolver functions to check for CNPG availability and
    delegate accordingly
+182/-1 
cnpg_edge_onboarding.go
New CNPG edge onboarding package operations                           

pkg/db/cnpg_edge_onboarding.go

  • New file implementing CNPG edge onboarding package and event
    operations
  • Added SQL constants for upserting edge packages and inserting events
  • Implemented argument builders with UUID validation and JSON
    normalization
  • Added helper functions for nullable time and string conversion
+244/-0 
Configuration changes
1 files
db.go
Update database configuration model for CNPG                         

pkg/models/db.go

  • Removed ProtonSettings struct with Proton-specific configuration
    options
  • Replaced ProtonDatabase struct with CNPGDatabase struct
  • Added PostgreSQL/Timescale connection parameters (host, port,
    database, username, password)
  • Added connection pool settings (max_connections, min_connections,
    max_conn_lifetime)
  • Added timeout and health check configuration (statement_timeout,
    health_check_period)
  • Added support for SSL mode and extra runtime parameters
+17/-18 
Tests
1 files
service_registry_queries_test.go
Add CNPG query testing infrastructure and tests                   

pkg/registry/service_registry_queries_test.go

  • Added test infrastructure with stub row implementations for CNPG query
    testing
  • Implemented testCNPGClient and stubRows types to mock database
    responses
  • Added TestGetPollerCNPG and TestListPollersCNPGFilters test cases
  • Added error types for stub row scanning failures
+240/-0 
Additional files
101 files
MODULE.bazel +29/-11 
Makefile +10/-0   
main.go +3/-12   
main.go +1/-17   
BUILD.bazel +20/-0   
docker-compose.yml +4/-2     
db-event-writer.docker.json +15/-20 
entrypoint-db-event-writer.sh +27/-22 
tools-entrypoint.sh +5/-0     
tools-profile.sh +39/-0   
BUILD.bazel +97/-1   
agents.md +247/-121
architecture.md +6/-6     
cluster.md +4/-4     
otel.md +5/-3     
docker-compose-login-500.md +35/-27 
sysmonvm-e2e.md +2/-2     
service-port-map.md +3/-3     
snmp.md +2/-2     
syslog.md +5/-5     
go.mod +4/-4     
go.sum +10/-53 
serviceradar-config.yaml +261/-156
core.yaml +18/-10 
db-event-writer-config.yaml +13/-16 
db-event-writer.yaml +10/-6   
DEPLOYMENT.md +50/-54 
README.md +71/-51 
Dockerfile.serviceradar-tools +2/-3     
cert-scripts-configmap.yaml +1/-2     
configmap.yaml +1254/-1511
kustomization.yaml +3/-2     
secret-generator-job.yaml +1/-5     
secrets.yaml +0/-1     
serviceradar-agent.yaml +2/-2     
serviceradar-core.yaml +28/-13 
serviceradar-datasvc.yaml +1/-1     
serviceradar-db-event-writer-config.yaml +14/-19 
serviceradar-db-event-writer.yaml +16/-6   
serviceradar-faker.yaml +2/-2     
serviceradar-flowgger.yaml +1/-1     
serviceradar-kong.yaml +2/-4     
serviceradar-mapper.yaml +2/-2     
serviceradar-otel.yaml +1/-1     
serviceradar-poller.yaml +2/-2     
serviceradar-proton.yaml +0/-139 
serviceradar-rperf-checker.yaml +1/-1     
serviceradar-snmp-checker.yaml +1/-1     
serviceradar-srql.yaml +0/-99   
serviceradar-sync.yaml +2/-2     
serviceradar-tools.yaml +118/-32
serviceradar-trapd.yaml +1/-1     
serviceradar-zen.yaml +1/-1     
server-configmap.yaml +1/-1     
server-statefulset.yaml +10/-0   
spire-clusterspiffeid-core.yaml +2/-2     
spire-clusterspiffeid-datasvc.yaml +2/-2     
spire-clusterspiffeid-db-event-writer.yaml +2/-2     
spire-clusterspiffeid-flowgger.yaml +2/-2     
spire-clusterspiffeid-mapper.yaml +2/-2     
spire-clusterspiffeid-poller.yaml +2/-2     
spire-clusterspiffeid-rperf-checker.yaml +2/-2     
spire-clusterspiffeid-serviceradar-agent.yaml +2/-2     
spire-clusterspiffeid-snmp-checker.yaml +2/-2     
spire-clusterspiffeid-sync.yaml +2/-2     
spire-clusterspiffeid-trapd.yaml +2/-2     
spire-clusterspiffeid-zen.yaml +2/-2     
var-references.yaml +33/-0   
deploy.sh +53/-912
kustomization.yaml +29/-0   
service-aliases.demo.yaml +0/-10   
var-references.yaml +33/-0   
resize-cnpg-pvc.sh +22/-0   
resize-proton-pvc.sh +0/-70   
db-event-writer-config.yaml +12/-17 
ingress.yaml +97/-6   
kustomization.yaml +59/-13 
namespace-transformer.yaml +8/-0     
namespace.yaml +3/-3     
service-aliases.demo-staging.yaml +125/-0 
serviceradar-core-grpc-external.yaml +22/-0   
serviceradar-datasvc-grpc-external.yaml +18/-0   
serviceradar-flowgger-external.yaml +22/-0   
spire-controller-manager-configmap.yaml +28/-0   
spire-server-configmap.yaml +69/-0   
spire-server-external.yaml +17/-0   
var-references.yaml +33/-0   
validate.sh +17/-3   
proposal.md +26/-0   
spec.md +34/-0   
tasks.md +13/-0   
proposal.md [link]   
spec.md [link]   
tasks.md [link]   
proposal.md +30/-0   
schema-mapping.md +90/-0   
spec.md +54/-0   
tasks.md +31/-0   
spec.md +44/-0   
components.json +0/-2     
Additional files not shown

Imported from GitHub pull request. Original GitHub pull request: #1945 Original author: @mfreeman451 Original URL: https://github.com/carverauto/serviceradar/pull/1945 Original created: 2025-11-16T05:40:14Z Original updated: 2025-11-16T06:25:01Z Original head: carverauto/serviceradar:1944-schema-migration-pkgdb Original base: main Original merged: 2025-11-16T06:24:20Z by @mfreeman451 --- ### **User description** ## IMPORTANT: Please sign the Developer Certificate of Origin Thank you for your contribution to ServiceRadar. Please note, when contributing, the developer must include a [DCO sign-off statement]( https://developercertificate.org/) indicating the DCO acceptance in one commit message. Here is an example DCO Signed-off-by line in a commit message: ``` Signed-off-by: J. Doe <j.doe@domain.com> ``` ## Describe your changes ## Issue ticket number and link ## Code checklist before requesting a review - [ ] I have signed the DCO? - [ ] The build completes without errors? - [ ] All tests are passing when running make test? ___ ### **PR Type** Enhancement ___ ### **Description** - **Complete migration from Proton to CloudNativePG (CNPG) PostgreSQL backend** across the entire codebase - Replaced Proton driver (`proton-go-driver`) with `pgx/pgxpool` for PostgreSQL connectivity - Refactored database layer with new CNPG-specific implementations for metrics, registry, events, discovery, and device operations - Introduced `CompatConn` wrapper to emulate Proton batch API on top of PostgreSQL - Migrated all metric storage and retrieval operations to use CNPG batch inserts and parameterized queries - Updated service registry with CNPG read/write paths including upsert operations for pollers, agents, and checkers - Simplified database configuration model from `ProtonSettings`/`ProtonDatabase` to `CNPGDatabase` with PostgreSQL connection parameters - Added new CNPG migration tool (`cnpg-migrate`) for schema setup and initialization - Refactored event writer consumer to use CNPG backend with generic `processOTELTable` helper - Implemented identifier resolution with CNPG fallback logic for Armis IDs, Netbox IDs, MACs, and IPs - Added comprehensive test infrastructure for CNPG query operations - Removed 813+ lines of Proton-specific batch processing and query logic - Deleted legacy Proton migration files and configuration ___ ### Diagram Walkthrough ```mermaid flowchart LR Proton["Proton Driver<br/>proton-go-driver"] CNPG["CloudNativePG<br/>pgx/pgxpool"] CompatConn["CompatConn Wrapper<br/>Batch API Emulation"] Metrics["Metrics Operations<br/>cnpg_metrics.go"] Registry["Registry Operations<br/>cnpg_registry.go"] Events["Events Operations<br/>events.go"] Discovery["Discovery Operations<br/>cnpg_discovery.go"] Devices["Device Operations<br/>cnpg_unified_devices.go"] Proton -- "Replace with" --> CNPG CNPG -- "Wrapped by" --> CompatConn CompatConn -- "Powers" --> Metrics CompatConn -- "Powers" --> Registry CompatConn -- "Powers" --> Events CompatConn -- "Powers" --> Discovery CompatConn -- "Powers" --> Devices ``` <details> <summary><h3> File Walkthrough</h3></summary> <table><thead><tr><th></th><th align="left">Relevant files</th></tr></thead><tbody><tr><td><strong>Enhancement</strong></td><td><details><summary>18 files</summary><table> <tr> <td> <details> <summary><strong>metrics.go</strong><dd><code>Migrate metrics module from Proton to CNPG backend</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/metrics.go <ul><li>Removed Apache license header and unused imports (errors, sort, <br>strings, proton driver)<br> <li> Refactored metric storage functions to delegate to CNPG helper methods <br>(<code>cnpgInsertTimeseriesMetrics</code>, <code>cnpgInsertCPUMetrics</code>, etc.)<br> <li> Simplified <code>storeRperfMetrics</code> to build metric series and call CNPG <br>insert instead of direct batch operations<br> <li> Replaced all metric read functions to call corresponding CNPG query <br>helpers (<code>cnpgGetCPUMetrics</code>, <code>cnpgGetAllDiskMetrics</code>, etc.)<br> <li> Removed large blocks of query logic and batch handling code, <br>consolidating into thin wrapper functions</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-2cf937853ac86eb9a879bd370cbe22b835ee0c48be6771b48b0276d93cc2ae93">+119/-1198</a></td> </tr> <tr> <td> <details> <summary><strong>db.go</strong><dd><code>Replace Proton database driver with CloudNativePG/PostgreSQL</code></dd></summary> <hr> pkg/db/db.go <ul><li>Replaced Proton driver imports with pgx/pgxpool <br>(CloudNativePG/PostgreSQL)<br> <li> Removed TLS configuration functions and Proton-specific connection <br>setup<br> <li> Refactored <code>DB</code> struct to use <code>pgxpool.Pool</code> instead of Proton connection<br> <li> Introduced <code>CompatConn</code> wrapper to emulate Proton batch API on top of <br>CNPG<br> <li> Added placeholder rewriting logic to convert Proton <code>?</code> placeholders to <br>PostgreSQL <code>$n</code> format<br> <li> Simplified database initialization and removed write buffer/streaming <br>connection logic<br> <li> Replaced <code>ExecuteQuery</code> to use pgx rows and normalize CNPG values</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-5da3684806835246d262230050593f460b12b6c0e3966df174e6061be0e9e575">+266/-521</a></td> </tr> <tr> <td> <details> <summary><strong>service_registry_queries.go</strong><dd><code>Add CNPG read path support to service registry queries</code>&nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/registry/service_registry_queries.go <ul><li>Added CNPG read path checks in query methods (<code>GetPoller</code>, <code>GetAgent</code>, <br><code>GetChecker</code>, <code>ListPollers</code>, etc.)<br> <li> Extracted metadata decoding into <code>decodeServiceMetadata</code> helper function<br> <li> Added CNPG-specific query implementations (<code>getPollerCNPG</code>, <br><code>getAgentCNPG</code>, <code>listPollersCNPG</code>, etc.)<br> <li> Updated <code>UpdateServiceStatus</code> to call CNPG upsert methods after Proton <br>writes<br> <li> Added <code>isKnownPollerCNPG</code> and <code>refreshPollerCacheCNPG</code> for CNPG-backed <br>caching<br> <li> Refactored status update logic to set status before marshaling and use <br>dedicated CNPG upsert functions</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-3591e8646384be68811f862b986342253cabc23176c6f0e09996453baf88a2e9">+609/-84</a></td> </tr> <tr> <td> <details> <summary><strong>processor.go</strong><dd><code>Schema migration from Proton to CNPG database backend</code>&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/consumers/db-event-writer/processor.go <ul><li>Migrated from Proton driver to CNPG (PostgreSQL) database backend by <br>replacing <code>proton.Conn</code> with <code>*db.DB</code><br> <li> Removed Proton-specific row type definitions (<code>LogRow</code>, <code>MetricsRow</code>, <br><code>TracesRow</code>) and replaced with <code>models.OTELLogRow</code>, <code>models.OTELMetricRow</code>, <br><code>models.OTELTraceRow</code><br> <li> Refactored batch processing functions to use generic <code>processOTELTable</code> <br>helper function, eliminating code duplication<br> <li> Updated all database insert operations to use new CNPG methods <br>(<code>InsertEvents</code>, <code>InsertOTELLogs</code>, <code>InsertOTELMetrics</code>, <code>InsertOTELTraces</code>)</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-c55d73b621975e3797271d69fc43b78fa44eb184437392f9e40e18d4568589a8">+130/-320</a></td> </tr> <tr> <td> <details> <summary><strong>cnpg_metrics_reads.go</strong><dd><code>CNPG metrics read operations implementation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/cnpg_metrics_reads.go <ul><li>New file implementing CNPG read operations for timeseries, CPU, disk, <br>memory, and process metrics<br> <li> Provides query functions for metric retrieval with filtering, time <br>range support, and device-based queries<br> <li> Includes helper functions for column sanitization, filter clause <br>building, and metric row scanning<br> <li> Supports chunked queries for large device ID lists to manage query <br>complexity</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-9fdfae8290eb4b1c2b242621f35349a01a507e98f337251a76ec17a6075fafe5">+888/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>edge_onboarding.go</strong><dd><code>Edge onboarding schema migration to CNPG PostgreSQL</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/edge_onboarding.go <ul><li>Migrated from Proton driver to CNPG (PostgreSQL) by replacing <br><code>proton-go-driver</code> imports with <code>pgx/v5</code><br> <li> Simplified query building from complex <code>arg_max</code> aggregations to <br>standard PostgreSQL <code>DISTINCT ON</code> pattern<br> <li> Refactored <code>ListEdgeOnboardingPackages</code> to use parameterized queries <br>with proper argument indexing<br> <li> Updated <code>scanEdgeOnboardingPackage</code> to work with PostgreSQL row scanning <br>instead of Proton-specific interface</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-bf7eeb112767dfc3f9f3cd6e377d13b7be35fe76fcf83cb0c18cc25b0761b3c6">+174/-362</a></td> </tr> <tr> <td> <details> <summary><strong>service_registry.go</strong><dd><code>Service registry CNPG write operations integration</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/registry/service_registry.go <ul><li>Added CNPG write support with SQL constants for upsert operations on <br><code>pollers</code>, <code>agents</code>, and <code>checkers</code> tables<br> <li> Introduced helper methods for CNPG client detection and conditional <br>write execution<br> <li> Refactored registration and heartbeat methods to use new <br><code>upsertCNPGPoller</code>, <code>upsertCNPGAgent</code>, <code>upsertCNPGChecker</code> functions<br> <li> Added <code>deleteServiceCNPG</code> method to handle service deletion in CNPG <br>backend</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-d6d6d09a58edde934a1d3f571ff5ce02f0475c5e85ecdec27888892aff8d9d1d">+385/-213</a></td> </tr> <tr> <td> <details> <summary><strong>cnpg_metrics.go</strong><dd><code>CNPG metrics write operations implementation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/cnpg_metrics.go <ul><li>New file implementing CNPG write operations for timeseries, CPU, disk, <br>memory, and process metrics<br> <li> Provides batch insert functions with argument builders for each metric <br>type<br> <li> Includes timestamp sanitization and JSON normalization utilities<br> <li> Uses <code>pgx.Batch</code> for efficient bulk inserts with error handling</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-8ca23b1b2b298b6eec9af9e23be2c7cb9caa65301f514c013ebbc5b52e933a23">+503/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>events.go</strong><dd><code>CNPG CloudEvents insertion implementation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/events.go <ul><li>New file implementing CNPG-backed CloudEvents insertion with upsert <br>semantics<br> <li> Handles batch insertion of event rows with automatic timestamp <br>defaulting<br> <li> Uses PostgreSQL <code>ON CONFLICT</code> clause for idempotent event storage</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-bf1824600585d423d03392632872269bf1d5f976430b55d7a9bd1238bc8de6b5">+89/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td> <details> <summary><strong>pollers.go</strong><dd><code>Migrate poller queries from Proton to CNPG delegation</code>&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/pollers.go <ul><li>Refactored poller operations to delegate to CNPG-specific <br>implementations via wrapper functions<br> <li> Removed direct Proton driver queries and batch operations<br> <li> Added input validation for <code>pollerID</code> and <code>limit</code> parameters<br> <li> Simplified public API methods to call corresponding <code>cnpg*</code> helper <br>functions</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-4e4ff6d32240a5f2e8d053438abcc4a77959d521bc99028ed2bbcf1e07145631">+46/-470</a></td> </tr> <tr> <td> <details> <summary><strong>cnpg_registry.go</strong><dd><code>New CNPG registry implementation for poller/service data</code>&nbsp; </dd></summary> <hr> pkg/db/cnpg_registry.go <ul><li>New file implementing CNPG-specific poller and service registry <br>operations<br> <li> Added SQL constants for upsert/insert operations on <code>pollers</code>, <br><code>poller_history</code>, <code>service_status</code>, and <code>services</code> tables<br> <li> Implemented functions to build argument lists and execute batch <br>queries against PostgreSQL<br> <li> Added helper functions for timestamp sanitization and JSON <br>normalization</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-e31b8b854d9ba774d2f3ed9899b1f5902462cd0e63990a2898dcaf9bc171d571">+565/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>unified_devices.go</strong><dd><code>Simplify unified devices to CNPG delegation layer</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/unified_devices.go <ul><li>Removed 467 lines of Proton-specific device query logic and helper <br>utilities<br> <li> Simplified all public methods to delegate to <code>cnpg*</code> implementations<br> <li> Removed batch processing, chunking, and deduplication logic from this <br>file<br> <li> Removed copyright header and complex query building code</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-165bffede411b9a031b4d85af0ae459a1913960593d59bfc19554cb838e39679">+13/-436</a></td> </tr> <tr> <td> <details> <summary><strong>discovery.go</strong><dd><code>Simplify discovery publishing to CNPG delegation</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/discovery.go <ul><li>Removed 346 lines of Proton batch insert logic for interfaces and <br>topology events<br> <li> Simplified <code>PublishDiscoveredInterface</code> and <br><code>PublishTopologyDiscoveryEvent</code> to delegate to CNPG functions<br> <li> Removed metadata marshaling and batch timeout handling code<br> <li> Removed copyright header and complex batch preparation logic</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-29a4ce8613152068c4493f784ec41cf340e445b6b6e7c5ddb698826f9a4f1341">+13/-324</a></td> </tr> <tr> <td> <details> <summary><strong>cnpg_unified_devices.go</strong><dd><code>New CNPG unified devices implementation with scanning</code>&nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/cnpg_unified_devices.go <ul><li>New file implementing CNPG-specific unified device queries and <br>operations<br> <li> Added functions to scan and gather device records from PostgreSQL with <br>proper null handling<br> <li> Implemented device deduplication and sorting by <code>last_seen</code> timestamp<br> <li> Added support for device updates with metadata JSON serialization</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-ab3bf557cf9bb1b281a315a73abee38748de1654941c2471542e5e9bfc1716d8">+369/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>cnpg_sweep.go</strong><dd><code>New CNPG sweep host state operations</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/cnpg_sweep.go <ul><li>New file implementing CNPG sweep host state operations<br> <li> Added SQL constant for inserting sweep host states with comprehensive <br>field mapping<br> <li> Implemented argument builders for sweep states with JSON field <br>normalization<br> <li> Added query functions to retrieve sweep host states with proper null <br>type handling</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-558271f7c16f802f25065a614cfc296ee6cb5e01d54ed14d64ae1186a565efd1">+338/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>main.go</strong><dd><code>New CNPG migration command-line tool</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> cmd/tools/cnpg-migrate/main.go <ul><li>New migration tool for applying CNPG schema migrations<br> <li> Implements flag parsing for database connection parameters (host, <br>port, database, credentials)<br> <li> Supports TLS configuration and runtime parameters<br> <li> Calls <code>db.RunCNPGMigrations</code> to execute schema setup</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-93eee0002cd036549d4ef0161788fbe4f4382eb266cc83b89517953b7448803e">+264/-0</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>registry.go</strong><dd><code>Add CNPG identifier resolution with fallback logic</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/registry/registry.go <ul><li>Added <code>cnpgRegistryClient</code> interface for CNPG read operations<br> <li> Implemented CNPG-specific identifier resolution functions <br>(<code>resolveArmisIDsCNPG</code>, <code>resolveNetboxIDsCNPG</code>, <code>resolveMACsCNPG</code>, <br><code>resolveIPsToCanonicalCNPG</code>)<br> <li> Added chunked query execution with configurable chunk size <br>(<code>cnpgIdentifierChunkSize</code>)<br> <li> Updated existing resolver functions to check for CNPG availability and <br>delegate accordingly</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-cb61d8f79451b9541de4a8cc0811523a68d15452b2f5971c7618ea5b423cf4ec">+182/-1</a>&nbsp; </td> </tr> <tr> <td> <details> <summary><strong>cnpg_edge_onboarding.go</strong><dd><code>New CNPG edge onboarding package operations</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/db/cnpg_edge_onboarding.go <ul><li>New file implementing CNPG edge onboarding package and event <br>operations<br> <li> Added SQL constants for upserting edge packages and inserting events<br> <li> Implemented argument builders with UUID validation and JSON <br>normalization<br> <li> Added helper functions for nullable time and string conversion</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-be1c4c42ef75efe08cfae943faaa88f850200c4d38b95137768ce8d723aa5ecf">+244/-0</a>&nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Configuration changes</strong></td><td><details><summary>1 files</summary><table> <tr> <td> <details> <summary><strong>db.go</strong><dd><code>Update database configuration model for CNPG</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/models/db.go <ul><li>Removed <code>ProtonSettings</code> struct with Proton-specific configuration <br>options<br> <li> Replaced <code>ProtonDatabase</code> struct with <code>CNPGDatabase</code> struct<br> <li> Added PostgreSQL/Timescale connection parameters (host, port, <br>database, username, password)<br> <li> Added connection pool settings (max_connections, min_connections, <br>max_conn_lifetime)<br> <li> Added timeout and health check configuration (statement_timeout, <br>health_check_period)<br> <li> Added support for SSL mode and extra runtime parameters</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-0ae965d146dd5aa35ea3cf7713541c1f2e4c92e1bdc339fbd84296aa625d5f5e">+17/-18</a>&nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Tests</strong></td><td><details><summary>1 files</summary><table> <tr> <td> <details> <summary><strong>service_registry_queries_test.go</strong><dd><code>Add CNPG query testing infrastructure and tests</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </dd></summary> <hr> pkg/registry/service_registry_queries_test.go <ul><li>Added test infrastructure with stub row implementations for CNPG query <br>testing<br> <li> Implemented <code>testCNPGClient</code> and <code>stubRows</code> types to mock database <br>responses<br> <li> Added <code>TestGetPollerCNPG</code> and <code>TestListPollersCNPGFilters</code> test cases<br> <li> Added error types for stub row scanning failures</ul> </details> </td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-6375daf4a77f18366326db1ac818f613eea7735f4bdc23c886274edc58826656">+240/-0</a>&nbsp; </td> </tr> </table></details></td></tr><tr><td><strong>Additional files</strong></td><td><details><summary>101 files</summary><table> <tr> <td><strong>MODULE.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-6136fc12446089c3db7360e923203dd114b6a1466252e71667c6791c20fe6bdc">+29/-11</a>&nbsp; </td> </tr> <tr> <td><strong>Makefile</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-76ed074a9305c04054cdebb9e9aad2d818052b07091de1f20cad0bbac34ffb52">+10/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>main.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-c9a73828b631e4618af51a47bc4c618d72ad1726fef3c3cbe12ab73b57b0eb63">+3/-12</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>main.go</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-7e1a99c83e5e5c2d5deec445135861c820d0aeddb2d3b5365e24b4c3b6955f3a">+1/-17</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-0d373909abab60bd29fd250496c1fd02684991636edc7d4bb1b8ace068ff70a9">+20/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>docker-compose.yml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-e45e45baeda1c1e73482975a664062aa56f20c03dd9d64a827aba57775bed0d3">+4/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>db-event-writer.docker.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-9fc51271f7ef5bb460160013e24e44e829b730656891d26fc49d5fe72fbb3147">+15/-20</a>&nbsp; </td> </tr> <tr> <td><strong>entrypoint-db-event-writer.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-a76a07ca0b18c5d7d9cf0ba3f1a3f9330307be7acd0ca3d7a6be7b67c84f81af">+27/-22</a>&nbsp; </td> </tr> <tr> <td><strong>tools-entrypoint.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-99e215d6b8a24d9891d6eee08e7cbf933c7e0622188f995066afda3bd5b84a10">+5/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>tools-profile.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-f47597e2f5d4d085d8bf109109608f8ec0b7db8e90545e869b9ae409b607a4ac">+39/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>BUILD.bazel</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-0e4db31c224a8f72ae8e870a849e38a59d74a2c7f7b04347b0b3eb07e20c5a80">+97/-1</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>agents.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-af8d04277f2353629065b0cc5fad3e44bd3e7c20339bd125e0812104bdbeff28">+247/-121</a></td> </tr> <tr> <td><strong>architecture.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-90abd06467420fd89391fd1a4d75ceb1f6a9381de4d13a95fffe606abff38d37">+6/-6</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>cluster.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-0d2e32a530d31d08b311213cd036c14e611d2f9a31e9cbcaa97f02b53edeeb44">+4/-4</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>otel.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-498eac46d1b44ca23346a9ef5edc4f38030fe37ce7313e691a0644e9deb381f4">+5/-3</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>docker-compose-login-500.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-d3ec1ba25f119cff4efcb5767a1afe923b92fd5727453c6276030bc8e9923f05">+35/-27</a>&nbsp; </td> </tr> <tr> <td><strong>sysmonvm-e2e.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-43e6004242d0658c1e3f03e280f6b40e67df1d60345387a6a6863ef341b5ceb1">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>service-port-map.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-81eabc52b9d300cbb6414b5f1f55cecb85adfe891bdb58cf138ce4dc115c48ee">+3/-3</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>snmp.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-5d2a72888517a4c731287280253a29cedbcbe595f1ce57c2fa14aef37c62595a">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>syslog.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-8efe898141f64a74b80133318b56ed95d03703ac95b47cebc95978b7ef761a4a">+5/-5</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>go.mod</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-33ef32bf6c23acb95f5902d7097b7a1d5128ca061167ec0716715b0b9eeaa5f6">+4/-4</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>go.sum</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-3295df7234525439d778f1b282d146a4f1ff6b415248aaac074e8042d9f42d63">+10/-53</a>&nbsp; </td> </tr> <tr> <td><strong>serviceradar-config.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-b8c8d2484103b11c396bc60d290c81df63c30a0f81103eceb5852a17e1d2b5e3">+261/-156</a></td> </tr> <tr> <td><strong>core.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-06ab387d2c169d82a1de28b5e66c86f0417bd81b82a96246d0a2da8bfaa8d224">+18/-10</a>&nbsp; </td> </tr> <tr> <td><strong>db-event-writer-config.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-4cce0ab31bec3428ffae6701d20ca14b0f27a1e8a810ba1c7388e5c7860c3254">+13/-16</a>&nbsp; </td> </tr> <tr> <td><strong>db-event-writer.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-e4f899d11e5720f7049aa6fd632bd6993739410051bf65bc6fc8469739e5d2e4">+10/-6</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>DEPLOYMENT.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-5f3fed1076fa76a981a6d79e8047dfdb15c6f53c62790064823bb9fd3548c315">+50/-54</a>&nbsp; </td> </tr> <tr> <td><strong>README.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-de06932c4108090378d4fb67c46fbeda85c5540f131cdafe7acee0a71466f4dc">+71/-51</a>&nbsp; </td> </tr> <tr> <td><strong>Dockerfile.serviceradar-tools</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-128fc43583a5bebffb342640261b5df8795456047930dedab3ced4d0a8769b61">+2/-3</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>cert-scripts-configmap.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-6cfdb89093e1dc013b04ac606d8bfd64e3079419d3b6c94c3eed6b12531bafbb">+1/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>configmap.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-f4548beaa0a3a01a46971c82c5647a0f3f49eb38d66dd939d06d19018173fcd6">+1254/-1511</a></td> </tr> <tr> <td><strong>kustomization.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-c4260176971b950ef1b967a2631b446225071906172f56287c465ad2e29788d9">+3/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>secret-generator-job.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-b73cf77d0215c7f4ed9d500237d690a1dcb8fc821b6410854373756654dce1f6">+1/-5</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>secrets.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-606d5c27a32fb978e98f7041e6dc6d30c013cec8052b8f48cf2f890c882b2022">+0/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-agent.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-750aaa803a43f0993450026e4174b8a7d20fe016b9ff726f154a77a4f0fb4e19">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-core.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-2f484d8fe3bae65aace437568f6dd660c92f57b452f7bd1608083a8fe3716ba3">+28/-13</a>&nbsp; </td> </tr> <tr> <td><strong>serviceradar-datasvc.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-eea03fac4c944d749f8aaf56f14f9d69cb1fb4258c938df3941985f1a0973606">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-db-event-writer-config.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-57ddd4cb03c5c669ff6dc5b3b60334e9205d01ae95a2b5316c147ed2114ebe49">+14/-19</a>&nbsp; </td> </tr> <tr> <td><strong>serviceradar-db-event-writer.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-b198b2f2d63cd46b28ae65eb30f77e483c3218b91a913ae7e19b6d515e0e6eda">+16/-6</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-faker.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-3a79bd11240a5d69063fcf7c53d01bfb716bb62e82d8ab252ab8196e32ca3b8d">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-flowgger.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-cb9955fe738eef278e02d4991fd265f3ff5f8d57f30fc7b6d6c1b70ce7ad5e51">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-kong.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-e1426b570f5caae8eee31f02984392f1bc68d0c329ae2f1118de3272d654856e">+2/-4</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-mapper.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-7752e6facd4363988ce71d1f5722cee4f935a2437f9723a8665c32ef9ad2c1b4">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-otel.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-6e48b92ef0f34e2aa982af24815d20bcc358fbd9c416480be0b59eca6e5a944f">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-poller.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-20492d7f5153e92f95cbbf2f62fb75b1a43f530304372a5e7731fdf95b583f3b">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-proton.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-d8814c9cdfa0f83548605468ce867dc550b5d51a4afdc46074f91420d1218686">+0/-139</a>&nbsp; </td> </tr> <tr> <td><strong>serviceradar-rperf-checker.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-5c1d31adae9b9bf40422fbdbb58c1252451b3f4f1d0a50eaf0117c8813245e9c">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-snmp-checker.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-384bd9ba241a5f786e439012255b11a15477ac5cde08fcd5c9565f84e4410b22">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-srql.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-671bb3bfd8bad4c29730ae3c96b9b78e1d446fa378742bd2091b1edad63fec87">+0/-99</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-sync.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-8b12e23e4eec411255c9ae947b353c680f5ff75a4e0891bc14e2db88d9e6b778">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-tools.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-89330d07cf9cc5953c8f6a96c9698450d59baceeeff2aa265b0e92c1b3c21852">+118/-32</a></td> </tr> <tr> <td><strong>serviceradar-trapd.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-77fed92f7cfe061af7713a55133552c64dcf325505e21e8d2c07ad34c6c17aec">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-zen.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-fcf1ab4f9246f3bbca02fd72895fa4968e70abaa8a5075873381d29736db8df1">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>server-configmap.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-fddd686470ef8dfa3150ef3cb10d20a5eb67c2352eb7d630a89431b1699a5c56">+1/-1</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>server-statefulset.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-ec0e246a770d0e9b28d6b6b39db5fd05e1d767b2da456ecd2d9e13bba59da7db">+10/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-clusterspiffeid-core.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-e55c65f79cefe84f9d0a7d1a24d6f970bd7c74e2852ae2d46f00ca32d4b6c019">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-clusterspiffeid-datasvc.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-9187091d212f3a15e14987956a5f4088ff5b1b4975d84de5f034f9e75568f10b">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-clusterspiffeid-db-event-writer.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-81d7a625f62557d60d50581c0f4b3f2cfc7b243c3c2a202bd6be75944d3bcb9e">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-clusterspiffeid-flowgger.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-292d4939e080bfcf799b4bd0f93e993a9a1fe2d00cc0a787fe0906bdb0448b89">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-clusterspiffeid-mapper.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-104a88f7e17357ce7ffd914014055dd0aa1b92c1bb8365a00932ee9be32f60f2">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-clusterspiffeid-poller.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-d5bbd0fe6d738753656b56e296007f58d7c5088ceeeef11ed5d26df7e255a232">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-clusterspiffeid-rperf-checker.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-eb699366fabc5e8718aad28acfcb6fddc8597a8dde546934acc365b59b941b30">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-clusterspiffeid-serviceradar-agent.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-496a23d35ce8959ea042bc66c98814f5ac3e14648fadfbd4c8a7a32748e556d8">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-clusterspiffeid-snmp-checker.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-5b31327145f7d255907e5ac483581775830f684c0b213566c54f36b728f9658f">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-clusterspiffeid-sync.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-a42b409971587dcf3ca8ba418584132f193d2b88a64240a32e59012f2da4e816">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-clusterspiffeid-trapd.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-fe4a5fb5f61abc8327efbe5b496a309ad8318ad0101484ef486ff45d94ef5efd">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-clusterspiffeid-zen.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-97dcd30288c4aea95a647156fdc4d53b8ba8bea4fb7a577b42b26e5058d24f04">+2/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>var-references.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-58457dd02cd2c7e9b6107097dddb78b67198f1bc3cb2f94d0bb3f6d3bf8252cb">+33/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>deploy.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-e37a2eb47f6488f6f391d56a7376be0ca4f93afa355028a71dd0608d3ef1a8ba">+53/-912</a></td> </tr> <tr> <td><strong>kustomization.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-0527e7f19d087f3576d5755a79554797ffbab78b1a7efaa38984b4f3241f6fc9">+29/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>service-aliases.demo.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-2a652ee3803d2decc7bf62439ee5712d719b72ebbf146d2a02093d4c06638f13">+0/-10</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>var-references.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-b4cf0d23fbb8f71f7cdcbd4c79e13ecca422d5fb28d2ceb756cf20191d5a391c">+33/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>resize-cnpg-pvc.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-cb10107a5a7c47a80ae0e2ebb4ff83c4952a21872caa59eaec1caf71ecf0df8a">+22/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>resize-proton-pvc.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-06d3c79156e94efcd90370b6a9adedceb1a733e62960d4f0bd37cabdcc8685c4">+0/-70</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>db-event-writer-config.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-ea076afd9c6db6e374af49b43e6f111b53a4e111c9ce23db3382342b26ec6737">+12/-17</a>&nbsp; </td> </tr> <tr> <td><strong>ingress.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-b73659cd220fe91eaf5c98cfc0844d203477e1a985d06a87ecd16e79c3ec0ca9">+97/-6</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>kustomization.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-ae7d8d4134a595a9d278924988f58e1843ad4d5d24b4df3b2c976dd3610a1b64">+59/-13</a>&nbsp; </td> </tr> <tr> <td><strong>namespace-transformer.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-e6731b7da73fcba4103de909587c7b8211f0c2bfba0a5252c2d3f0af7da74b82">+8/-0</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>namespace.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-f7763a9ad3cfd4a070f6dc391828318b8aac17b446fa6b867b6ba517ded8274a">+3/-3</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>service-aliases.demo-staging.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-f413bc02fc71777fbad597ecdc5b26a6a2008d1d72915cdd212abdcedcc32bc9">+125/-0</a>&nbsp; </td> </tr> <tr> <td><strong>serviceradar-core-grpc-external.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-f51adfb8139cb35e63c15d12bc01dbd1083c5fb364ec73c0ba3ee094920209cf">+22/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-datasvc-grpc-external.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-58871f36f9c4587dd3cc9b6f594ea78df2cbd31afbceeede17396d0870beee31">+18/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>serviceradar-flowgger-external.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-6dc12970f8cae80d859fe0ab295a8e2ce7092f34fb2911532206247cfcb21a6d">+22/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-controller-manager-configmap.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-7573f55d958373dcc6243ac88532aec097c2aae5bf892e7f9a343fae50937611">+28/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-server-configmap.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-66f1204bca4c03cf645d676190fed93bbdac09ee6b84ef27db9bc597006424c6">+69/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>spire-server-external.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-e7208840788e5a68c04dcbbb6f11cd61853d053c6d307c489a8134c41cb092c1">+17/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>var-references.yaml</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-0b2766ba18815a0a6b437eb90ba0b25c6939b8b04af27f3b0af542c4e9686205">+33/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>validate.sh</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-4bd5e618354334c264e72e9b183b49f9f8dca3bad7c406c49c1ed8d4510e6e66">+17/-3</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>proposal.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-6f0c5aff0b965fcdd9db0c5694e0687fa72e170a89c33bda18ec8f82f426ad33">+26/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>spec.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-2860cadf010e37686782686da542b1669a8e860231acf8b00bbadbbde3f54719">+34/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>tasks.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-bc5b4da0f06380ea7f2f89c15b94cdbfc7c6637d3db0b6703950e2609f7933ff">+13/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>proposal.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-337fd33143ab4a63b1f8fb65facb103f25455ee0f7317a128940d232faa6c68f">[link]</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>spec.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-d1d28d37524c64ae9bf483a7244ce40bb2328f63e0b3164ec556b0947057018b">[link]</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>tasks.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-4bd2a6affbaa4882e1cf19dd27ae8086d0077bea190ed34ca2f35b27f935d87d">[link]</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>proposal.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-0fdd4aa24e76cffe64401b5f3ed8bd0df236aa967851dec02939944647077c5f">+30/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>schema-mapping.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-7d2d02aac4b8c93911ff89f3f2baad6c06cda6200b50a208912c138f83a470ba">+90/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>spec.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-12a4fb79e11a97004a73b86bea21ab75d6e9e3aea1bc42829de521088b795e7c">+54/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>tasks.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-41f4a077d59d3369f485b1680f01ebee506f69f5297e197af9a8aed377df707d">+31/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>spec.md</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-fbdfe226488e4c913d4f6aab0fc98bdc8116c2a77b413376eb91db8ccc452286">+44/-0</a>&nbsp; &nbsp; </td> </tr> <tr> <td><strong>components.json</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-3ae5949d89b0252d10fce9bf950231c8151a73b2154dccfe4e7261acc116582c">+0/-2</a>&nbsp; &nbsp; &nbsp; </td> </tr> <tr> <td><strong>Additional files not shown</strong></td> <td><a href="https://github.com/carverauto/serviceradar/pull/1945/files#diff-2f328e4cd8dbe3ad193e49d92bcf045f47a6b72b1e9487d366f6b8288589b4ca"></a></td> </tr> </table></details></td></tr></tr></tbody></table> </details> ___
qodo-code-review[bot] commented 2025-11-16 05:41:18 +00:00 (Migrated from github.com)
Author
Owner

Imported GitHub PR comment.

Original author: @qodo-code-review[bot]
Original URL: https://github.com/carverauto/serviceradar/pull/1945#issuecomment-3537916848
Original created: 2025-11-16T05:41:18Z

PR Compliance Guide 🔍

(Compliance updated until commit github.com/carverauto/serviceradar@8663f5004e)

Below is a summary of compliance checks for this PR:

Security Compliance
SQL placeholder rewrite

Description: The CompatConn placeholder rewriter replaces all '?' with positional parameters without
parsing SQL, which can corrupt literals/comments and lead to query breakage or unexpected
behavior; while not an injection itself (pgx uses parameterization), it should parse only
parameter placeholders outside string literals.
db.go [280-379]

Referred Code
func (c *CompatConn) QueryRow(ctx context.Context, query string, args ...interface{}) pgx.Row {
	if c == nil || c.db == nil {
		return &errorRow{err: ErrCNPGCompatUnconfigured}
	}

	rewritten, err := rewritePlaceholders(query, len(args))
	if err != nil {
		return &errorRow{err: err}
	}

	return c.db.pgPool.QueryRow(ctx, rewritten, args...)
}

// Query proxies Proton-style queries to CNPG.
func (c *CompatConn) Query(ctx context.Context, query string, args ...interface{}) (Rows, error) {
	if c == nil || c.db == nil {
		return nil, ErrCNPGCompatUnconfigured
	}

	rewritten, err := rewritePlaceholders(query, len(args))
	if err != nil {



 ... (clipped 79 lines)
Large JSON payload risk

Description: JSON metadata is accepted and embedded after a basic Unmarshal check but other CNPG insert
args rely on upstream validation; ensure untrusted strings (e.g., names) are not
concatenated into SQL elsewhere—current file uses parameters, but validation of large
payloads may allow oversized records causing resource exhaustion.
cnpg_metrics.go [492-503]

Referred Code
func normalizeJSON(raw string) (interface{}, error) {
	if strings.TrimSpace(raw) == "" {
		return nil, nil
	}

	var tmp json.RawMessage
	if err := json.Unmarshal([]byte(raw), &tmp); err != nil {
		return nil, err
	}

	return json.RawMessage(raw), nil
}

Ticket Compliance
🟡
🎫 #1944
🟢 Replace Proton driver usage with pgx/pgxpool for PostgreSQL connectivity.
Provide a compatibility layer to emulate Proton batch/query API where needed.
Update metrics storage and retrieval to CNPG with parameterized queries/batch inserts.
Update CLI/help and configs to reflect CNPG (remove Proton references).
Migrate database backend from Proton to CloudNativePG (CNPG) across the codebase.
Implement CNPG-specific write paths for metrics, registry, events, discovery, and device
operations.
Add a CNPG migration tool and schema migrations for initialization.
Ensure identifier resolution, event processing, and device update publishing use CNPG.
Add tests for CNPG operations.
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit logs: New CNPG write paths (e.g., cnpgInsertTimeseriesMetrics via storeRperfMetrics and
StoreSysmonMetrics) add or modify critical data without any explicit audit logging of
actor, action, and outcome in the updated code segment.

Referred Code
	if err := db.cnpgInsertTimeseriesMetrics(ctx, pollerID, series); err != nil {
		return 0, err
	}

	return len(series), nil
}

func buildRperfMetric(ts time.Time, target, suffix, value, metadata string) *models.TimeseriesMetric {
	return &models.TimeseriesMetric{
		Name:           fmt.Sprintf("rperf_%s_%s", target, suffix),
		Type:           "rperf",
		Value:          value,
		Timestamp:      ts,
		TargetDeviceIP: target,
		Metadata:       metadata,
	}
}

// StoreMetric stores a single timeseries metric using the CNPG helper.
func (db *DB) StoreMetric(ctx context.Context, pollerID string, metric *models.TimeseriesMetric) error {
	if metric == nil {



 ... (clipped 14 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Error context gaps: Placeholder rewrite and batch emulation paths return generic errors (e.g.,
ErrUnsupportedInsertStatement, ErrPlaceholderMismatch) without logging or operation
context, which may hinder diagnosis if SQL parsing or CNPG exec fails.

Referred Code
	if c == nil || c.db == nil {
		return ErrCNPGCompatUnconfigured
	}

	rewritten, err := rewritePlaceholders(query, len(args))
	if err != nil {
		return err
	}

	return c.db.ExecCNPG(ctx, rewritten, args...)
}

var insertStmtRegex = regexp.MustCompile(`(?is)insert\s+into\s+([a-zA-Z0-9_\."]+)\s*\(([^)]+)\)`)

func parseInsertStatement(query string) (string, []string, error) {
	matches := insertStmtRegex.FindStringSubmatch(query)
	if len(matches) != 3 {
		return "", nil, fmt.Errorf("%w: %s", ErrUnsupportedInsertStatement, query)
	}

	table := strings.TrimSpace(matches[1])



 ... (clipped 50 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Metadata in logs: Error branches log serialized rperf result metadata and error strings that may include
target addresses; ensure no sensitive data (e.g., IPs considered sensitive in your
context) is emitted at error/warn levels.

Referred Code
metadataBytes, err := json.Marshal(result)
if err != nil {
	db.logger.Error().Err(err).
		Str("poller_id", pollerID).
		Str("target", result.Target).
		Msg("Failed to marshal rperf result metadata")
	continue
}

metadataStr := string(metadataBytes)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
SQL parsing risks: The CompatConn SQL parsing (parseInsertStatement, rewritePlaceholders) accepts and
rewrites arbitrary SQL without visible input validation or strict allowance lists in this
diff, which could introduce risks if fed untrusted statements.

Referred Code

var insertStmtRegex = regexp.MustCompile(`(?is)insert\s+into\s+([a-zA-Z0-9_\."]+)\s*\(([^)]+)\)`)

func parseInsertStatement(query string) (string, []string, error) {
	matches := insertStmtRegex.FindStringSubmatch(query)
	if len(matches) != 3 {
		return "", nil, fmt.Errorf("%w: %s", ErrUnsupportedInsertStatement, query)
	}

	table := strings.TrimSpace(matches[1])
	rawColumns := strings.Split(matches[2], ",")
	columns := make([]string, 0, len(rawColumns))
	for _, col := range rawColumns {
		col = strings.TrimSpace(col)
		if col != "" {
			columns = append(columns, col)
		}
	}

	if len(columns) == 0 {
		return "", nil, fmt.Errorf("%w: %s", ErrInsertColumnsMissing, query)



 ... (clipped 18 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
- Requires Further Human Verification
🏷️ - Compliance label

Previous compliance checks

Compliance check up to commit e0d1313
Security Compliance
Query placeholder rewrite

Description: The placeholder rewrite in rewritePlaceholders is naive and replaces every '?' character
globally without parsing SQL, which can corrupt queries containing '?' inside string
literals or comments and cause unintended execution paths.
db.go [361-379]

Referred Code
	var builder strings.Builder
	count := 0

	for _, r := range query {
		if r == '?' {
			count++
			builder.WriteString(fmt.Sprintf("$%d", count))
			continue
		}

		builder.WriteRune(r)
	}

	if argCount >= 0 && count != argCount {
		return "", fmt.Errorf("%w: query expects %d args, got %d", ErrPlaceholderMismatch, count, argCount)
	}

	return builder.String(), nil
}

Ticket Compliance
🟡
🎫 #1944
🟢 Migrate database layer from Proton to CloudNativePG (CNPG) using pgx/pgxpool.
Replace Proton-specific batch/query logic with CNPG-compatible implementations and
helpers.
Provide compatibility layer to emulate prior Proton batch API where needed.
Implement CNPG-backed storage and retrieval for timeseries and sysmon metrics (CPU, disk,
memory, process).
Update write paths for related features (device updates, capabilities, events, etc.) to
use CNPG.
Maintain functional parity for rperf metric ingestion and query helpers.
Ensure read paths for metrics and device queries are backed by CNPG.
Add CNPG migrations and tooling to initialize/update schema.
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Missing audit logs: New CNPG write paths (e.g., cnpgInsertTimeseriesMetrics and related helpers) are invoked
without visible audit logging of critical actions, but the actual helpers are not in diff,
requiring verification.

Referred Code
	if err := db.cnpgInsertTimeseriesMetrics(ctx, pollerID, series); err != nil {
		return 0, err
	}

	return len(series), nil
}

func buildRperfMetric(ts time.Time, target, suffix, value, metadata string) *models.TimeseriesMetric {
	return &models.TimeseriesMetric{
		Name:           fmt.Sprintf("rperf_%s_%s", target, suffix),
		Type:           "rperf",
		Value:          value,
		Timestamp:      ts,
		TargetDeviceIP: target,
		Metadata:       metadata,
	}
}

// StoreMetric stores a single timeseries metric using the CNPG helper.
func (db *DB) StoreMetric(ctx context.Context, pollerID string, metric *models.TimeseriesMetric) error {
	if metric == nil {



 ... (clipped 14 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Partial error context: Several CNPG proxy methods wrap/return errors but some paths (e.g., Close, cnpgConfigured
fallbacks) return generic errors without operation context; actual insert/query helpers
are external so full coverage is uncertain.

Referred Code
// Close closes the database connection.
func (db *DB) Close() error {
	if db.pgPool != nil {
		db.pgPool.Close()
	}

	return nil
}

// QueryCNPGRows executes a query against the CNPG pool and returns a Rows implementation.
func (db *DB) QueryCNPGRows(ctx context.Context, query string, args ...interface{}) (Rows, error) {
	if !db.cnpgConfigured() {
		return nil, ErrCNPGUnavailable
	}

	rows, err := db.pgPool.Query(ctx, query, args...)
	if err != nil {
		return nil, err
	}

	return &cnpgRows{rows: rows}, nil



 ... (clipped 49 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Metadata logging risk: Rperf metadata is marshaled and stored, and warnings/errors include target and poller_id;
confirm that cnpgInsert... helpers and surrounding logs do not print sensitive metadata
contents.

Referred Code
	metadataBytes, err := json.Marshal(result)
	if err != nil {
		db.logger.Error().Err(err).
			Str("poller_id", pollerID).
			Str("target", result.Target).
			Msg("Failed to marshal rperf result metadata")
		continue
	}

	metadataStr := string(metadataBytes)

	series = append(series,
		buildRperfMetric(timestamp, result.Target, "bandwidth_mbps", fmt.Sprintf("%.2f", result.BitsPerSec/rperfBitsPerSecondDivisor), metadataStr),
		buildRperfMetric(timestamp, result.Target, "jitter_ms", fmt.Sprintf("%.2f", result.JitterMs), metadataStr),
		buildRperfMetric(timestamp, result.Target, "loss_percent", fmt.Sprintf("%.1f", result.LossPercent), metadataStr),
	)
}

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
SQL placeholder rewrite: CompatConn rewrites '?' to '$n' and builds INSERT from parsed columns;
while pgx parameterization is used, parseInsertStatement relies on regex and may accept
unexpected inputs—needs verification of upstream-controlled statements.

Referred Code
var insertStmtRegex = regexp.MustCompile(`(?is)insert\s+into\s+([a-zA-Z0-9_\."]+)\s*\(([^)]+)\)`)

func parseInsertStatement(query string) (string, []string, error) {
	matches := insertStmtRegex.FindStringSubmatch(query)
	if len(matches) != 3 {
		return "", nil, fmt.Errorf("%w: %s", ErrUnsupportedInsertStatement, query)
	}

	table := strings.TrimSpace(matches[1])
	rawColumns := strings.Split(matches[2], ",")
	columns := make([]string, 0, len(rawColumns))
	for _, col := range rawColumns {
		col = strings.TrimSpace(col)
		if col != "" {
			columns = append(columns, col)
		}
	}

	if len(columns) == 0 {
		return "", nil, fmt.Errorf("%w: %s", ErrInsertColumnsMissing, query)
	}



 ... (clipped 18 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/1945#issuecomment-3537916848 Original created: 2025-11-16T05:41:18Z --- ## PR Compliance Guide 🔍 <!-- https://github.com/carverauto/serviceradar/commit/8663f5004e3552a4d8aa6eef7b9bd6fdc66e6614 --> #### (Compliance updated until commit https://github.com/carverauto/serviceradar/commit/8663f5004e3552a4d8aa6eef7b9bd6fdc66e6614) Below is a summary of compliance checks for this PR:<br> <table><tbody><tr><td colspan='2'><strong>Security Compliance</strong></td></tr> <tr><td rowspan=2>⚪</td> <td><details><summary><strong>SQL placeholder rewrite </strong></summary><br> <b>Description:</b> The CompatConn placeholder rewriter replaces all '?' with positional parameters without <br>parsing SQL, which can corrupt literals/comments and lead to query breakage or unexpected <br>behavior; while not an injection itself (pgx uses parameterization), it should parse only <br>parameter placeholders outside string literals.<br> <strong><a href='https://github.com/carverauto/serviceradar/pull/1945/files#diff-5da3684806835246d262230050593f460b12b6c0e3966df174e6061be0e9e575R280-R379'>db.go [280-379]</a></strong><br> <details open><summary>Referred Code</summary> ```go func (c *CompatConn) QueryRow(ctx context.Context, query string, args ...interface{}) pgx.Row { if c == nil || c.db == nil { return &errorRow{err: ErrCNPGCompatUnconfigured} } rewritten, err := rewritePlaceholders(query, len(args)) if err != nil { return &errorRow{err: err} } return c.db.pgPool.QueryRow(ctx, rewritten, args...) } // Query proxies Proton-style queries to CNPG. func (c *CompatConn) Query(ctx context.Context, query string, args ...interface{}) (Rows, error) { if c == nil || c.db == nil { return nil, ErrCNPGCompatUnconfigured } rewritten, err := rewritePlaceholders(query, len(args)) if err != nil { ... (clipped 79 lines) ``` </details></details></td></tr> <tr><td><details><summary><strong>Large JSON payload risk </strong></summary><br> <b>Description:</b> JSON metadata is accepted and embedded after a basic Unmarshal check but other CNPG insert <br>args rely on upstream validation; ensure untrusted strings (e.g., names) are not <br>concatenated into SQL elsewhere—current file uses parameters, but validation of large <br>payloads may allow oversized records causing resource exhaustion.<br> <strong><a href='https://github.com/carverauto/serviceradar/pull/1945/files#diff-8ca23b1b2b298b6eec9af9e23be2c7cb9caa65301f514c013ebbc5b52e933a23R492-R503'>cnpg_metrics.go [492-503]</a></strong><br> <details open><summary>Referred Code</summary> ```go func normalizeJSON(raw string) (interface{}, error) { if strings.TrimSpace(raw) == "" { return nil, nil } var tmp json.RawMessage if err := json.Unmarshal([]byte(raw), &tmp); err != nil { return nil, err } return json.RawMessage(raw), nil } ``` </details></details></td></tr> <tr><td colspan='2'><strong>Ticket Compliance</strong></td></tr> <tr><td>🟡</td> <td> <details> <summary>🎫 <a href=https://github.com/carverauto/serviceradar/issues/1944>#1944</a></summary> <table width='100%'><tbody> <tr><td rowspan=4>🟢</td> <td>Replace Proton driver usage with pgx/pgxpool for PostgreSQL connectivity.</td></tr> <tr><td>Provide a compatibility layer to emulate Proton batch/query API where needed.</td></tr> <tr><td>Update metrics storage and retrieval to CNPG with parameterized queries/batch inserts.</td></tr> <tr><td>Update CLI/help and configs to reflect CNPG (remove Proton references).</td></tr> <tr><td rowspan=5>⚪</td> <td>Migrate database backend from Proton to CloudNativePG (CNPG) across the codebase.</td></tr> <tr><td>Implement CNPG-specific write paths for metrics, registry, events, discovery, and device <br>operations.</td></tr> <tr><td>Add a CNPG migration tool and schema migrations for initialization.</td></tr> <tr><td>Ensure identifier resolution, event processing, and device update publishing use CNPG.</td></tr> <tr><td>Add tests for CNPG operations.</td></tr> </tbody></table> </details> </td></tr> <tr><td colspan='2'><strong>Codebase Duplication Compliance</strong></td></tr> <tr><td>⚪</td><td><details><summary><strong>Codebase context is not defined </strong></summary> Follow the <a href='https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/'>guide</a> to enable codebase context checks. </details></td></tr> <tr><td colspan='2'><strong>Custom Compliance</strong></td></tr> <tr><td rowspan=2>🟢</td><td> <details><summary><strong>Generic: Meaningful Naming and Self-Documenting Code</strong></summary><br> **Objective:** Ensure all identifiers clearly express their purpose and intent, making code <br>self-documenting<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td> <details><summary><strong>Generic: Secure Error Handling</strong></summary><br> **Objective:** To prevent the leakage of sensitive system information through error messages while <br>providing sufficient detail for internal debugging.<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td rowspan=4>⚪</td> <td><details> <summary><strong>Generic: Comprehensive Audit Trails</strong></summary><br> **Objective:** To create a detailed and reliable record of critical system actions for security analysis <br>and compliance.<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/1945/files#diff-2cf937853ac86eb9a879bd370cbe22b835ee0c48be6771b48b0276d93cc2ae93R161-R195'><strong>Missing audit logs</strong></a>: New CNPG write paths (e.g., cnpgInsertTimeseriesMetrics via storeRperfMetrics and <br>StoreSysmonMetrics) add or modify critical data without any explicit audit logging of <br>actor, action, and outcome in the updated code segment.<br> <details open><summary>Referred Code</summary> ```go if err := db.cnpgInsertTimeseriesMetrics(ctx, pollerID, series); err != nil { return 0, err } return len(series), nil } func buildRperfMetric(ts time.Time, target, suffix, value, metadata string) *models.TimeseriesMetric { return &models.TimeseriesMetric{ Name: fmt.Sprintf("rperf_%s_%s", target, suffix), Type: "rperf", Value: value, Timestamp: ts, TargetDeviceIP: target, Metadata: metadata, } } // StoreMetric stores a single timeseries metric using the CNPG helper. func (db *DB) StoreMetric(ctx context.Context, pollerID string, metric *models.TimeseriesMetric) error { if metric == nil { ... (clipped 14 lines) ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td><details> <summary><strong>Generic: Robust Error Handling and Edge Case Management</strong></summary><br> **Objective:** Ensure comprehensive error handling that provides meaningful context and graceful <br>degradation<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/1945/files#diff-5da3684806835246d262230050593f460b12b6c0e3966df174e6061be0e9e575R309-R379'><strong>Error context gaps</strong></a>: Placeholder rewrite and batch emulation paths return generic errors (e.g., <br>ErrUnsupportedInsertStatement, ErrPlaceholderMismatch) without logging or operation <br>context, which may hinder diagnosis if SQL parsing or CNPG exec fails.<br> <details open><summary>Referred Code</summary> ```go if c == nil || c.db == nil { return ErrCNPGCompatUnconfigured } rewritten, err := rewritePlaceholders(query, len(args)) if err != nil { return err } return c.db.ExecCNPG(ctx, rewritten, args...) } var insertStmtRegex = regexp.MustCompile(`(?is)insert\s+into\s+([a-zA-Z0-9_\."]+)\s*\(([^)]+)\)`) func parseInsertStatement(query string) (string, []string, error) { matches := insertStmtRegex.FindStringSubmatch(query) if len(matches) != 3 { return "", nil, fmt.Errorf("%w: %s", ErrUnsupportedInsertStatement, query) } table := strings.TrimSpace(matches[1]) ... (clipped 50 lines) ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td><details> <summary><strong>Generic: Secure Logging Practices</strong></summary><br> **Objective:** To ensure logs are useful for debugging and auditing without exposing sensitive <br>information like PII, PHI, or cardholder data.<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/1945/files#diff-2cf937853ac86eb9a879bd370cbe22b835ee0c48be6771b48b0276d93cc2ae93R139-R149'><strong>Metadata in logs</strong></a>: Error branches log serialized rperf result metadata and error strings that may include <br>target addresses; ensure no sensitive data (e.g., IPs considered sensitive in your <br>context) is emitted at error/warn levels.<br> <details open><summary>Referred Code</summary> ```go metadataBytes, err := json.Marshal(result) if err != nil { db.logger.Error().Err(err). Str("poller_id", pollerID). Str("target", result.Target). Msg("Failed to marshal rperf result metadata") continue } metadataStr := string(metadataBytes) ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td><details> <summary><strong>Generic: Security-First Input Validation and Data Handling</strong></summary><br> **Objective:** Ensure all data inputs are validated, sanitized, and handled securely to prevent <br>vulnerabilities<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/1945/files#diff-5da3684806835246d262230050593f460b12b6c0e3966df174e6061be0e9e575R320-R358'><strong>SQL parsing risks</strong></a>: The CompatConn SQL parsing (parseInsertStatement, rewritePlaceholders) accepts and <br>rewrites arbitrary SQL without visible input validation or strict allowance lists in this <br>diff, which could introduce risks if fed untrusted statements.<br> <details open><summary>Referred Code</summary> ```go var insertStmtRegex = regexp.MustCompile(`(?is)insert\s+into\s+([a-zA-Z0-9_\."]+)\s*\(([^)]+)\)`) func parseInsertStatement(query string) (string, []string, error) { matches := insertStmtRegex.FindStringSubmatch(query) if len(matches) != 3 { return "", nil, fmt.Errorf("%w: %s", ErrUnsupportedInsertStatement, query) } table := strings.TrimSpace(matches[1]) rawColumns := strings.Split(matches[2], ",") columns := make([]string, 0, len(rawColumns)) for _, col := range rawColumns { col = strings.TrimSpace(col) if col != "" { columns = append(columns, col) } } if len(columns) == 0 { return "", nil, fmt.Errorf("%w: %s", ErrInsertColumnsMissing, query) ... (clipped 18 lines) ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td align="center" colspan="2"> <!-- placeholder --> <!-- /compliance --update_compliance=true --> </td></tr></tbody></table> <details><summary>Compliance status legend</summary> 🟢 - Fully Compliant<br> 🟡 - Partial Compliant<br> 🔴 - Not Compliant<br> ⚪ - Requires Further Human Verification<br> 🏷️ - Compliance label<br> </details> ___ #### Previous compliance checks <details> <summary>Compliance check up to commit <a href='https://github.com/carverauto/serviceradar/commit/e0d13136cb6bd1d3155cc54661a85d8284dde62f'>e0d1313</a></summary><br> <table><tbody><tr><td colspan='2'><strong>Security Compliance</strong></td></tr> <tr><td rowspan=1>⚪</td> <td><details><summary><strong>Query placeholder rewrite</strong></summary><br> <b>Description:</b> The placeholder rewrite in <code>rewritePlaceholders</code> is naive and replaces every '?' character <br>globally without parsing SQL, which can corrupt queries containing '?' inside string <br>literals or comments and cause unintended execution paths.<br> <strong><a href='https://github.com/carverauto/serviceradar/pull/1945/files#diff-5da3684806835246d262230050593f460b12b6c0e3966df174e6061be0e9e575R361-R379'>db.go [361-379]</a></strong><br> <details open><summary>Referred Code</summary> ```go var builder strings.Builder count := 0 for _, r := range query { if r == '?' { count++ builder.WriteString(fmt.Sprintf("$%d", count)) continue } builder.WriteRune(r) } if argCount >= 0 && count != argCount { return "", fmt.Errorf("%w: query expects %d args, got %d", ErrPlaceholderMismatch, count, argCount) } return builder.String(), nil } ``` </details></details></td></tr> <tr><td colspan='2'><strong>Ticket Compliance</strong></td></tr> <tr><td>🟡</td> <td> <details> <summary>🎫 <a href=https://github.com/carverauto/serviceradar/issues/1944>#1944</a></summary> <table width='100%'><tbody> <tr><td rowspan=6>🟢</td> <td>Migrate database layer from Proton to CloudNativePG (CNPG) using pgx/pgxpool.</td></tr> <tr><td>Replace Proton-specific batch/query logic with CNPG-compatible implementations and <br>helpers.</td></tr> <tr><td>Provide compatibility layer to emulate prior Proton batch API where needed.</td></tr> <tr><td>Implement CNPG-backed storage and retrieval for timeseries and sysmon metrics (CPU, disk, <br>memory, process).</td></tr> <tr><td>Update write paths for related features (device updates, capabilities, events, etc.) to <br>use CNPG.</td></tr> <tr><td>Maintain functional parity for rperf metric ingestion and query helpers.</td></tr> <tr><td rowspan=2>⚪</td> <td>Ensure read paths for metrics and device queries are backed by CNPG.</td></tr> <tr><td>Add CNPG migrations and tooling to initialize/update schema.</td></tr> </tbody></table> </details> </td></tr> <tr><td colspan='2'><strong>Codebase Duplication Compliance</strong></td></tr> <tr><td>⚪</td><td><details><summary><strong>Codebase context is not defined </strong></summary> Follow the <a href='https://qodo-merge-docs.qodo.ai/core-abilities/rag_context_enrichment/'>guide</a> to enable codebase context checks. </details></td></tr> <tr><td colspan='2'><strong>Custom Compliance</strong></td></tr> <tr><td rowspan=2>🟢</td><td> <details><summary><strong>Generic: Meaningful Naming and Self-Documenting Code</strong></summary><br> **Objective:** Ensure all identifiers clearly express their purpose and intent, making code <br>self-documenting<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td> <details><summary><strong>Generic: Secure Error Handling</strong></summary><br> **Objective:** To prevent the leakage of sensitive system information through error messages while <br>providing sufficient detail for internal debugging.<br> **Status:** Passed<br> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td rowspan=4>⚪</td> <td><details> <summary><strong>Generic: Comprehensive Audit Trails</strong></summary><br> **Objective:** To create a detailed and reliable record of critical system actions for security analysis <br>and compliance.<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/1945/files#diff-2cf937853ac86eb9a879bd370cbe22b835ee0c48be6771b48b0276d93cc2ae93R161-R195'><strong>Missing audit logs</strong></a>: New CNPG write paths (e.g., cnpgInsertTimeseriesMetrics and related helpers) are invoked <br>without visible audit logging of critical actions, but the actual helpers are not in diff, <br>requiring verification.<br> <details open><summary>Referred Code</summary> ```go if err := db.cnpgInsertTimeseriesMetrics(ctx, pollerID, series); err != nil { return 0, err } return len(series), nil } func buildRperfMetric(ts time.Time, target, suffix, value, metadata string) *models.TimeseriesMetric { return &models.TimeseriesMetric{ Name: fmt.Sprintf("rperf_%s_%s", target, suffix), Type: "rperf", Value: value, Timestamp: ts, TargetDeviceIP: target, Metadata: metadata, } } // StoreMetric stores a single timeseries metric using the CNPG helper. func (db *DB) StoreMetric(ctx context.Context, pollerID string, metric *models.TimeseriesMetric) error { if metric == nil { ... (clipped 14 lines) ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td><details> <summary><strong>Generic: Robust Error Handling and Edge Case Management</strong></summary><br> **Objective:** Ensure comprehensive error handling that provides meaningful context and graceful <br>degradation<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/1945/files#diff-5da3684806835246d262230050593f460b12b6c0e3966df174e6061be0e9e575R117-R186'><strong>Partial error context</strong></a>: Several CNPG proxy methods wrap/return errors but some paths (e.g., Close, cnpgConfigured <br>fallbacks) return generic errors without operation context; actual insert/query helpers <br>are external so full coverage is uncertain.<br> <details open><summary>Referred Code</summary> ```go // Close closes the database connection. func (db *DB) Close() error { if db.pgPool != nil { db.pgPool.Close() } return nil } // QueryCNPGRows executes a query against the CNPG pool and returns a Rows implementation. func (db *DB) QueryCNPGRows(ctx context.Context, query string, args ...interface{}) (Rows, error) { if !db.cnpgConfigured() { return nil, ErrCNPGUnavailable } rows, err := db.pgPool.Query(ctx, query, args...) if err != nil { return nil, err } return &cnpgRows{rows: rows}, nil ... (clipped 49 lines) ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td><details> <summary><strong>Generic: Secure Logging Practices</strong></summary><br> **Objective:** To ensure logs are useful for debugging and auditing without exposing sensitive <br>information like PII, PHI, or cardholder data.<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/1945/files#diff-2cf937853ac86eb9a879bd370cbe22b835ee0c48be6771b48b0276d93cc2ae93R139-R155'><strong>Metadata logging risk</strong></a>: Rperf metadata is marshaled and stored, and warnings/errors include target and poller_id; <br>confirm that cnpgInsert... helpers and surrounding logs do not print sensitive metadata <br>contents.<br> <details open><summary>Referred Code</summary> ```go metadataBytes, err := json.Marshal(result) if err != nil { db.logger.Error().Err(err). Str("poller_id", pollerID). Str("target", result.Target). Msg("Failed to marshal rperf result metadata") continue } metadataStr := string(metadataBytes) series = append(series, buildRperfMetric(timestamp, result.Target, "bandwidth_mbps", fmt.Sprintf("%.2f", result.BitsPerSec/rperfBitsPerSecondDivisor), metadataStr), buildRperfMetric(timestamp, result.Target, "jitter_ms", fmt.Sprintf("%.2f", result.JitterMs), metadataStr), buildRperfMetric(timestamp, result.Target, "loss_percent", fmt.Sprintf("%.1f", result.LossPercent), metadataStr), ) } ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td><details> <summary><strong>Generic: Security-First Input Validation and Data Handling</strong></summary><br> **Objective:** Ensure all data inputs are validated, sanitized, and handled securely to prevent <br>vulnerabilities<br> **Status:** <br><a href='https://github.com/carverauto/serviceradar/pull/1945/files#diff-5da3684806835246d262230050593f460b12b6c0e3966df174e6061be0e9e575R321-R359'><strong>SQL placeholder rewrite</strong></a>: CompatConn rewrites &#x27;?&#x27; to &#x27;$n&#x27; and builds INSERT from parsed columns; <br>while pgx parameterization is used, parseInsertStatement relies on regex and may accept <br>unexpected inputs—needs verification of upstream-controlled statements.<br> <details open><summary>Referred Code</summary> ```go var insertStmtRegex = regexp.MustCompile(`(?is)insert\s+into\s+([a-zA-Z0-9_\."]+)\s*\(([^)]+)\)`) func parseInsertStatement(query string) (string, []string, error) { matches := insertStmtRegex.FindStringSubmatch(query) if len(matches) != 3 { return "", nil, fmt.Errorf("%w: %s", ErrUnsupportedInsertStatement, query) } table := strings.TrimSpace(matches[1]) rawColumns := strings.Split(matches[2], ",") columns := make([]string, 0, len(rawColumns)) for _, col := range rawColumns { col = strings.TrimSpace(col) if col != "" { columns = append(columns, col) } } if len(columns) == 0 { return "", nil, fmt.Errorf("%w: %s", ErrInsertColumnsMissing, query) } ... (clipped 18 lines) ``` </details> > Learn more about managing compliance <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#configuration-options'>generic rules</a> or creating your own <a href='https://qodo-merge-docs.qodo.ai/tools/compliance/#custom-compliance'>custom rules</a> </details></td></tr> <tr><td align="center" colspan="2"> <!-- /compliance --update_compliance=true --> </td></tr></tbody></table> </details>
qodo-code-review[bot] commented 2025-11-16 05:42:26 +00:00 (Migrated from github.com)
Author
Owner

Imported GitHub PR comment.

Original author: @qodo-code-review[bot]
Original URL: https://github.com/carverauto/serviceradar/pull/1945#issuecomment-3537921527
Original created: 2025-11-16T05:42:26Z

PR Code Suggestions

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Use efficient bulk insert operation

Replace the inefficient loop of single INSERT statements in compatBatch.Send
with a single, high-performance pgx.CopyFrom bulk insert operation to improve
database write performance.

pkg/db/db.go [265-277]

 // Send executes the buffered INSERT statements against CNPG.
 func (b *compatBatch) Send() error {
 	if len(b.rowBuffers) == 0 {
 		return nil
 	}
 
-	for _, row := range b.rowBuffers {
-		if err := b.conn.db.ExecCNPG(b.ctx, b.insertSQL, row...); err != nil {
-			return err
+	table, _, err := parseInsertStatement(b.insertSQL)
+	if err != nil {
+		// Fallback for safety, though parseInsertStatement is called in PrepareBatch
+		for _, row := range b.rowBuffers {
+			if err := b.conn.db.ExecCNPG(b.ctx, b.insertSQL, row...); err != nil {
+				return err
+			}
 		}
+		return nil
+	}
+
+	_, err = b.conn.db.pgPool.CopyFrom(
+		b.ctx,
+		pgx.Identifier{table},
+		b.columns,
+		pgx.CopyFromRows(b.rowBuffers),
+	)
+
+	if err != nil {
+		return fmt.Errorf("cnpg copy from: %w", err)
 	}
 
 	return nil
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical performance issue where the batch insert is implemented as a loop of single inserts, and it proposes the correct and idiomatic pgx.CopyFrom solution for efficient bulk insertion.

High
Refactor to use safe batch helper

Refactor cnpgInsertDeviceUpdates to use the sendCNPG helper function for sending
the batch, ensuring thread-safe database operations and centralizing logic.

pkg/db/cnpg_unified_devices.go [58-127]

 func (db *DB) cnpgInsertDeviceUpdates(ctx context.Context, updates []*models.DeviceUpdate) error {
 	if len(updates) == 0 || !db.useCNPGWrites() {
 		return nil
 	}
 
 	batch := &pgx.Batch{}
 
 	for _, update := range updates {
-...
+		observed := update.Timestamp
+		if observed.IsZero() {
+			observed = time.Now().UTC()
+		}
+
+		if update.Partition == "" {
+			update.Partition = defaultPartitionValue
+		}
+
+		if update.Metadata == nil {
+			update.Metadata = make(map[string]string)
+		}
+
+		metaBytes, err := json.Marshal(update.Metadata)
+		if err != nil {
+			db.logger.Warn().
+				Err(err).
+				Str("device_id", update.DeviceID).
+				Msg("failed to marshal device update metadata for CNPG; defaulting to empty object")
+			metaBytes = []byte("{}")
+		}
+
+		arbitrarySource := string(update.Source)
+		if arbitrarySource == "" {
+			arbitrarySource = "unknown"
+		}
+
 		batch.Queue(
 			`INSERT INTO device_updates (
-...
+				observed_at,
+				agent_id,
+				poller_id,
+				partition,
+				device_id,
+				discovery_source,
+				ip,
+				mac,
+				hostname,
+				available,
+				metadata
 			) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb)`,
 			observed,
-...
+			update.AgentID,
+			update.PollerID,
+			update.Partition,
+			update.DeviceID,
+			arbitrarySource,
+			update.IP,
 			toNullableString(update.MAC),
 			toNullableString(update.Hostname),
-...
+			update.IsAvailable,
+			metaBytes,
 		)
 	}
 
-	br := db.pgPool.SendBatch(ctx, batch)
-	if err := br.Close(); err != nil {
-		return fmt.Errorf("cnpg device_updates batch: %w", err)
-	}
-
-	return nil
+	return db.sendCNPG(ctx, batch, "device_updates")
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a concurrency issue by using pgPool.SendBatch directly and proposes a valid refactoring to use the sendCNPG helper. This improves thread safety and centralizes batch sending logic, enhancing code maintainability.

Medium
Improve placeholder rewriting logic

Refactor rewritePlaceholders to use a regular expression for replacing ? with $n
placeholders to improve efficiency and prevent incorrect replacements within
string literals.

pkg/db/db.go [360-379]

+var placeholderRegex = regexp.MustCompile(`\?`)
+
 func rewritePlaceholders(query string, argCount int) (string, error) {
-	var builder strings.Builder
 	count := 0
-
-	for _, r := range query {
-		if r == '?' {
-			count++
-			builder.WriteString(fmt.Sprintf("$%d", count))
-			continue
-		}
-
-		builder.WriteRune(r)
-	}
+	rewrittenQuery := placeholderRegex.ReplaceAllStringFunc(query, func(string) string {
+		count++
+		return fmt.Sprintf("$%d", count)
+	})
 
 	if argCount >= 0 && count != argCount {
 		return "", fmt.Errorf("%w: query expects %d args, got %d", ErrPlaceholderMismatch, count, argCount)
 	}
 
-	return builder.String(), nil
+	return rewrittenQuery, nil
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 4

__

Why: The suggestion correctly points out a potential edge case where ? inside string literals would be incorrectly replaced, but the proposed regex-based solution does not fix this issue and is not necessarily more performant than the existing strings.Builder implementation.

Low
Possible issue
Return error on database scan failure

In list functions, return an error immediately on a row scan failure instead of
logging and continuing, to prevent returning partial data without an error.

pkg/registry/service_registry_queries.go [961-977]

 		if err := rows.Scan(
 			&poller.PollerID,
 ...
 			&checkerCount,
 		); err != nil {
-			r.logger.Error().Err(err).Msg("Error scanning poller")
-			continue
+			return nil, fmt.Errorf("failed to scan poller row: %w", err)
 		}

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: This suggestion identifies a critical bug where a scan error is suppressed, leading to the function returning a partial dataset without an error, which could cause silent data corruption or incorrect behavior downstream.

High
Return messages on database insertion failure

On a database insertion failure in processEventsTable, return the original
messages along with the error to prevent them from being incorrectly handled by
the caller, which could lead to an infinite redelivery loop.

pkg/consumers/db-event-writer/processor.go [622-644]

 func (p *Processor) processEventsTable(ctx context.Context, msgs []jetstream.Msg) ([]jetstream.Msg, error) {
 	if len(msgs) == 0 {
 		return nil, nil
 	}
 
 	if p.db == nil {
 		return nil, errCNPGEventsNotConfigured
 	}
 
 	eventRows := make([]*models.EventRow, 0, len(msgs))
 
 	for _, msg := range msgs {
 		row := buildEventRow(msg.Data(), msg.Subject())
 		clone := row
 		eventRows = append(eventRows, &clone)
 	}
 
 	if err := p.db.InsertEvents(ctx, eventRows); err != nil {
-		return nil, err
+		return msgs, err
 	}
 
 	return msgs, nil
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where returning nil on a database error would cause message redelivery and a potential infinite processing loop, and provides the correct fix.

High
Ensure concurrent database batch safety

Modify sendCNPG to acquire a dedicated connection from the pool before sending a
batch and release it afterward, preventing potential concurrency issues and data
corruption.

pkg/db/cnpg_metrics.go [291-297]

 func (db *DB) sendCNPG(ctx context.Context, batch *pgx.Batch, name string) error {
-	br := db.pgPool.SendBatch(ctx, batch)
+	conn, err := db.pgPool.Acquire(ctx)
+	if err != nil {
+		return fmt.Errorf("failed to acquire connection for cnpg %s insert: %w", name, err)
+	}
+	defer conn.Release()
+
+	br := conn.SendBatch(ctx, batch)
 	if err := br.Close(); err != nil {
 		return fmt.Errorf("cnpg %s insert: %w", name, err)
 	}
 	return nil
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical concurrency issue where sending batches directly on a connection pool (pgPool) can lead to race conditions and data corruption. Acquiring a dedicated connection for each batch operation is the correct way to ensure thread safety.

High
Log dual-write errors without failing

In dual-write scenarios, log errors from the secondary database write instead of
returning an error to prevent data inconsistency and failed operations.

pkg/registry/service_registry_queries.go [449-453]

 		if execErr == nil {
 			if err := r.upsertCNPGPoller(ctx, poller); err != nil {
-				return err
+				// Log the inconsistency for reconciliation, but don't fail the overall operation
+				// as the primary database write succeeded.
+				r.logger.Error().Err(err).Str("poller_id", poller.PollerID).
+					Msg("Failed to upsert poller to CNPG during status update")
 			}
 		}

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential data inconsistency issue in the dual-write migration strategy and proposes a robust solution to handle it, improving the reliability of the system during the transition.

Medium
Return correct type for nullables

Modify the toNullableString function to return sql.NullString instead of
interface{} to ensure type safety and proper handling of nullable strings by the
database driver.

pkg/db/cnpg_unified_devices.go [359-369]

-func toNullableString(value *string) interface{} {
+func toNullableString(value *string) sql.NullString {
 	if value == nil {
-		return nil
+		return sql.NullString{}
 	}
 
-	if trimmed := strings.TrimSpace(*value); trimmed == "" {
-		return nil
-	} else {
-		return trimmed
+	trimmed := strings.TrimSpace(*value)
+	if trimmed == "" {
+		return sql.NullString{}
 	}
+
+	return sql.NullString{String: trimmed, Valid: true}
 }

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out that returning sql.NullString is more idiomatic and type-safe for database operations than returning interface{}. While the pgx driver might handle nil correctly, this change improves code clarity and robustness.

Low
  • Update
Imported GitHub PR comment. Original author: @qodo-code-review[bot] Original URL: https://github.com/carverauto/serviceradar/pull/1945#issuecomment-3537921527 Original created: 2025-11-16T05:42:26Z --- ## PR Code Suggestions ✨ <!-- e0d1313 --> Explore these optional code suggestions: <table><thead><tr><td><strong>Category</strong></td><td align=left><strong>Suggestion&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; </strong></td><td align=center><strong>Impact</strong></td></tr><tbody><tr><td rowspan=3>General</td> <td> <details><summary>Use efficient bulk insert operation</summary> ___ **Replace the inefficient loop of single <code>INSERT</code> statements in <code>compatBatch.Send</code> <br>with a single, high-performance <code>pgx.CopyFrom</code> bulk insert operation to improve <br>database write performance.** [pkg/db/db.go [265-277]](https://github.com/carverauto/serviceradar/pull/1945/files#diff-5da3684806835246d262230050593f460b12b6c0e3966df174e6061be0e9e575R265-R277) ```diff // Send executes the buffered INSERT statements against CNPG. func (b *compatBatch) Send() error { if len(b.rowBuffers) == 0 { return nil } - for _, row := range b.rowBuffers { - if err := b.conn.db.ExecCNPG(b.ctx, b.insertSQL, row...); err != nil { - return err + table, _, err := parseInsertStatement(b.insertSQL) + if err != nil { + // Fallback for safety, though parseInsertStatement is called in PrepareBatch + for _, row := range b.rowBuffers { + if err := b.conn.db.ExecCNPG(b.ctx, b.insertSQL, row...); err != nil { + return err + } } + return nil + } + + _, err = b.conn.db.pgPool.CopyFrom( + b.ctx, + pgx.Identifier{table}, + b.columns, + pgx.CopyFromRows(b.rowBuffers), + ) + + if err != nil { + return fmt.Errorf("cnpg copy from: %w", err) } return nil } ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 9</summary> __ Why: The suggestion correctly identifies a critical performance issue where the batch insert is implemented as a loop of single inserts, and it proposes the correct and idiomatic `pgx.CopyFrom` solution for efficient bulk insertion. </details></details></td><td align=center>High </td></tr><tr><td> <details><summary>Refactor to use safe batch helper</summary> ___ **Refactor <code>cnpgInsertDeviceUpdates</code> to use the <code>sendCNPG</code> helper function for sending <br>the batch, ensuring thread-safe database operations and centralizing logic.** [pkg/db/cnpg_unified_devices.go [58-127]](https://github.com/carverauto/serviceradar/pull/1945/files#diff-ab3bf557cf9bb1b281a315a73abee38748de1654941c2471542e5e9bfc1716d8R58-R127) ```diff func (db *DB) cnpgInsertDeviceUpdates(ctx context.Context, updates []*models.DeviceUpdate) error { if len(updates) == 0 || !db.useCNPGWrites() { return nil } batch := &pgx.Batch{} for _, update := range updates { -... + observed := update.Timestamp + if observed.IsZero() { + observed = time.Now().UTC() + } + + if update.Partition == "" { + update.Partition = defaultPartitionValue + } + + if update.Metadata == nil { + update.Metadata = make(map[string]string) + } + + metaBytes, err := json.Marshal(update.Metadata) + if err != nil { + db.logger.Warn(). + Err(err). + Str("device_id", update.DeviceID). + Msg("failed to marshal device update metadata for CNPG; defaulting to empty object") + metaBytes = []byte("{}") + } + + arbitrarySource := string(update.Source) + if arbitrarySource == "" { + arbitrarySource = "unknown" + } + batch.Queue( `INSERT INTO device_updates ( -... + observed_at, + agent_id, + poller_id, + partition, + device_id, + discovery_source, + ip, + mac, + hostname, + available, + metadata ) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb)`, observed, -... + update.AgentID, + update.PollerID, + update.Partition, + update.DeviceID, + arbitrarySource, + update.IP, toNullableString(update.MAC), toNullableString(update.Hostname), -... + update.IsAvailable, + metaBytes, ) } - br := db.pgPool.SendBatch(ctx, batch) - if err := br.Close(); err != nil { - return fmt.Errorf("cnpg device_updates batch: %w", err) - } - - return nil + return db.sendCNPG(ctx, batch, "device_updates") } ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies a concurrency issue by using `pgPool.SendBatch` directly and proposes a valid refactoring to use the `sendCNPG` helper. This improves thread safety and centralizes batch sending logic, enhancing code maintainability. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Improve placeholder rewriting logic</summary> ___ **Refactor <code>rewritePlaceholders</code> to use a regular expression for replacing <code>?</code> with <code>$n</code> <br>placeholders to improve efficiency and prevent incorrect replacements within <br>string literals.** [pkg/db/db.go [360-379]](https://github.com/carverauto/serviceradar/pull/1945/files#diff-5da3684806835246d262230050593f460b12b6c0e3966df174e6061be0e9e575R360-R379) ```diff +var placeholderRegex = regexp.MustCompile(`\?`) + func rewritePlaceholders(query string, argCount int) (string, error) { - var builder strings.Builder count := 0 - - for _, r := range query { - if r == '?' { - count++ - builder.WriteString(fmt.Sprintf("$%d", count)) - continue - } - - builder.WriteRune(r) - } + rewrittenQuery := placeholderRegex.ReplaceAllStringFunc(query, func(string) string { + count++ + return fmt.Sprintf("$%d", count) + }) if argCount >= 0 && count != argCount { return "", fmt.Errorf("%w: query expects %d args, got %d", ErrPlaceholderMismatch, count, argCount) } - return builder.String(), nil + return rewrittenQuery, nil } ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 4</summary> __ Why: The suggestion correctly points out a potential edge case where `?` inside string literals would be incorrectly replaced, but the proposed regex-based solution does not fix this issue and is not necessarily more performant than the existing `strings.Builder` implementation. </details></details></td><td align=center>Low </td></tr><tr><td rowspan=5>Possible issue</td> <td> <details><summary>Return error on database scan failure</summary> ___ **In list functions, return an error immediately on a row scan failure instead of <br>logging and continuing, to prevent returning partial data without an error.** [pkg/registry/service_registry_queries.go [961-977]](https://github.com/carverauto/serviceradar/pull/1945/files#diff-3591e8646384be68811f862b986342253cabc23176c6f0e09996453baf88a2e9R961-R977) ```diff if err := rows.Scan( &poller.PollerID, ... &checkerCount, ); err != nil { - r.logger.Error().Err(err).Msg("Error scanning poller") - continue + return nil, fmt.Errorf("failed to scan poller row: %w", err) } ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 9</summary> __ Why: This suggestion identifies a critical bug where a scan error is suppressed, leading to the function returning a partial dataset without an error, which could cause silent data corruption or incorrect behavior downstream. </details></details></td><td align=center>High </td></tr><tr><td> <details><summary>Return messages on database insertion failure</summary> ___ **On a database insertion failure in <code>processEventsTable</code>, return the original <br>messages along with the error to prevent them from being incorrectly handled by <br>the caller, which could lead to an infinite redelivery loop.** [pkg/consumers/db-event-writer/processor.go [622-644]](https://github.com/carverauto/serviceradar/pull/1945/files#diff-c55d73b621975e3797271d69fc43b78fa44eb184437392f9e40e18d4568589a8R622-R644) ```diff func (p *Processor) processEventsTable(ctx context.Context, msgs []jetstream.Msg) ([]jetstream.Msg, error) { if len(msgs) == 0 { return nil, nil } if p.db == nil { return nil, errCNPGEventsNotConfigured } eventRows := make([]*models.EventRow, 0, len(msgs)) for _, msg := range msgs { row := buildEventRow(msg.Data(), msg.Subject()) clone := row eventRows = append(eventRows, &clone) } if err := p.db.InsertEvents(ctx, eventRows); err != nil { - return nil, err + return msgs, err } return msgs, nil } ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 9</summary> __ Why: The suggestion correctly identifies a critical bug where returning `nil` on a database error would cause message redelivery and a potential infinite processing loop, and provides the correct fix. </details></details></td><td align=center>High </td></tr><tr><td> <details><summary>Ensure concurrent database batch safety</summary> ___ **Modify <code>sendCNPG</code> to acquire a dedicated connection from the pool before sending a <br>batch and release it afterward, preventing potential concurrency issues and data <br>corruption.** [pkg/db/cnpg_metrics.go [291-297]](https://github.com/carverauto/serviceradar/pull/1945/files#diff-8ca23b1b2b298b6eec9af9e23be2c7cb9caa65301f514c013ebbc5b52e933a23R291-R297) ```diff func (db *DB) sendCNPG(ctx context.Context, batch *pgx.Batch, name string) error { - br := db.pgPool.SendBatch(ctx, batch) + conn, err := db.pgPool.Acquire(ctx) + if err != nil { + return fmt.Errorf("failed to acquire connection for cnpg %s insert: %w", name, err) + } + defer conn.Release() + + br := conn.SendBatch(ctx, batch) if err := br.Close(); err != nil { return fmt.Errorf("cnpg %s insert: %w", name, err) } return nil } ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 9</summary> __ Why: The suggestion correctly identifies a critical concurrency issue where sending batches directly on a connection pool (`pgPool`) can lead to race conditions and data corruption. Acquiring a dedicated connection for each batch operation is the correct way to ensure thread safety. </details></details></td><td align=center>High </td></tr><tr><td> <details><summary>Log dual-write errors without failing</summary> ___ **In dual-write scenarios, log errors from the secondary database write instead of <br>returning an error to prevent data inconsistency and failed operations.** [pkg/registry/service_registry_queries.go [449-453]](https://github.com/carverauto/serviceradar/pull/1945/files#diff-3591e8646384be68811f862b986342253cabc23176c6f0e09996453baf88a2e9R449-R453) ```diff if execErr == nil { if err := r.upsertCNPGPoller(ctx, poller); err != nil { - return err + // Log the inconsistency for reconciliation, but don't fail the overall operation + // as the primary database write succeeded. + r.logger.Error().Err(err).Str("poller_id", poller.PollerID). + Msg("Failed to upsert poller to CNPG during status update") } } ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 8</summary> __ Why: The suggestion correctly identifies a potential data inconsistency issue in the dual-write migration strategy and proposes a robust solution to handle it, improving the reliability of the system during the transition. </details></details></td><td align=center>Medium </td></tr><tr><td> <details><summary>Return correct type for nullables</summary> ___ **Modify the <code>toNullableString</code> function to return <code>sql.NullString</code> instead of <br><code>interface{}</code> to ensure type safety and proper handling of nullable strings by the <br>database driver.** [pkg/db/cnpg_unified_devices.go [359-369]](https://github.com/carverauto/serviceradar/pull/1945/files#diff-ab3bf557cf9bb1b281a315a73abee38748de1654941c2471542e5e9bfc1716d8R359-R369) ```diff -func toNullableString(value *string) interface{} { +func toNullableString(value *string) sql.NullString { if value == nil { - return nil + return sql.NullString{} } - if trimmed := strings.TrimSpace(*value); trimmed == "" { - return nil - } else { - return trimmed + trimmed := strings.TrimSpace(*value) + if trimmed == "" { + return sql.NullString{} } + + return sql.NullString{String: trimmed, Valid: true} } ``` `[To ensure code accuracy, apply this suggestion manually]` <details><summary>Suggestion importance[1-10]: 6</summary> __ Why: The suggestion correctly points out that returning `sql.NullString` is more idiomatic and type-safe for database operations than returning `interface{}`. While the `pgx` driver might handle `nil` correctly, this change improves code clarity and robustness. </details></details></td><td align=center>Low </td></tr> <tr><td align="center" colspan="2"> - [ ] Update <!-- /improve_multi --more_suggestions=true --> </td><td></td></tr></tbody></table>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
carverauto/serviceradar!2414
No description provided.