2026-01-20 21:52:44 +00:00
|
|
|
package database
|
|
|
|
|
|
|
|
|
|
import "fmt"
|
|
|
|
|
|
2026-03-18 10:59:29 +00:00
|
|
|
const postgresTimestamp = "TIMESTAMP"
|
|
|
|
|
|
2026-01-29 16:44:01 +00:00
|
|
|
// Schema for proxy-specific tables. The packages and versions tables
|
|
|
|
|
// are compatible with git-pkgs, allowing the proxy to use an existing
|
|
|
|
|
// git-pkgs database as a starting point.
|
|
|
|
|
|
2026-01-29 16:06:56 +00:00
|
|
|
var schemaSQLite = `
|
|
|
|
|
CREATE TABLE IF NOT EXISTS schema_info (
|
|
|
|
|
version INTEGER NOT NULL
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS packages (
|
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
|
purl TEXT NOT NULL,
|
|
|
|
|
ecosystem TEXT NOT NULL,
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
latest_version TEXT,
|
|
|
|
|
license TEXT,
|
|
|
|
|
description TEXT,
|
|
|
|
|
homepage TEXT,
|
|
|
|
|
repository_url TEXT,
|
2026-01-29 16:44:01 +00:00
|
|
|
registry_url TEXT,
|
|
|
|
|
supplier_name TEXT,
|
|
|
|
|
supplier_type TEXT,
|
|
|
|
|
source TEXT,
|
|
|
|
|
enriched_at DATETIME,
|
|
|
|
|
vulns_synced_at DATETIME,
|
2026-01-29 16:06:56 +00:00
|
|
|
created_at DATETIME,
|
|
|
|
|
updated_at DATETIME
|
|
|
|
|
);
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_packages_purl ON packages(purl);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_packages_ecosystem_name ON packages(ecosystem, name);
|
|
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS versions (
|
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
|
purl TEXT NOT NULL,
|
2026-01-29 16:44:01 +00:00
|
|
|
package_purl TEXT NOT NULL,
|
2026-01-29 16:06:56 +00:00
|
|
|
license TEXT,
|
|
|
|
|
published_at DATETIME,
|
2026-01-29 16:44:01 +00:00
|
|
|
integrity TEXT,
|
2026-01-29 16:06:56 +00:00
|
|
|
yanked INTEGER DEFAULT 0,
|
2026-01-29 16:44:01 +00:00
|
|
|
source TEXT,
|
|
|
|
|
enriched_at DATETIME,
|
2026-01-29 16:06:56 +00:00
|
|
|
created_at DATETIME,
|
|
|
|
|
updated_at DATETIME
|
|
|
|
|
);
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_versions_purl ON versions(purl);
|
2026-01-29 16:44:01 +00:00
|
|
|
CREATE INDEX IF NOT EXISTS idx_versions_package_purl ON versions(package_purl);
|
2026-01-29 16:06:56 +00:00
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS artifacts (
|
|
|
|
|
id INTEGER PRIMARY KEY,
|
2026-01-29 16:44:01 +00:00
|
|
|
version_purl TEXT NOT NULL,
|
2026-01-29 16:06:56 +00:00
|
|
|
filename TEXT NOT NULL,
|
|
|
|
|
upstream_url TEXT NOT NULL,
|
|
|
|
|
storage_path TEXT,
|
|
|
|
|
content_hash TEXT,
|
|
|
|
|
size INTEGER,
|
|
|
|
|
content_type TEXT,
|
|
|
|
|
fetched_at DATETIME,
|
|
|
|
|
hit_count INTEGER DEFAULT 0,
|
|
|
|
|
last_accessed_at DATETIME,
|
|
|
|
|
created_at DATETIME,
|
|
|
|
|
updated_at DATETIME
|
|
|
|
|
);
|
2026-01-29 16:44:01 +00:00
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_artifacts_version_filename ON artifacts(version_purl, filename);
|
2026-01-29 16:06:56 +00:00
|
|
|
CREATE INDEX IF NOT EXISTS idx_artifacts_storage_path ON artifacts(storage_path);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_artifacts_last_accessed ON artifacts(last_accessed_at);
|
2026-01-29 19:35:15 +00:00
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS vulnerabilities (
|
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
|
vuln_id TEXT NOT NULL,
|
|
|
|
|
ecosystem TEXT NOT NULL,
|
|
|
|
|
package_name TEXT NOT NULL,
|
|
|
|
|
severity TEXT,
|
|
|
|
|
summary TEXT,
|
|
|
|
|
fixed_version TEXT,
|
|
|
|
|
cvss_score REAL,
|
|
|
|
|
"references" TEXT,
|
|
|
|
|
fetched_at DATETIME,
|
|
|
|
|
created_at DATETIME,
|
|
|
|
|
updated_at DATETIME
|
|
|
|
|
);
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_vulns_id_pkg ON vulnerabilities(vuln_id, ecosystem, package_name);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_vulns_ecosystem_pkg ON vulnerabilities(ecosystem, package_name);
|
2026-01-29 16:06:56 +00:00
|
|
|
`
|
|
|
|
|
|
|
|
|
|
var schemaPostgres = `
|
|
|
|
|
CREATE TABLE IF NOT EXISTS schema_info (
|
|
|
|
|
version INTEGER NOT NULL
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS packages (
|
|
|
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
|
purl TEXT NOT NULL,
|
|
|
|
|
ecosystem TEXT NOT NULL,
|
|
|
|
|
name TEXT NOT NULL,
|
|
|
|
|
latest_version TEXT,
|
|
|
|
|
license TEXT,
|
|
|
|
|
description TEXT,
|
|
|
|
|
homepage TEXT,
|
|
|
|
|
repository_url TEXT,
|
2026-01-29 16:44:01 +00:00
|
|
|
registry_url TEXT,
|
|
|
|
|
supplier_name TEXT,
|
|
|
|
|
supplier_type TEXT,
|
|
|
|
|
source TEXT,
|
|
|
|
|
enriched_at TIMESTAMP,
|
|
|
|
|
vulns_synced_at TIMESTAMP,
|
2026-01-29 16:06:56 +00:00
|
|
|
created_at TIMESTAMP,
|
|
|
|
|
updated_at TIMESTAMP
|
|
|
|
|
);
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_packages_purl ON packages(purl);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_packages_ecosystem_name ON packages(ecosystem, name);
|
|
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS versions (
|
|
|
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
|
purl TEXT NOT NULL,
|
2026-01-29 16:44:01 +00:00
|
|
|
package_purl TEXT NOT NULL,
|
2026-01-29 16:06:56 +00:00
|
|
|
license TEXT,
|
|
|
|
|
published_at TIMESTAMP,
|
2026-01-29 16:44:01 +00:00
|
|
|
integrity TEXT,
|
2026-01-29 16:06:56 +00:00
|
|
|
yanked BOOLEAN DEFAULT FALSE,
|
2026-01-29 16:44:01 +00:00
|
|
|
source TEXT,
|
|
|
|
|
enriched_at TIMESTAMP,
|
2026-01-29 16:06:56 +00:00
|
|
|
created_at TIMESTAMP,
|
|
|
|
|
updated_at TIMESTAMP
|
|
|
|
|
);
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_versions_purl ON versions(purl);
|
2026-01-29 16:44:01 +00:00
|
|
|
CREATE INDEX IF NOT EXISTS idx_versions_package_purl ON versions(package_purl);
|
|
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS artifacts (
|
|
|
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
|
version_purl TEXT NOT NULL,
|
|
|
|
|
filename TEXT NOT NULL,
|
|
|
|
|
upstream_url TEXT NOT NULL,
|
|
|
|
|
storage_path TEXT,
|
|
|
|
|
content_hash TEXT,
|
|
|
|
|
size BIGINT,
|
|
|
|
|
content_type TEXT,
|
|
|
|
|
fetched_at TIMESTAMP,
|
|
|
|
|
hit_count BIGINT DEFAULT 0,
|
|
|
|
|
last_accessed_at TIMESTAMP,
|
|
|
|
|
created_at TIMESTAMP,
|
|
|
|
|
updated_at TIMESTAMP
|
|
|
|
|
);
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_artifacts_version_filename ON artifacts(version_purl, filename);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_artifacts_storage_path ON artifacts(storage_path);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_artifacts_last_accessed ON artifacts(last_accessed_at);
|
2026-01-29 19:35:15 +00:00
|
|
|
|
|
|
|
|
CREATE TABLE IF NOT EXISTS vulnerabilities (
|
|
|
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
|
vuln_id TEXT NOT NULL,
|
|
|
|
|
ecosystem TEXT NOT NULL,
|
|
|
|
|
package_name TEXT NOT NULL,
|
|
|
|
|
severity TEXT,
|
|
|
|
|
summary TEXT,
|
|
|
|
|
fixed_version TEXT,
|
|
|
|
|
cvss_score REAL,
|
|
|
|
|
"references" TEXT,
|
|
|
|
|
fetched_at TIMESTAMP,
|
|
|
|
|
created_at TIMESTAMP,
|
|
|
|
|
updated_at TIMESTAMP
|
|
|
|
|
);
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_vulns_id_pkg ON vulnerabilities(vuln_id, ecosystem, package_name);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_vulns_ecosystem_pkg ON vulnerabilities(ecosystem, package_name);
|
2026-01-29 16:44:01 +00:00
|
|
|
`
|
|
|
|
|
|
|
|
|
|
// schemaArtifactsOnly contains just the artifacts table for adding to existing git-pkgs databases.
|
|
|
|
|
var schemaArtifactsSQLite = `
|
|
|
|
|
CREATE TABLE IF NOT EXISTS artifacts (
|
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
|
version_purl TEXT NOT NULL,
|
|
|
|
|
filename TEXT NOT NULL,
|
|
|
|
|
upstream_url TEXT NOT NULL,
|
|
|
|
|
storage_path TEXT,
|
|
|
|
|
content_hash TEXT,
|
|
|
|
|
size INTEGER,
|
|
|
|
|
content_type TEXT,
|
|
|
|
|
fetched_at DATETIME,
|
|
|
|
|
hit_count INTEGER DEFAULT 0,
|
|
|
|
|
last_accessed_at DATETIME,
|
|
|
|
|
created_at DATETIME,
|
|
|
|
|
updated_at DATETIME
|
|
|
|
|
);
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_artifacts_version_filename ON artifacts(version_purl, filename);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_artifacts_storage_path ON artifacts(storage_path);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_artifacts_last_accessed ON artifacts(last_accessed_at);
|
|
|
|
|
`
|
2026-01-29 16:06:56 +00:00
|
|
|
|
2026-01-29 16:44:01 +00:00
|
|
|
var schemaArtifactsPostgres = `
|
2026-01-29 16:06:56 +00:00
|
|
|
CREATE TABLE IF NOT EXISTS artifacts (
|
|
|
|
|
id SERIAL PRIMARY KEY,
|
2026-01-29 16:44:01 +00:00
|
|
|
version_purl TEXT NOT NULL,
|
2026-01-29 16:06:56 +00:00
|
|
|
filename TEXT NOT NULL,
|
|
|
|
|
upstream_url TEXT NOT NULL,
|
|
|
|
|
storage_path TEXT,
|
|
|
|
|
content_hash TEXT,
|
|
|
|
|
size BIGINT,
|
|
|
|
|
content_type TEXT,
|
|
|
|
|
fetched_at TIMESTAMP,
|
|
|
|
|
hit_count BIGINT DEFAULT 0,
|
|
|
|
|
last_accessed_at TIMESTAMP,
|
|
|
|
|
created_at TIMESTAMP,
|
|
|
|
|
updated_at TIMESTAMP
|
|
|
|
|
);
|
2026-01-29 16:44:01 +00:00
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_artifacts_version_filename ON artifacts(version_purl, filename);
|
2026-01-29 16:06:56 +00:00
|
|
|
CREATE INDEX IF NOT EXISTS idx_artifacts_storage_path ON artifacts(storage_path);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_artifacts_last_accessed ON artifacts(last_accessed_at);
|
|
|
|
|
`
|
|
|
|
|
|
2026-01-20 21:52:44 +00:00
|
|
|
func (db *DB) CreateSchema() error {
|
|
|
|
|
if err := db.OptimizeForBulkWrites(); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-29 16:06:56 +00:00
|
|
|
var schema string
|
|
|
|
|
if db.dialect == DialectPostgres {
|
|
|
|
|
schema = schemaPostgres
|
|
|
|
|
} else {
|
|
|
|
|
schema = schemaSQLite
|
|
|
|
|
}
|
2026-01-20 21:52:44 +00:00
|
|
|
|
|
|
|
|
if _, err := db.Exec(schema); err != nil {
|
|
|
|
|
return fmt.Errorf("executing schema: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-29 16:06:56 +00:00
|
|
|
query := db.Rebind("INSERT INTO schema_info (version) VALUES (?)")
|
|
|
|
|
if _, err := db.Exec(query, SchemaVersion); err != nil {
|
2026-01-20 21:52:44 +00:00
|
|
|
return fmt.Errorf("setting schema version: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return db.OptimizeForReads()
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-29 16:44:01 +00:00
|
|
|
// EnsureArtifactsTable adds the artifacts table to an existing database
|
|
|
|
|
// (e.g., a git-pkgs database) if it doesn't already exist.
|
|
|
|
|
func (db *DB) EnsureArtifactsTable() error {
|
|
|
|
|
var schema string
|
|
|
|
|
if db.dialect == DialectPostgres {
|
|
|
|
|
schema = schemaArtifactsPostgres
|
|
|
|
|
} else {
|
|
|
|
|
schema = schemaArtifactsSQLite
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if _, err := db.Exec(schema); err != nil {
|
|
|
|
|
return fmt.Errorf("creating artifacts table: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 21:52:44 +00:00
|
|
|
func (db *DB) SchemaVersion() (int, error) {
|
|
|
|
|
var version int
|
2026-01-29 16:06:56 +00:00
|
|
|
err := db.Get(&version, "SELECT version FROM schema_info LIMIT 1")
|
2026-01-20 21:52:44 +00:00
|
|
|
if err != nil {
|
|
|
|
|
return 0, err
|
|
|
|
|
}
|
|
|
|
|
return version, nil
|
|
|
|
|
}
|
2026-01-29 16:44:01 +00:00
|
|
|
|
|
|
|
|
// HasTable checks if a table exists in the database.
|
|
|
|
|
func (db *DB) HasTable(name string) (bool, error) {
|
|
|
|
|
var exists bool
|
|
|
|
|
var query string
|
|
|
|
|
|
|
|
|
|
if db.dialect == DialectPostgres {
|
|
|
|
|
query = "SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_name = $1)"
|
|
|
|
|
} else {
|
|
|
|
|
query = "SELECT EXISTS (SELECT 1 FROM sqlite_master WHERE type='table' AND name=?)"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err := db.Get(&exists, query, name)
|
|
|
|
|
return exists, err
|
|
|
|
|
}
|
2026-02-03 22:40:23 +00:00
|
|
|
|
|
|
|
|
// HasColumn checks if a column exists in a table.
|
|
|
|
|
func (db *DB) HasColumn(table, column string) (bool, error) {
|
|
|
|
|
var exists bool
|
|
|
|
|
var query string
|
|
|
|
|
|
|
|
|
|
if db.dialect == DialectPostgres {
|
|
|
|
|
query = "SELECT EXISTS (SELECT FROM information_schema.columns WHERE table_name = $1 AND column_name = $2)"
|
|
|
|
|
} else {
|
|
|
|
|
// For SQLite, check table_info
|
|
|
|
|
query = "SELECT COUNT(*) > 0 FROM pragma_table_info(?) WHERE name = ?"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
err := db.Get(&exists, query, table, column)
|
|
|
|
|
return exists, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MigrateSchema adds missing columns to existing tables for backward compatibility.
|
|
|
|
|
func (db *DB) MigrateSchema() error {
|
|
|
|
|
// Check and add missing columns to packages table
|
|
|
|
|
packagesColumns := map[string]string{
|
|
|
|
|
"registry_url": "TEXT",
|
|
|
|
|
"supplier_name": "TEXT",
|
|
|
|
|
"supplier_type": "TEXT",
|
|
|
|
|
"source": "TEXT",
|
|
|
|
|
"enriched_at": "DATETIME",
|
|
|
|
|
"vulns_synced_at": "DATETIME",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if db.dialect == DialectPostgres {
|
2026-03-18 10:59:29 +00:00
|
|
|
packagesColumns["enriched_at"] = postgresTimestamp
|
|
|
|
|
packagesColumns["vulns_synced_at"] = postgresTimestamp
|
2026-02-03 22:40:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for column, colType := range packagesColumns {
|
|
|
|
|
hasCol, err := db.HasColumn("packages", column)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("checking column %s: %w", column, err)
|
|
|
|
|
}
|
|
|
|
|
if !hasCol {
|
2026-03-18 10:59:29 +00:00
|
|
|
alterQuery := fmt.Sprintf("ALTER TABLE packages ADD COLUMN %s %s", column, colType)
|
2026-02-03 22:40:23 +00:00
|
|
|
if _, err := db.Exec(alterQuery); err != nil {
|
|
|
|
|
return fmt.Errorf("adding column %s to packages: %w", column, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check and add missing columns to versions table
|
|
|
|
|
versionsColumns := map[string]string{
|
|
|
|
|
"integrity": "TEXT",
|
|
|
|
|
"yanked": "INTEGER DEFAULT 0",
|
|
|
|
|
"source": "TEXT",
|
|
|
|
|
"enriched_at": "DATETIME",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if db.dialect == DialectPostgres {
|
|
|
|
|
versionsColumns["yanked"] = "BOOLEAN DEFAULT FALSE"
|
2026-03-18 10:59:29 +00:00
|
|
|
versionsColumns["enriched_at"] = postgresTimestamp
|
2026-02-03 22:40:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for column, colType := range versionsColumns {
|
|
|
|
|
hasCol, err := db.HasColumn("versions", column)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("checking column %s: %w", column, err)
|
|
|
|
|
}
|
|
|
|
|
if !hasCol {
|
2026-03-18 10:59:29 +00:00
|
|
|
alterQuery := fmt.Sprintf("ALTER TABLE versions ADD COLUMN %s %s", column, colType)
|
2026-02-03 22:40:23 +00:00
|
|
|
if _, err := db.Exec(alterQuery); err != nil {
|
|
|
|
|
return fmt.Errorf("adding column %s to versions: %w", column, err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure artifacts table exists
|
|
|
|
|
if err := db.EnsureArtifactsTable(); err != nil {
|
|
|
|
|
return fmt.Errorf("ensuring artifacts table: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Ensure vulnerabilities table exists
|
|
|
|
|
hasVulns, err := db.HasTable("vulnerabilities")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return fmt.Errorf("checking vulnerabilities table: %w", err)
|
|
|
|
|
}
|
|
|
|
|
if !hasVulns {
|
|
|
|
|
var vulnSchema string
|
|
|
|
|
if db.dialect == DialectPostgres {
|
|
|
|
|
vulnSchema = `
|
|
|
|
|
CREATE TABLE vulnerabilities (
|
|
|
|
|
id SERIAL PRIMARY KEY,
|
|
|
|
|
vuln_id TEXT NOT NULL,
|
|
|
|
|
ecosystem TEXT NOT NULL,
|
|
|
|
|
package_name TEXT NOT NULL,
|
|
|
|
|
severity TEXT,
|
|
|
|
|
summary TEXT,
|
|
|
|
|
fixed_version TEXT,
|
|
|
|
|
cvss_score REAL,
|
|
|
|
|
"references" TEXT,
|
|
|
|
|
fetched_at TIMESTAMP,
|
|
|
|
|
created_at TIMESTAMP,
|
|
|
|
|
updated_at TIMESTAMP
|
|
|
|
|
);
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_vulns_id_pkg ON vulnerabilities(vuln_id, ecosystem, package_name);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_vulns_ecosystem_pkg ON vulnerabilities(ecosystem, package_name);
|
|
|
|
|
`
|
|
|
|
|
} else {
|
|
|
|
|
vulnSchema = `
|
|
|
|
|
CREATE TABLE vulnerabilities (
|
|
|
|
|
id INTEGER PRIMARY KEY,
|
|
|
|
|
vuln_id TEXT NOT NULL,
|
|
|
|
|
ecosystem TEXT NOT NULL,
|
|
|
|
|
package_name TEXT NOT NULL,
|
|
|
|
|
severity TEXT,
|
|
|
|
|
summary TEXT,
|
|
|
|
|
fixed_version TEXT,
|
|
|
|
|
cvss_score REAL,
|
|
|
|
|
"references" TEXT,
|
|
|
|
|
fetched_at DATETIME,
|
|
|
|
|
created_at DATETIME,
|
|
|
|
|
updated_at DATETIME
|
|
|
|
|
);
|
|
|
|
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_vulns_id_pkg ON vulnerabilities(vuln_id, ecosystem, package_name);
|
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_vulns_ecosystem_pkg ON vulnerabilities(ecosystem, package_name);
|
|
|
|
|
`
|
|
|
|
|
}
|
|
|
|
|
if _, err := db.Exec(vulnSchema); err != nil {
|
|
|
|
|
return fmt.Errorf("creating vulnerabilities table: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|