This commit is contained in:
Daniel Ponte 2024-08-06 11:19:30 -04:00
parent a7acacda15
commit 93c742895f
8 changed files with 155 additions and 22 deletions

90
pkg/calls/filter.go Normal file
View file

@ -0,0 +1,90 @@
package calls
type filterQuery struct {
Query string
Params []interface{}
}
type Talkgroup struct {
System uint32
Talkgroup uint32
}
func (t Talkgroup) Pack() int64 {
// P25 system IDs are 12 bits, so we can fit them in a signed 8 byte int (int64, pg INT8)
return int64((int64(t.System) << 32) | int64(t.Talkgroup))
}
type Filter struct {
Talkgroups []Talkgroup `json:"talkgroups"`
TalkgroupsNot []Talkgroup `json:"talkgroupsNot"`
TalkgroupTagsAll []string `json:"talkgroupTagsAll"`
TalkgroupTagsAny []string `json:"talkgroupTagsAny"`
TalkgroupTagsNot []string `json:"talkgroupTagsNot"`
talkgroups map[Talkgroup]bool
talkgroupTagsAll map[string]bool
talkgroupTagsAny map[string]bool
talkgroupTagsNot map[string]bool
query *filterQuery
}
func queryParams(s string, p ...any) (string, []any) {
return s, p
}
func (f *Filter) filterQuery() filterQuery {
var q string
var args []interface{}
q, args = queryParams(
`((talkgroups.id = ANY(?) OR talkgroups.tags @> ARRAY[?]) OR (talkgroups.tags && ARRAY[?])) AND (talkgroups.id != ANY(?) AND NOT talkgroups.tags @> ARRAY[?])`,
f.Talkgroups, f.TalkgroupTagsAny, f.TalkgroupTagsAll, f.TalkgroupsNot, f.TalkgroupTagsNot)
return filterQuery{Query: q, Params: args}
}
func (f *Filter) Packed(tg []Talkgroup) []int64 {
s := make([]int64, len(f.Talkgroups))
for i, v := range tg {
s[i] = v.Pack()
}
return s
}
func (f *Filter) Compile() *Filter {
f.talkgroups = make(map[Talkgroup]bool)
for _, tg := range f.Talkgroups {
f.talkgroups[tg] = true
}
for _, tg := range f.TalkgroupsNot {
f.talkgroups[tg] = false
}
f.talkgroupTagsAll = make(map[string]bool)
for _, tag := range f.TalkgroupTagsAll {
f.talkgroupTagsAll[tag] = true
}
f.talkgroupTagsAny = make(map[string]bool)
for _, tag := range f.TalkgroupTagsAny {
f.talkgroupTagsAny[tag] = true
}
for _, tag := range f.TalkgroupTagsNot {
f.talkgroupTagsNot[tag] = true
}
q := f.filterQuery()
f.query = &q
return f
}
type FilterCache struct {
cache map[string]Filter
}

View file

