You've already forked coredns-powerdns-api-wrapper
first commit
This commit is contained in:
123
store.go
Normal file
123
store.go
Normal file
@@ -0,0 +1,123 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
// Copyright (C) 2026 Bryan Joshua Pedini
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Store wraps DB access against the {TablePrefix}records table (schema per
|
||||
// docs/SEMANTICS.md §1).
|
||||
type Store struct {
|
||||
db *sql.DB
|
||||
tableName string
|
||||
}
|
||||
|
||||
// NewStore builds a Store bound to {prefix}records.
|
||||
func NewStore(db *sql.DB, tablePrefix string) *Store {
|
||||
return &Store{
|
||||
db: db,
|
||||
tableName: tablePrefix + "records",
|
||||
}
|
||||
}
|
||||
|
||||
// Record is one row of {prefix}records.
|
||||
type Record struct {
|
||||
Name string
|
||||
TTL sql.NullInt64
|
||||
Content string
|
||||
RecordType string
|
||||
}
|
||||
|
||||
// ListZones returns distinct zones present in the table, ordered by name.
|
||||
func (st *Store) ListZones(ctx context.Context) ([]string, error) {
|
||||
query := fmt.Sprintf("SELECT DISTINCT zone FROM %s ORDER BY zone", st.tableName)
|
||||
rows, err := st.db.QueryContext(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var zones []string
|
||||
for rows.Next() {
|
||||
var zone string
|
||||
if err := rows.Scan(&zone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zones = append(zones, zone)
|
||||
}
|
||||
return zones, rows.Err()
|
||||
}
|
||||
|
||||
// GetZoneRecords returns every row for the given zone.
|
||||
func (st *Store) GetZoneRecords(ctx context.Context, zone string) ([]Record, error) {
|
||||
query := fmt.Sprintf("SELECT name, ttl, content, record_type FROM %s WHERE zone = ?", st.tableName)
|
||||
rows, err := st.db.QueryContext(ctx, query, zone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var records []Record
|
||||
for rows.Next() {
|
||||
var rec Record
|
||||
if err := rows.Scan(&rec.Name, &rec.TTL, &rec.Content, &rec.RecordType); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
records = append(records, rec)
|
||||
}
|
||||
return records, rows.Err()
|
||||
}
|
||||
|
||||
// ZoneExists reports whether at least one row exists for the zone.
|
||||
func (st *Store) ZoneExists(ctx context.Context, zone string) (bool, error) {
|
||||
query := fmt.Sprintf("SELECT EXISTS(SELECT 1 FROM %s WHERE zone = ?)", st.tableName)
|
||||
var exists bool
|
||||
if err := st.db.QueryRowContext(ctx, query, zone).Scan(&exists); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
// RRsetChange is one decomposed rrset change ready to apply to the DB.
|
||||
type RRsetChange struct {
|
||||
Zone string
|
||||
Name string // relative name, per SEMANTICS §1 (empty string at apex)
|
||||
RecordType string
|
||||
ChangeType string // REPLACE or DELETE
|
||||
TTL int
|
||||
Contents []string // marshalled JSON content, one per record; unused for DELETE
|
||||
}
|
||||
|
||||
// ApplyRRsets runs every rrset change of one PATCH inside a single
|
||||
// transaction. REPLACE deletes existing rows for (zone, name, record_type)
|
||||
// then inserts one row per record. DELETE just deletes. Any error rolls
|
||||
// back the entire transaction.
|
||||
func (st *Store) ApplyRRsets(ctx context.Context, changes []RRsetChange) error {
|
||||
tx, err := st.db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
deleteQuery := fmt.Sprintf("DELETE FROM %s WHERE zone = ? AND name = ? AND record_type = ?", st.tableName)
|
||||
insertQuery := fmt.Sprintf("INSERT INTO %s (zone, name, ttl, content, record_type) VALUES (?, ?, ?, ?, ?)", st.tableName)
|
||||
|
||||
for _, ch := range changes {
|
||||
if _, err := tx.ExecContext(ctx, deleteQuery, ch.Zone, ch.Name, ch.RecordType); err != nil {
|
||||
return err
|
||||
}
|
||||
if ch.ChangeType == "REPLACE" {
|
||||
for _, content := range ch.Contents {
|
||||
if _, err := tx.ExecContext(ctx, insertQuery, ch.Zone, ch.Name, ch.TTL, content, ch.RecordType); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return tx.Commit()
|
||||
}
|
||||
Reference in New Issue
Block a user