mirror of
https://github.com/DeBrosOfficial/orama.git
synced 2026-03-17 03:13:00 +00:00
Fixed bug on rqlite array overflow buffer
This commit is contained in:
parent
45a8285ae8
commit
ed4e490463
2
Makefile
2
Makefile
@ -63,7 +63,7 @@ test-e2e-quick:
|
|||||||
|
|
||||||
.PHONY: build clean test deps tidy fmt vet lint install-hooks upload-devnet upload-testnet redeploy-devnet redeploy-testnet release health
|
.PHONY: build clean test deps tidy fmt vet lint install-hooks upload-devnet upload-testnet redeploy-devnet redeploy-testnet release health
|
||||||
|
|
||||||
VERSION := 0.112.7
|
VERSION := 0.112.8
|
||||||
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown)
|
||||||
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ)
|
||||||
LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)'
|
LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)'
|
||||||
|
|||||||
@ -9,6 +9,28 @@ import (
|
|||||||
"github.com/rqlite/gorqlite"
|
"github.com/rqlite/gorqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// safeWriteOneParameterized wraps conn.WriteOneParameterized with panic recovery.
|
||||||
|
// gorqlite panics with "index out of range" when RQLite returns empty results
|
||||||
|
// during temporary unavailability. This converts the panic to a normal error.
|
||||||
|
func safeWriteOneParameterized(conn *gorqlite.Connection, stmt gorqlite.ParameterizedStatement) (result gorqlite.WriteResult, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("gorqlite panic (WriteOneParameterized): %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return conn.WriteOneParameterized(stmt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeWriteOne wraps conn.WriteOne with panic recovery.
|
||||||
|
func safeWriteOne(conn *gorqlite.Connection, query string) (result gorqlite.WriteResult, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("gorqlite panic (WriteOne): %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return conn.WriteOne(query)
|
||||||
|
}
|
||||||
|
|
||||||
// DatabaseClientImpl implements DatabaseClient
|
// DatabaseClientImpl implements DatabaseClient
|
||||||
type DatabaseClientImpl struct {
|
type DatabaseClientImpl struct {
|
||||||
client *Client
|
client *Client
|
||||||
@ -79,7 +101,7 @@ func (d *DatabaseClientImpl) Query(ctx context.Context, sql string, args ...inte
|
|||||||
|
|
||||||
if isWriteOperation {
|
if isWriteOperation {
|
||||||
// Execute write operation with parameters
|
// Execute write operation with parameters
|
||||||
_, err := conn.WriteOneParameterized(gorqlite.ParameterizedStatement{
|
_, err := safeWriteOneParameterized(conn, gorqlite.ParameterizedStatement{
|
||||||
Query: sql,
|
Query: sql,
|
||||||
Arguments: args,
|
Arguments: args,
|
||||||
})
|
})
|
||||||
@ -293,7 +315,7 @@ func (d *DatabaseClientImpl) Transaction(ctx context.Context, queries []string)
|
|||||||
// Execute all queries in the transaction
|
// Execute all queries in the transaction
|
||||||
success := true
|
success := true
|
||||||
for _, query := range queries {
|
for _, query := range queries {
|
||||||
_, err := conn.WriteOne(query)
|
_, err := safeWriteOne(conn, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = err
|
lastErr = err
|
||||||
success = false
|
success = false
|
||||||
@ -321,7 +343,7 @@ func (d *DatabaseClientImpl) CreateTable(ctx context.Context, schema string) err
|
|||||||
}
|
}
|
||||||
|
|
||||||
return d.withRetry(func(conn *gorqlite.Connection) error {
|
return d.withRetry(func(conn *gorqlite.Connection) error {
|
||||||
_, err := conn.WriteOne(schema)
|
_, err := safeWriteOne(conn, schema)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -334,7 +356,7 @@ func (d *DatabaseClientImpl) DropTable(ctx context.Context, tableName string) er
|
|||||||
|
|
||||||
return d.withRetry(func(conn *gorqlite.Connection) error {
|
return d.withRetry(func(conn *gorqlite.Connection) error {
|
||||||
dropSQL := fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName)
|
dropSQL := fmt.Sprintf("DROP TABLE IF EXISTS %s", tableName)
|
||||||
_, err := conn.WriteOne(dropSQL)
|
_, err := safeWriteOne(conn, dropSQL)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/DeBrosOfficial/network/pkg/logging"
|
"github.com/DeBrosOfficial/network/pkg/logging"
|
||||||
|
"github.com/DeBrosOfficial/network/pkg/rqlite"
|
||||||
"github.com/DeBrosOfficial/network/pkg/wireguard"
|
"github.com/DeBrosOfficial/network/pkg/wireguard"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
@ -57,7 +58,7 @@ func (n *Node) registerDNSNode(ctx context.Context) error {
|
|||||||
`
|
`
|
||||||
|
|
||||||
db := n.rqliteAdapter.GetSQLDB()
|
db := n.rqliteAdapter.GetSQLDB()
|
||||||
_, err = db.ExecContext(ctx, query, nodeID, ipAddress, internalIP, region)
|
_, err = rqlite.SafeExecContext(db, ctx, query, nodeID, ipAddress, internalIP, region)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to register DNS node: %w", err)
|
return fmt.Errorf("failed to register DNS node: %w", err)
|
||||||
}
|
}
|
||||||
@ -112,7 +113,7 @@ func (n *Node) updateDNSHeartbeat(ctx context.Context) error {
|
|||||||
|
|
||||||
query := `UPDATE dns_nodes SET last_seen = datetime('now'), updated_at = datetime('now') WHERE id = ?`
|
query := `UPDATE dns_nodes SET last_seen = datetime('now'), updated_at = datetime('now') WHERE id = ?`
|
||||||
db := n.rqliteAdapter.GetSQLDB()
|
db := n.rqliteAdapter.GetSQLDB()
|
||||||
_, err := db.ExecContext(ctx, query, nodeID)
|
_, err := rqlite.SafeExecContext(db, ctx, query, nodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to update DNS heartbeat: %w", err)
|
return fmt.Errorf("failed to update DNS heartbeat: %w", err)
|
||||||
}
|
}
|
||||||
@ -177,7 +178,7 @@ func (n *Node) ensureBaseDNSRecords(ctx context.Context) error {
|
|||||||
query := `INSERT INTO dns_records (fqdn, record_type, value, ttl, namespace, created_by, is_active, created_at, updated_at)
|
query := `INSERT INTO dns_records (fqdn, record_type, value, ttl, namespace, created_by, is_active, created_at, updated_at)
|
||||||
VALUES (?, 'A', ?, 300, 'system', 'system', TRUE, datetime('now'), datetime('now'))
|
VALUES (?, 'A', ?, 300, 'system', 'system', TRUE, datetime('now'), datetime('now'))
|
||||||
ON CONFLICT(fqdn, record_type, value) DO NOTHING`
|
ON CONFLICT(fqdn, record_type, value) DO NOTHING`
|
||||||
if _, err := db.ExecContext(ctx, query, r.fqdn, r.value); err != nil {
|
if _, err := rqlite.SafeExecContext(db, ctx, query, r.fqdn, r.value); err != nil {
|
||||||
n.logger.ComponentWarn(logging.ComponentNode, "Failed to ensure DNS record",
|
n.logger.ComponentWarn(logging.ComponentNode, "Failed to ensure DNS record",
|
||||||
zap.String("fqdn", r.fqdn), zap.Error(err))
|
zap.String("fqdn", r.fqdn), zap.Error(err))
|
||||||
}
|
}
|
||||||
@ -219,7 +220,7 @@ func (n *Node) ensureSOAAndNSRecords(ctx context.Context, baseDomain string) {
|
|||||||
// Create SOA record
|
// Create SOA record
|
||||||
soaValue := fmt.Sprintf("ns1.%s. admin.%s. %d 3600 1800 604800 300",
|
soaValue := fmt.Sprintf("ns1.%s. admin.%s. %d 3600 1800 604800 300",
|
||||||
baseDomain, baseDomain, time.Now().Unix())
|
baseDomain, baseDomain, time.Now().Unix())
|
||||||
if _, err := db.ExecContext(ctx,
|
if _, err := rqlite.SafeExecContext(db, ctx,
|
||||||
`INSERT INTO dns_records (fqdn, record_type, value, ttl, namespace, created_by, is_active, created_at, updated_at)
|
`INSERT INTO dns_records (fqdn, record_type, value, ttl, namespace, created_by, is_active, created_at, updated_at)
|
||||||
VALUES (?, 'SOA', ?, 300, 'system', 'system', TRUE, datetime('now'), datetime('now'))
|
VALUES (?, 'SOA', ?, 300, 'system', 'system', TRUE, datetime('now'), datetime('now'))
|
||||||
ON CONFLICT(fqdn, record_type, value) DO NOTHING`,
|
ON CONFLICT(fqdn, record_type, value) DO NOTHING`,
|
||||||
@ -231,7 +232,7 @@ func (n *Node) ensureSOAAndNSRecords(ctx context.Context, baseDomain string) {
|
|||||||
// Create NS records (ns1, ns2, ns3)
|
// Create NS records (ns1, ns2, ns3)
|
||||||
for i := 1; i <= 3; i++ {
|
for i := 1; i <= 3; i++ {
|
||||||
nsValue := fmt.Sprintf("ns%d.%s.", i, baseDomain)
|
nsValue := fmt.Sprintf("ns%d.%s.", i, baseDomain)
|
||||||
if _, err := db.ExecContext(ctx,
|
if _, err := rqlite.SafeExecContext(db, ctx,
|
||||||
`INSERT INTO dns_records (fqdn, record_type, value, ttl, namespace, created_by, is_active, created_at, updated_at)
|
`INSERT INTO dns_records (fqdn, record_type, value, ttl, namespace, created_by, is_active, created_at, updated_at)
|
||||||
VALUES (?, 'NS', ?, 300, 'system', 'system', TRUE, datetime('now'), datetime('now'))
|
VALUES (?, 'NS', ?, 300, 'system', 'system', TRUE, datetime('now'), datetime('now'))
|
||||||
ON CONFLICT(fqdn, record_type, value) DO NOTHING`,
|
ON CONFLICT(fqdn, record_type, value) DO NOTHING`,
|
||||||
@ -257,7 +258,7 @@ func (n *Node) claimNameserverSlot(ctx context.Context, domain, ipAddress string
|
|||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
// Already claimed — update IP if changed
|
// Already claimed — update IP if changed
|
||||||
if _, err := db.ExecContext(ctx,
|
if _, err := rqlite.SafeExecContext(db, ctx,
|
||||||
`UPDATE dns_nameservers SET ip_address = ?, updated_at = datetime('now') WHERE hostname = ? AND domain = ?`,
|
`UPDATE dns_nameservers SET ip_address = ?, updated_at = datetime('now') WHERE hostname = ? AND domain = ?`,
|
||||||
ipAddress, existingHostname, domain,
|
ipAddress, existingHostname, domain,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@ -265,7 +266,7 @@ func (n *Node) claimNameserverSlot(ctx context.Context, domain, ipAddress string
|
|||||||
}
|
}
|
||||||
// Ensure the glue A record matches
|
// Ensure the glue A record matches
|
||||||
nsFQDN := existingHostname + "." + domain + "."
|
nsFQDN := existingHostname + "." + domain + "."
|
||||||
if _, err := db.ExecContext(ctx,
|
if _, err := rqlite.SafeExecContext(db, ctx,
|
||||||
`INSERT INTO dns_records (fqdn, record_type, value, ttl, namespace, created_by, is_active, created_at, updated_at)
|
`INSERT INTO dns_records (fqdn, record_type, value, ttl, namespace, created_by, is_active, created_at, updated_at)
|
||||||
VALUES (?, 'A', ?, 300, 'system', 'system', TRUE, datetime('now'), datetime('now'))
|
VALUES (?, 'A', ?, 300, 'system', 'system', TRUE, datetime('now'), datetime('now'))
|
||||||
ON CONFLICT(fqdn, record_type, value) DO NOTHING`,
|
ON CONFLICT(fqdn, record_type, value) DO NOTHING`,
|
||||||
@ -278,7 +279,7 @@ func (n *Node) claimNameserverSlot(ctx context.Context, domain, ipAddress string
|
|||||||
|
|
||||||
// Try to claim an available slot
|
// Try to claim an available slot
|
||||||
for _, hostname := range []string{"ns1", "ns2", "ns3"} {
|
for _, hostname := range []string{"ns1", "ns2", "ns3"} {
|
||||||
result, err := db.ExecContext(ctx,
|
result, err := rqlite.SafeExecContext(db, ctx,
|
||||||
`INSERT INTO dns_nameservers (hostname, node_id, ip_address, domain) VALUES (?, ?, ?, ?)
|
`INSERT INTO dns_nameservers (hostname, node_id, ip_address, domain) VALUES (?, ?, ?, ?)
|
||||||
ON CONFLICT(hostname) DO NOTHING`,
|
ON CONFLICT(hostname) DO NOTHING`,
|
||||||
hostname, nodeID, ipAddress, domain,
|
hostname, nodeID, ipAddress, domain,
|
||||||
@ -290,7 +291,7 @@ func (n *Node) claimNameserverSlot(ctx context.Context, domain, ipAddress string
|
|||||||
if rows > 0 {
|
if rows > 0 {
|
||||||
// Successfully claimed this slot — create glue record
|
// Successfully claimed this slot — create glue record
|
||||||
nsFQDN := hostname + "." + domain + "."
|
nsFQDN := hostname + "." + domain + "."
|
||||||
if _, err := db.ExecContext(ctx,
|
if _, err := rqlite.SafeExecContext(db, ctx,
|
||||||
`INSERT INTO dns_records (fqdn, record_type, value, ttl, namespace, created_by, is_active, created_at, updated_at)
|
`INSERT INTO dns_records (fqdn, record_type, value, ttl, namespace, created_by, is_active, created_at, updated_at)
|
||||||
VALUES (?, 'A', ?, 300, 'system', 'system', TRUE, datetime('now'), datetime('now'))
|
VALUES (?, 'A', ?, 300, 'system', 'system', TRUE, datetime('now'), datetime('now'))
|
||||||
ON CONFLICT(fqdn, record_type, value) DO NOTHING`,
|
ON CONFLICT(fqdn, record_type, value) DO NOTHING`,
|
||||||
@ -347,27 +348,27 @@ func (n *Node) cleanupStaleNodeRecords(ctx context.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Mark node as inactive
|
// Mark node as inactive
|
||||||
if _, err := db.ExecContext(ctx, `UPDATE dns_nodes SET status = 'inactive', updated_at = datetime('now') WHERE id = ?`, nodeID); err != nil {
|
if _, err := rqlite.SafeExecContext(db, ctx, `UPDATE dns_nodes SET status = 'inactive', updated_at = datetime('now') WHERE id = ?`, nodeID); err != nil {
|
||||||
n.logger.ComponentWarn(logging.ComponentNode, "Failed to mark node inactive", zap.String("node_id", nodeID), zap.Error(err))
|
n.logger.ComponentWarn(logging.ComponentNode, "Failed to mark node inactive", zap.String("node_id", nodeID), zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the dead node's A records from round-robin
|
// Remove the dead node's A records from round-robin
|
||||||
for _, f := range fqdnsToClean {
|
for _, f := range fqdnsToClean {
|
||||||
if _, err := db.ExecContext(ctx, `DELETE FROM dns_records WHERE fqdn = ? AND record_type = 'A' AND value = ? AND namespace = 'system'`, f, ip); err != nil {
|
if _, err := rqlite.SafeExecContext(db, ctx, `DELETE FROM dns_records WHERE fqdn = ? AND record_type = 'A' AND value = ? AND namespace = 'system'`, f, ip); err != nil {
|
||||||
n.logger.ComponentWarn(logging.ComponentNode, "Failed to remove stale DNS record",
|
n.logger.ComponentWarn(logging.ComponentNode, "Failed to remove stale DNS record",
|
||||||
zap.String("fqdn", f), zap.String("ip", ip), zap.Error(err))
|
zap.String("fqdn", f), zap.String("ip", ip), zap.Error(err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release any NS slot held by this dead node
|
// Release any NS slot held by this dead node
|
||||||
if _, err := db.ExecContext(ctx, `DELETE FROM dns_nameservers WHERE node_id = ?`, nodeID); err != nil {
|
if _, err := rqlite.SafeExecContext(db, ctx, `DELETE FROM dns_nameservers WHERE node_id = ?`, nodeID); err != nil {
|
||||||
n.logger.ComponentWarn(logging.ComponentNode, "Failed to release NS slot", zap.String("node_id", nodeID), zap.Error(err))
|
n.logger.ComponentWarn(logging.ComponentNode, "Failed to release NS slot", zap.String("node_id", nodeID), zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove glue records for this node's IP (ns1.domain., ns2.domain., ns3.domain.)
|
// Remove glue records for this node's IP (ns1.domain., ns2.domain., ns3.domain.)
|
||||||
for _, ns := range []string{"ns1", "ns2", "ns3"} {
|
for _, ns := range []string{"ns1", "ns2", "ns3"} {
|
||||||
nsFQDN := ns + "." + baseDomain + "."
|
nsFQDN := ns + "." + baseDomain + "."
|
||||||
if _, err := db.ExecContext(ctx,
|
if _, err := rqlite.SafeExecContext(db, ctx,
|
||||||
`DELETE FROM dns_records WHERE fqdn = ? AND record_type = 'A' AND value = ? AND namespace = 'system'`,
|
`DELETE FROM dns_records WHERE fqdn = ? AND record_type = 'A' AND value = ? AND namespace = 'system'`,
|
||||||
nsFQDN, ip,
|
nsFQDN, ip,
|
||||||
); err != nil {
|
); err != nil {
|
||||||
@ -484,7 +485,7 @@ func cleanupPrivateIPRecords(ctx context.Context, db *sql.DB, logger *logging.Co
|
|||||||
AND (value LIKE '10.%' OR value LIKE '172.16.%' OR value LIKE '172.17.%' OR value LIKE '172.18.%'
|
AND (value LIKE '10.%' OR value LIKE '172.16.%' OR value LIKE '172.17.%' OR value LIKE '172.18.%'
|
||||||
OR value LIKE '172.19.%' OR value LIKE '172.2_.%' OR value LIKE '172.30.%' OR value LIKE '172.31.%'
|
OR value LIKE '172.19.%' OR value LIKE '172.2_.%' OR value LIKE '172.30.%' OR value LIKE '172.31.%'
|
||||||
OR value LIKE '192.168.%' OR value = '127.0.0.1')`
|
OR value LIKE '192.168.%' OR value = '127.0.0.1')`
|
||||||
result, err := db.ExecContext(ctx, query)
|
result, err := rqlite.SafeExecContext(db, ctx, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ComponentWarn(logging.ComponentNode, "Failed to clean up private IP DNS records", zap.Error(err))
|
logger.ComponentWarn(logging.ComponentNode, "Failed to clean up private IP DNS records", zap.Error(err))
|
||||||
return
|
return
|
||||||
|
|||||||
@ -12,6 +12,7 @@ import (
|
|||||||
|
|
||||||
"github.com/DeBrosOfficial/network/pkg/environments/production"
|
"github.com/DeBrosOfficial/network/pkg/environments/production"
|
||||||
"github.com/DeBrosOfficial/network/pkg/logging"
|
"github.com/DeBrosOfficial/network/pkg/logging"
|
||||||
|
"github.com/DeBrosOfficial/network/pkg/rqlite"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -166,13 +167,13 @@ func (n *Node) ensureWireGuardSelfRegistered(ctx context.Context) {
|
|||||||
// Clean up stale entries for this public IP with a different node_id.
|
// Clean up stale entries for this public IP with a different node_id.
|
||||||
// This prevents ghost peers from previous installs or from the temporary
|
// This prevents ghost peers from previous installs or from the temporary
|
||||||
// "node-10.0.0.X" ID that the join handler creates.
|
// "node-10.0.0.X" ID that the join handler creates.
|
||||||
if _, err := db.ExecContext(ctx,
|
if _, err := rqlite.SafeExecContext(db, ctx,
|
||||||
"DELETE FROM wireguard_peers WHERE public_ip = ? AND node_id != ?",
|
"DELETE FROM wireguard_peers WHERE public_ip = ? AND node_id != ?",
|
||||||
publicIP, nodeID); err != nil {
|
publicIP, nodeID); err != nil {
|
||||||
n.logger.ComponentWarn(logging.ComponentNode, "Failed to clean stale WG entries", zap.Error(err))
|
n.logger.ComponentWarn(logging.ComponentNode, "Failed to clean stale WG entries", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.ExecContext(ctx,
|
_, err = rqlite.SafeExecContext(db, ctx,
|
||||||
"INSERT OR REPLACE INTO wireguard_peers (node_id, wg_ip, public_key, public_ip, wg_port, ipfs_peer_id) VALUES (?, ?, ?, ?, ?, ?)",
|
"INSERT OR REPLACE INTO wireguard_peers (node_id, wg_ip, public_key, public_ip, wg_port, ipfs_peer_id) VALUES (?, ?, ?, ?, ?, ?)",
|
||||||
nodeID, wgIP, localPubKey, publicIP, 51820, ipfsPeerID)
|
nodeID, wgIP, localPubKey, publicIP, 51820, ipfsPeerID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@ -35,7 +35,14 @@ func (c *client) Query(ctx context.Context, dest any, query string, args ...any)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Exec runs a write statement (INSERT/UPDATE/DELETE).
|
// Exec runs a write statement (INSERT/UPDATE/DELETE).
|
||||||
func (c *client) Exec(ctx context.Context, query string, args ...any) (sql.Result, error) {
|
// Includes panic recovery because the gorqlite stdlib driver can panic
|
||||||
|
// with "index out of range" when RQLite is temporarily unavailable.
|
||||||
|
func (c *client) Exec(ctx context.Context, query string, args ...any) (result sql.Result, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("gorqlite panic (ExecContext): %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
return c.db.ExecContext(ctx, query, args...)
|
return c.db.ExecContext(ctx, query, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
19
pkg/rqlite/safe_exec.go
Normal file
19
pkg/rqlite/safe_exec.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
package rqlite
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SafeExecContext wraps db.ExecContext with panic recovery.
|
||||||
|
// The gorqlite stdlib driver can panic with "index out of range" when
|
||||||
|
// RQLite is temporarily unavailable. This converts the panic to an error.
|
||||||
|
func SafeExecContext(db *sql.DB, ctx context.Context, query string, args ...interface{}) (result sql.Result, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = fmt.Errorf("gorqlite panic (ExecContext): %v", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
return db.ExecContext(ctx, query, args...)
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user