@ -13,6 +13,8 @@ import (
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )
// This file will eventually turn into a postgres driver.
// DB is a database handle. // DB is a database handle.
type DB struct { type DB struct {
*pgxpool.Pool *pgxpool.Pool

View file

@ -68,8 +68,9 @@ type System struct {
} }
type Talkgroup struct { type Talkgroup struct {
ID int64 `json:"id"`
SystemID int `json:"system_id"` SystemID int `json:"system_id"`
Tgid int `json:"tgid"` Tgid *int32 `json:"tgid"`
Name *string `json:"name"` Name *string `json:"name"`
TgGroup *string `json:"tg_group"` TgGroup *string `json:"tg_group"`
Frequency *int32 `json:"frequency"` Frequency *int32 `json:"frequency"`

View file

@ -13,12 +13,13 @@ import (
type Querier interface { type Querier interface {
AddCall(ctx context.Context, arg AddCallParams) (uuid.UUID, error) AddCall(ctx context.Context, arg AddCallParams) (uuid.UUID, error)
BulkSetTalkgroupTags(ctx context.Context, iD int64, tags []string) error
CreateAPIKey(ctx context.Context, owner int, expires pgtype.Timestamp, disabled *bool) (ApiKey, error) CreateAPIKey(ctx context.Context, owner int, expires pgtype.Timestamp, disabled *bool) (ApiKey, error)
CreateUser(ctx context.Context, arg CreateUserParams) (User, error) CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
DeleteAPIKey(ctx context.Context, apiKey string) error DeleteAPIKey(ctx context.Context, apiKey string) error
DeleteUser(ctx context.Context, username string) error DeleteUser(ctx context.Context, username string) error
GetAPIKey(ctx context.Context, apiKey string) (ApiKey, error) GetAPIKey(ctx context.Context, apiKey string) (ApiKey, error)
GetTalkgroupTags(ctx context.Context, systemID int, tgid int) ([]string, error) GetTalkgroupTags(ctx context.Context, sys int, tg int) ([]string, error)
GetTalkgroupsWithAllTags(ctx context.Context, tags []string) ([]Talkgroup, error) GetTalkgroupsWithAllTags(ctx context.Context, tags []string) ([]Talkgroup, error)
GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) ([]Talkgroup, error) GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) ([]Talkgroup, error)
GetUserByID(ctx context.Context, id int32) (User, error) GetUserByID(ctx context.Context, id int32) (User, error)
@ -26,7 +27,7 @@ type Querier interface {
GetUserByUsername(ctx context.Context, username string) (User, error) GetUserByUsername(ctx context.Context, username string) (User, error)
GetUsers(ctx context.Context) ([]User, error) GetUsers(ctx context.Context) ([]User, error)
SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript *string) error SetCallTranscript(ctx context.Context, iD uuid.UUID, transcript *string) error
SetTalkgroupTags(ctx context.Context, tags []string, tgid int) error SetTalkgroupTags(ctx context.Context, sys int, tg int, tags []string) error
UpdatePassword(ctx context.Context, username string, password string) error UpdatePassword(ctx context.Context, username string, password string) error
} }

View file

@ -9,20 +9,30 @@ import (
"context" "context"
) )
const getTalkgroupTags = `-- name: GetTalkgroupTags :one const bulkSetTalkgroupTags = `-- name: BulkSetTalkgroupTags :exec
SELECT tags FROM talkgroups UPDATE talkgroups SET tags = $2
WHERE system_id = $1 AND tgid = $2 WHERE id = ANY($1)
` `
func (q *Queries) GetTalkgroupTags(ctx context.Context, systemID int, tgid int) ([]string, error) { func (q *Queries) BulkSetTalkgroupTags(ctx context.Context, iD int64, tags []string) error {
row := q.db.QueryRow(ctx, getTalkgroupTags, systemID, tgid) _, err := q.db.Exec(ctx, bulkSetTalkgroupTags, iD, tags)
return err
}
const getTalkgroupTags = `-- name: GetTalkgroupTags :one
SELECT tags FROM talkgroups
WHERE id = systg2id($1, $2)
`
func (q *Queries) GetTalkgroupTags(ctx context.Context, sys int, tg int) ([]string, error) {
row := q.db.QueryRow(ctx, getTalkgroupTags, sys, tg)
var tags []string var tags []string
err := row.Scan(&tags) err := row.Scan(&tags)
return tags, err return tags, err
} }
const getTalkgroupsWithAllTags = `-- name: GetTalkgroupsWithAllTags :many const getTalkgroupsWithAllTags = `-- name: GetTalkgroupsWithAllTags :many
SELECT system_id, tgid, name, tg_group, frequency, metadata, tags FROM talkgroups SELECT id, system_id, tgid, name, tg_group, frequency, metadata, tags FROM talkgroups
WHERE tags && ARRAY[$1] WHERE tags && ARRAY[$1]
` `
@ -36,6 +46,7 @@ func (q *Queries) GetTalkgroupsWithAllTags(ctx context.Context, tags []string) (
for rows.Next() { for rows.Next() {
var i Talkgroup var i Talkgroup
if err := rows.Scan( if err := rows.Scan(
&i.ID,
&i.SystemID, &i.SystemID,
&i.Tgid, &i.Tgid,
&i.Name, &i.Name,
@ -55,7 +66,7 @@ func (q *Queries) GetTalkgroupsWithAllTags(ctx context.Context, tags []string) (
} }
const getTalkgroupsWithAnyTags = `-- name: GetTalkgroupsWithAnyTags :many const getTalkgroupsWithAnyTags = `-- name: GetTalkgroupsWithAnyTags :many
SELECT system_id, tgid, name, tg_group, frequency, metadata, tags FROM talkgroups SELECT id, system_id, tgid, name, tg_group, frequency, metadata, tags FROM talkgroups
WHERE tags @> ARRAY[$1] WHERE tags @> ARRAY[$1]
` `
@ -69,6 +80,7 @@ func (q *Queries) GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) (
for rows.Next() { for rows.Next() {
var i Talkgroup var i Talkgroup
if err := rows.Scan( if err := rows.Scan(
&i.ID,
&i.SystemID, &i.SystemID,
&i.Tgid, &i.Tgid,
&i.Name, &i.Name,
@ -88,11 +100,11 @@ func (q *Queries) GetTalkgroupsWithAnyTags(ctx context.Context, tags []string) (
} }
const setTalkgroupTags = `-- name: SetTalkgroupTags :exec const setTalkgroupTags = `-- name: SetTalkgroupTags :exec
UPDATE talkgroups SET tags = $1 UPDATE talkgroups SET tags = $3
WHERE system_id = $1 AND tgid = $2 WHERE id = systg2id($1, $2)
` `
func (q *Queries) SetTalkgroupTags(ctx context.Context, tags []string, tgid int) error { func (q *Queries) SetTalkgroupTags(ctx context.Context, sys int, tg int, tags []string) error {
_, err := q.db.Exec(ctx, setTalkgroupTags, tags, tgid) _, err := q.db.Exec(ctx, setTalkgroupTags, sys, tg, tags)
return err return err
} }

View file

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.34.2 // protoc-gen-go v1.34.2
// protoc v5.27.1 // protoc v5.27.2
// source: stillbox.proto // source: stillbox.proto
package pb package pb

View file

@ -23,19 +23,42 @@ CREATE TABLE IF NOT EXISTS systems(
name TEXT NOT NULL name TEXT NOT NULL
); );
CREATE OR REPLACE FUNCTION systg2id(_sys INTEGER, _tg INTEGER) RETURNS INT8 LANGUAGE plpgsql AS
$$
BEGIN
RETURN ((_sys::BIGINT << 32) | _tg);
END
$$;
CREATE OR REPLACE FUNCTION tgfromid(_id INT8) RETURNS INTEGER LANGUAGE plpgsql AS
$$
BEGIN
RETURN (_id & x'ffffffff'::BIGINT);
END
$$;
CREATE OR REPLACE FUNCTION sysfromid(_id INT8) RETURNS INTEGER LANGUAGE plpgsql AS
$$
BEGIN
RETURN (_id >> 32);
END
$$;
CREATE TABLE IF NOT EXISTS talkgroups( CREATE TABLE IF NOT EXISTS talkgroups(
system_id INTEGER REFERENCES systems(id) NOT NULL, id INT8 PRIMARY KEY,
tgid INTEGER, system_id INTEGER REFERENCES systems(id) NOT NULL GENERATED ALWAYS AS (id >> 32) STORED,
tgid INTEGER GENERATED ALWAYS AS (id & x'ffffffff'::BIGINT) STORED,
name TEXT, name TEXT,
tg_group TEXT, tg_group TEXT,
frequency INTEGER, frequency INTEGER,
metadata JSONB, metadata JSONB,
tags TEXT[] NOT NULL DEFAULT '{}', tags TEXT[] NOT NULL DEFAULT '{}'
PRIMARY KEY (system_id, tgid)
); );
CREATE INDEX IF NOT EXISTS talkgroup_id_tags ON talkgroups USING GIN (tags); CREATE INDEX IF NOT EXISTS talkgroup_id_tags ON talkgroups USING GIN (tags);
CREATE TABLE IF NOT EXISTS talkgroups_learned( CREATE TABLE IF NOT EXISTS talkgroups_learned(
id SERIAL PRIMARY KEY, id SERIAL PRIMARY KEY,
system_id INTEGER REFERENCES systems(id) NOT NULL, system_id INTEGER REFERENCES systems(id) NOT NULL,

View file

@ -8,8 +8,12 @@ WHERE tags && ARRAY[$1];
-- name: GetTalkgroupTags :one -- name: GetTalkgroupTags :one
SELECT tags FROM talkgroups SELECT tags FROM talkgroups
WHERE system_id = $1 AND tgid = $2; WHERE id = systg2id($1, $2);
-- name: SetTalkgroupTags :exec -- name: SetTalkgroupTags :exec
UPDATE talkgroups SET tags = $1 UPDATE talkgroups SET tags = $3
WHERE system_id = $1 AND tgid = $2; WHERE id = systg2id($1, $2);
-- name: BulkSetTalkgroupTags :exec
UPDATE talkgroups SET tags = $2
WHERE id = ANY($1);