You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by la...@apache.org on 2026/08/21 20:33:38 UTC
(airavata-custos) branch postgresql-migration created (now 32c7ac32c)
This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a change to branch postgresql-migration
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
at 32c7ac32c Migrate SSH certificate signer to PostgreSQL
This branch includes the following new commits:
new c07ec21ba Migrate core and connectors from MariaDB to PostgreSQL
new 32c7ac32c Migrate SSH certificate signer to PostgreSQL
The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(airavata-custos) 01/02: Migrate core and connectors from MariaDB to PostgreSQL
Posted by la...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a commit to branch postgresql-migration
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
commit c07ec21ba9f96dd6c4fda29211518292fba2bd6d
Author: lahiruj <la...@gmail.com>
AuthorDate: Fri Aug 21 16:32:31 2026 -0400
Migrate core and connectors from MariaDB to PostgreSQL
---
.env.example | 4 +-
.github/workflows/README.md | 4 +-
.github/workflows/amie-integration-tests.yml | 2 +-
.github/workflows/core-verify.yml | 17 +-
CONFIG.md | 6 +-
CONTRIBUTING.md | 12 +-
INSTALL.md | 6 +-
connectors/ACCESS/AMIE-Processor/README.md | 4 +-
.../db/migrations/000001_initial_schema.up.sql | 64 +-
.../data_account_create_integration_test.go | 2 +-
.../data_project_create_integration_test.go | 4 +-
.../AMIE-Processor/handler/integration_common.go | 17 +-
.../request_account_create_integration_test.go | 8 +-
.../request_account_inactivate_integration_test.go | 4 +-
.../request_person_merge_integration_test.go | 56 +-
.../request_project_create_integration_test.go | 2 +-
.../request_project_inactivate_integration_test.go | 2 +-
.../request_user_modify_integration_test.go | 20 +-
.../pipeline/baseline_integration_test.go | 2 +-
.../AMIE-Processor/pipeline/integration_common.go | 17 +-
.../ACCESS/AMIE-Processor/store/audit_store.go | 8 +-
.../ACCESS/AMIE-Processor/store/error_store.go | 8 +-
.../ACCESS/AMIE-Processor/store/event_store.go | 22 +-
.../AMIE-Processor/store/packet_audit_store.go | 8 +-
.../ACCESS/AMIE-Processor/store/packet_store.go | 42 +-
.../ACCESS/AMIE-Processor/store/user_dn_store.go | 26 +-
.../AMIE-Processor/worker/integration_common.go | 14 +-
.../worker/processor_failures_integration_test.go | 4 +-
.../worker/processor_integration_test.go | 10 +-
.../pkg/analytics/analytics_integration_test.go | 18 +-
.../pkg/analytics/integration_common_test.go | 17 +-
connectors/Analytics/pkg/analytics/store.go | 50 +-
dev-ops/compose/dbinit/init-db.sh | 25 +-
dev-ops/compose/docker-compose.yml | 21 +-
.../compose/grafana/dashboards/amie-service.json | 20 +-
.../datasources/{mariadb.yml => postgres.yml} | 9 +-
dev-ops/compose/seeds/default_cluster.sql | 9 +-
dev-ops/compose/seeds/dev_portal_data.sql | 48 +-
dev-ops/compose/seeds/dev_users_and_roles.sql | 50 +-
dev-ops/compose/seeds/sample_projects.sql | 790 ++++++++++++---------
dev-ops/compose/seeds/sample_resources.sql | 45 +-
dev-ops/compose/seeds/sample_usage_series.sql | 70 +-
dev-ops/local-amie/README.md | 2 +-
dev-ops/local-amie/compose.yaml | 15 +-
docs/API-Docs.md | 4 +-
docs/contributing/writing-a-connector.md | 6 +-
go.mod | 6 +-
go.sum | 11 +
internal/db/db.go | 6 +-
internal/db/migrate.go | 10 +-
internal/db/migrations/000001_identity.down.sql | 1 +
internal/db/migrations/000001_identity.up.sql | 59 +-
.../db/migrations/000002_compute_clusters.up.sql | 33 +-
internal/db/migrations/000003_projects.up.sql | 19 +-
internal/db/migrations/000004_allocations.up.sql | 149 ++--
internal/db/migrations/000005_audit.up.sql | 27 +-
internal/db/migrations/000006_authorization.up.sql | 31 +-
internal/server/integration_common_test.go | 19 +-
internal/server/privilege_integration_test.go | 6 +-
internal/store/audit_event_store.go | 28 +-
internal/store/audit_trace_store.go | 26 +-
internal/store/audit_trace_store_test.go | 4 +-
...ompute_allocation_change_request_event_store.go | 26 +-
.../compute_allocation_change_request_store.go | 54 +-
internal/store/compute_allocation_diff_store.go | 26 +-
...llocation_membership_resource_override_store.go | 42 +-
.../store/compute_allocation_membership_store.go | 60 +-
.../compute_allocation_resource_mapping_store.go | 38 +-
.../compute_allocation_resource_rate_store.go | 36 +-
.../store/compute_allocation_resource_store.go | 36 +-
internal/store/compute_allocation_store.go | 44 +-
internal/store/compute_allocation_usage_store.go | 42 +-
internal/store/compute_cluster_store.go | 28 +-
internal/store/compute_cluster_user_store.go | 56 +-
internal/store/organization_store.go | 30 +-
internal/store/project_membership_store.go | 44 +-
internal/store/project_store.go | 66 +-
internal/store/role_store.go | 46 +-
internal/store/user_identity_store.go | 46 +-
internal/store/user_privilege_store.go | 34 +-
internal/store/user_role_store.go | 44 +-
internal/store/user_store.go | 44 +-
pkg/README.md | 6 +-
pkg/service/identity_resolver_integration_test.go | 14 +-
pkg/service/integration_common_test.go | 21 +-
pkg/service/role_integration_test.go | 2 +-
pkg/service/service.go | 2 +-
pkg/service/user_privilege_integration_test.go | 4 +-
pkg/service/user_role.go | 8 +-
scripts/run-amie-integration-tests.sh | 2 +-
90 files changed, 1554 insertions(+), 1376 deletions(-)
diff --git a/.env.example b/.env.example
index a6ef288fb..9290e776c 100644
--- a/.env.example
+++ b/.env.example
@@ -21,8 +21,8 @@
# --- Server: required ----------------------------------------------------
-# MySQL/MariaDB DSN. Must allow multi-statements for migrations.
-DATABASE_DSN=admin:admin@tcp(localhost:3306)/custos?parseTime=true&charset=utf8mb4&multiStatements=true
+# PostgreSQL DSN.
+DATABASE_DSN=postgres://admin:admin@localhost:5432/custos?sslmode=disable
# OIDC issuer + audience. Server refuses to start without these.
#
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index cab31badb..3ab2c52c2 100644
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -31,12 +31,12 @@ the repo the workflow validates:
Current workflows:
- `core-verify.yml` — formatting, build, vet, unit tests for all Go packages,
- and the `./internal/...` integration suite against a MariaDB service
+ and the `./internal/...` integration suite against a PostgreSQL service
container. Runs on Go changes.
- `web-verify.yml` — frozen-lockfile install, typecheck, lint, unit tests,
and a production build. Runs on `web/` changes.
- `amie-integration-tests.yml` — the AMIE integration suite with its own
- compose stack (mock AMIE server + MariaDB). Runs on connector changes.
+ compose stack (mock AMIE server + PostgreSQL). Runs on connector changes.
Not duplicated by `core-verify.yml`.
When adding a workflow, keep the area prefix, add path filters so unrelated
diff --git a/.github/workflows/amie-integration-tests.yml b/.github/workflows/amie-integration-tests.yml
index b6c78e3fb..edf574929 100644
--- a/.github/workflows/amie-integration-tests.yml
+++ b/.github/workflows/amie-integration-tests.yml
@@ -55,7 +55,7 @@ jobs:
run: |
echo "--- mock-amie logs ---"
docker logs custos_amie_test_mock 2>&1 || true
- echo "--- mariadb logs ---"
+ echo "--- postgres logs ---"
docker logs custos_amie_test_db 2>&1 || true
- name: Tear down stack
diff --git a/.github/workflows/core-verify.yml b/.github/workflows/core-verify.yml
index 810134266..7e1b75fd8 100644
--- a/.github/workflows/core-verify.yml
+++ b/.github/workflows/core-verify.yml
@@ -41,17 +41,16 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
services:
- mariadb:
- image: mariadb:11.2
+ postgres:
+ image: postgres:17
env:
- MARIADB_ROOT_PASSWORD: root
- MARIADB_DATABASE: custos
- MARIADB_USER: admin
- MARIADB_PASSWORD: admin
+ POSTGRES_DB: custos
+ POSTGRES_USER: admin
+ POSTGRES_PASSWORD: admin
ports:
- - 3306:3306
+ - 5432:5432
options: >-
- --health-cmd="healthcheck.sh --connect --innodb_initialized"
+ --health-cmd="pg_isready -U admin -d custos"
--health-interval=10s
--health-timeout=5s
--health-retries=10
@@ -87,5 +86,5 @@ jobs:
# this covers the core suite only.
- name: Core integration tests
env:
- CORE_TEST_DATABASE_DSN: "admin:admin@tcp(127.0.0.1:3306)/custos?parseTime=true&charset=utf8mb4&multiStatements=true"
+ CORE_TEST_DATABASE_DSN: "postgres://admin:admin@127.0.0.1:5432/custos?sslmode=disable"
run: go test -tags integration ./internal/...
diff --git a/CONFIG.md b/CONFIG.md
index d99a5047b..91915a61e 100644
--- a/CONFIG.md
+++ b/CONFIG.md
@@ -48,13 +48,13 @@ The `core` section contains essential server settings:
```yaml
core:
database:
- url: "admin:admin@tcp(localhost:3306)/custos?parseTime=true&charset=utf8mb4"
+ url: "postgres://admin:admin@localhost:5432/custos?sslmode=disable"
api:
port: 8080
log_level: "info"
```
-- **database.url**: MariaDB / MySQL DSN (the server uses the `go-sql-driver/mysql` driver). Required.
+- **database.url**: PostgreSQL DSN (the server uses the `pgx` driver). Required.
- **api.port**: HTTP API port (default: 8080)
- **log_level**: Logging level (info, debug, warn, error)
@@ -171,7 +171,7 @@ core:
In your shell:
```bash
-export DATABASE_URL="admin:admin@tcp(localhost:3306)/custos?parseTime=true&charset=utf8mb4"
+export DATABASE_URL="postgres://admin:admin@localhost:5432/custos?sslmode=disable"
./custos
```
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ad9a67a5e..ace39008e 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -35,7 +35,7 @@ For instructions on running Custos locally against a database, see [INSTALL.md](
- `pkg/` — public packages (models, service, events)
- `connectors/` — protocol/site-specific connectors (ACCESS, SLURM, …)
- `extensions/` — out-of-process extensions (PAM module, SSH cert signer, …)
-- `dev-ops/compose/` — local Docker Compose stack (MariaDB, Adminer, Prometheus, Grafana, Vault)
+- `dev-ops/compose/` — local Docker Compose stack (PostgreSQL, Adminer, Prometheus, Grafana, Vault)
## Build
@@ -91,23 +91,23 @@ required env vars:
```bash
# General integration tests (server, core service, identity resolver).
-# Reuses the dev compose stack on :3306.
-export CORE_TEST_DATABASE_DSN='admin:admin@tcp(localhost:3306)/custos?parseTime=true&charset=utf8mb4&multiStatements=true'
+# Reuses the dev compose stack on :5432 (custos_test database).
+export CORE_TEST_DATABASE_DSN='postgres://admin:admin@localhost:5432/custos_test?sslmode=disable'
go test -tags integration ./...
```
The AMIE connector ships its own integration stack because its tests mutate a
lot of state and we don't want to corrupt the dev DB. It brings up an isolated
-MariaDB on `:3307` and a mock AMIE server on `:8181`, runs the suite, then
+PostgreSQL on `:5433` and a mock AMIE server on `:8181`, runs the suite, then
tears it down:
```bash
make integration-test-amie
```
-(equivalent to `scripts/run-amie-integration-tests.sh`). The `:3307` is
+(equivalent to `scripts/run-amie-integration-tests.sh`). The `:5433` is
deliberate. It's a separate stack defined in `dev-ops/local-amie/`, independent
-of the `:3306` dev compose.
+of the `:5432` dev compose.
If you see a test file you expect to run but `go test ./...` reports zero tests
for the package, check the first line for `//go:build integration`.
diff --git a/INSTALL.md b/INSTALL.md
index 2ca2403a8..c2c4918fe 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -50,7 +50,7 @@ From `dev-ops/compose/`:
docker compose up -d db
```
-This starts MariaDB on `localhost:3306` and runs `dbinit/init-db.sh`, which
+This starts PostgreSQL on `localhost:5432` and runs `dbinit/init-db.sh`, which
creates the `custos` database and the `admin` user (password `admin`).
**Bundled Keycloak only:** also bring up Keycloak. On first start it imports
@@ -114,7 +114,7 @@ The migrations create the schema; the cluster seed inserts the default
Apply it once, in a second terminal, after the server has come up:
```bash
-docker exec -i custos_db mariadb -uadmin -padmin custos < dev-ops/compose/seeds/default_cluster.sql
+docker exec -i custos_db psql -U admin -d custos < dev-ops/compose/seeds/default_cluster.sql
```
The seed uses `INSERT IGNORE`, so re-running is safe.
@@ -125,7 +125,7 @@ For the bundled Keycloak path there is also a sample identity seed (org,
users, roles, role assignments) matched to the users in the imported realm:
```bash
-docker exec -i custos_db mariadb -uadmin -padmin custos < dev-ops/compose/seeds/dev_users_and_roles.sql
+docker exec -i custos_db psql -U admin -d custos < dev-ops/compose/seeds/dev_users_and_roles.sql
```
Skip this for external OIDC. You don't need it, and the emails would collide
diff --git a/connectors/ACCESS/AMIE-Processor/README.md b/connectors/ACCESS/AMIE-Processor/README.md
index cdc5ba29c..fd632a740 100644
--- a/connectors/ACCESS/AMIE-Processor/README.md
+++ b/connectors/ACCESS/AMIE-Processor/README.md
@@ -47,7 +47,7 @@ export AMIE_API_KEY="your-api-key"
```
AMIE reads its config from the `connectors.amie-processor` block in `config/custos.yaml`. The defaults work against the
-Docker Compose MariaDB.
+Docker Compose PostgreSQL.
For local dev without a real ACCESS endpoint, point the service at the local mock AMIE server in [
`mock-server/`](./mock-server/README.md)
@@ -88,7 +88,7 @@ vars set:
- `DATABASE_DSN`, `AMIE_BASE_URL`, `AMIE_SITE_CODE`, `AMIE_API_KEY`, `AMIE_CLUSTER_ID`
-The canonical runner brings up an isolated DB on `:3307` + mock AMIE on `:8181`, applies all migrations, fires every
+The canonical runner brings up an isolated DB on `:5433` + mock AMIE on `:8181`, applies all migrations, fires every
integration test, then tears down:
```bash
diff --git a/connectors/ACCESS/AMIE-Processor/db/migrations/000001_initial_schema.up.sql b/connectors/ACCESS/AMIE-Processor/db/migrations/000001_initial_schema.up.sql
index 09a033f19..9346b4f78 100644
--- a/connectors/ACCESS/AMIE-Processor/db/migrations/000001_initial_schema.up.sql
+++ b/connectors/ACCESS/AMIE-Processor/db/migrations/000001_initial_schema.up.sql
@@ -25,16 +25,16 @@ CREATE TABLE IF NOT EXISTS amie_packets
type VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL,
raw_json TEXT NOT NULL,
- received_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- decoded_at TIMESTAMP(6) NULL,
- processed_at TIMESTAMP(6) NULL,
+ received_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ decoded_at TIMESTAMPTZ(6) NULL,
+ processed_at TIMESTAMPTZ(6) NULL,
retries INT NOT NULL DEFAULT 0,
last_error TEXT NULL,
PRIMARY KEY (id),
- UNIQUE KEY uq_amie_packets_amie_id (amie_id),
- KEY idx_amie_packets_status (status),
- KEY idx_amie_packets_received_at (received_at)
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+ CONSTRAINT uq_amie_packets_amie_id UNIQUE (amie_id)
+);
+CREATE INDEX IF NOT EXISTS idx_amie_packets_status ON amie_packets (status);
+CREATE INDEX IF NOT EXISTS idx_amie_packets_received_at ON amie_packets (received_at);
CREATE TABLE IF NOT EXISTS amie_processing_events
(
@@ -43,35 +43,35 @@ CREATE TABLE IF NOT EXISTS amie_processing_events
type VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL,
attempts INT NOT NULL DEFAULT 0,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- started_at TIMESTAMP(6) NULL,
- finished_at TIMESTAMP(6) NULL,
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ started_at TIMESTAMPTZ(6) NULL,
+ finished_at TIMESTAMPTZ(6) NULL,
last_error TEXT NULL,
- next_retry_at TIMESTAMP(6) NULL DEFAULT NULL,
+ next_retry_at TIMESTAMPTZ(6) NULL DEFAULT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_amie_events_packet FOREIGN KEY (packet_id) REFERENCES amie_packets (id) ON DELETE CASCADE,
- UNIQUE KEY uq_amie_events_packet_type (packet_id, type),
- KEY idx_amie_events_status (status),
- KEY idx_amie_events_packet_id (packet_id),
- KEY idx_amie_events_type (type),
- KEY idx_amie_events_next_retry_at (next_retry_at)
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+ CONSTRAINT uq_amie_events_packet_type UNIQUE (packet_id, type)
+);
+CREATE INDEX IF NOT EXISTS idx_amie_events_status ON amie_processing_events (status);
+CREATE INDEX IF NOT EXISTS idx_amie_events_packet_id ON amie_processing_events (packet_id);
+CREATE INDEX IF NOT EXISTS idx_amie_events_type ON amie_processing_events (type);
+CREATE INDEX IF NOT EXISTS idx_amie_events_next_retry_at ON amie_processing_events (next_retry_at);
CREATE TABLE IF NOT EXISTS amie_processing_errors
(
- id BIGINT NOT NULL AUTO_INCREMENT,
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY,
packet_id VARCHAR(255) NULL,
event_id VARCHAR(255) NULL,
- occurred_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ occurred_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
summary TEXT NOT NULL,
detail TEXT NULL,
PRIMARY KEY (id),
CONSTRAINT fk_amie_errors_packet FOREIGN KEY (packet_id) REFERENCES amie_packets (id) ON DELETE SET NULL,
- CONSTRAINT fk_amie_errors_event FOREIGN KEY (event_id) REFERENCES amie_processing_events (id) ON DELETE SET NULL,
- KEY idx_amie_errors_packet_id (packet_id),
- KEY idx_amie_errors_event_id (event_id),
- KEY idx_amie_errors_occurred_at (occurred_at)
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+ CONSTRAINT fk_amie_errors_event FOREIGN KEY (event_id) REFERENCES amie_processing_events (id) ON DELETE SET NULL
+);
+CREATE INDEX IF NOT EXISTS idx_amie_errors_packet_id ON amie_processing_errors (packet_id);
+CREATE INDEX IF NOT EXISTS idx_amie_errors_event_id ON amie_processing_errors (event_id);
+CREATE INDEX IF NOT EXISTS idx_amie_errors_occurred_at ON amie_processing_errors (occurred_at);
-- AMIE writes audit rows to core's audit_events table; this side table
-- carries the connector-specific references (packet_id, event_id) joined on
@@ -84,10 +84,10 @@ CREATE TABLE IF NOT EXISTS amie_audit_extras
PRIMARY KEY (audit_event_id),
CONSTRAINT fk_amie_audit_extras_event FOREIGN KEY (audit_event_id) REFERENCES audit_events(id) ON DELETE CASCADE,
CONSTRAINT fk_amie_audit_extras_packet FOREIGN KEY (packet_id) REFERENCES amie_packets(id) ON DELETE CASCADE,
- CONSTRAINT fk_amie_audit_extras_procev FOREIGN KEY (event_id) REFERENCES amie_processing_events(id) ON DELETE SET NULL,
- KEY idx_amie_audit_extras_packet_id (packet_id),
- KEY idx_amie_audit_extras_event_id (event_id)
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+ CONSTRAINT fk_amie_audit_extras_procev FOREIGN KEY (event_id) REFERENCES amie_processing_events(id) ON DELETE SET NULL
+);
+CREATE INDEX IF NOT EXISTS idx_amie_audit_extras_event_id ON amie_audit_extras (event_id);
+CREATE INDEX IF NOT EXISTS idx_amie_audit_extras_packet_id ON amie_audit_extras (packet_id);
-- AMIE-side DN registry. AMIE delivers DnList fields that contain DNs across
-- the federation (not just this site's downstream cluster).
@@ -99,8 +99,8 @@ CREATE TABLE IF NOT EXISTS amie_user_dns
id VARCHAR(255) NOT NULL,
user_id VARCHAR(255) NOT NULL,
dn VARCHAR(512) NOT NULL,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- UNIQUE KEY uq_amie_user_dns_dn (dn),
- KEY idx_amie_user_dns_user (user_id)
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+ CONSTRAINT uq_amie_user_dns_dn UNIQUE (dn)
+);
+CREATE INDEX IF NOT EXISTS idx_amie_user_dns_user ON amie_user_dns (user_id);
diff --git a/connectors/ACCESS/AMIE-Processor/handler/data_account_create_integration_test.go b/connectors/ACCESS/AMIE-Processor/handler/data_account_create_integration_test.go
index fb670af19..c49eb50a1 100644
--- a/connectors/ACCESS/AMIE-Processor/handler/data_account_create_integration_test.go
+++ b/connectors/ACCESS/AMIE-Processor/handler/data_account_create_integration_test.go
@@ -47,7 +47,7 @@ func baseDataAccountCreateBody() map[string]any {
"ProjectID": "TG-BL-001",
"GlobalID": "bl-user-001",
"DnList": []any{
- dacExistingDN, // duplicate; INSERT IGNORE keeps the existing row
+ dacExistingDN, // duplicate; conflict is ignored, existing row kept
dacNewDN1,
dacNewDN2,
},
diff --git a/connectors/ACCESS/AMIE-Processor/handler/data_project_create_integration_test.go b/connectors/ACCESS/AMIE-Processor/handler/data_project_create_integration_test.go
index 341edf40f..b2d7e9ca5 100644
--- a/connectors/ACCESS/AMIE-Processor/handler/data_project_create_integration_test.go
+++ b/connectors/ACCESS/AMIE-Processor/handler/data_project_create_integration_test.go
@@ -48,7 +48,7 @@ func baseDataProjectCreateBody() map[string]any {
"ProjectID": "TG-BL-001",
"GlobalID": "bl-pi-001",
"DnList": []any{
- dpcExistingDN, // duplicate of seeded row; INSERT IGNORE keeps one
+ dpcExistingDN, // duplicate of seeded row; conflict is ignored, one row kept
dpcNewDN1,
dpcNewDN2,
},
@@ -153,7 +153,7 @@ func TestDataProjectCreate_HappyPath(t *testing.T) {
}
// Additive write: the existing DN must still be present; the two new DNs
- // must be added. Duplicate of existing DN is a silent no-op (INSERT IGNORE).
+ // must be added. Duplicate of existing DN is a silent no-op.
if got, want := countDNsForUser(t, database, userID), 3; got != want {
t.Errorf("amie_user_dns for PI: got %d, want %d", got, want)
}
diff --git a/connectors/ACCESS/AMIE-Processor/handler/integration_common.go b/connectors/ACCESS/AMIE-Processor/handler/integration_common.go
index 6562407fc..49947218c 100644
--- a/connectors/ACCESS/AMIE-Processor/handler/integration_common.go
+++ b/connectors/ACCESS/AMIE-Processor/handler/integration_common.go
@@ -24,6 +24,7 @@ import (
"database/sql"
"encoding/json"
"os"
+ "strings"
"sync"
"testing"
"time"
@@ -127,16 +128,8 @@ func truncateAll(t *testing.T, database *sqlx.DB) {
"users",
"organizations",
}
- if _, err := database.Exec("SET FOREIGN_KEY_CHECKS = 0"); err != nil {
- t.Fatalf("disable FK: %v", err)
- }
- for _, tbl := range tables {
- if _, err := database.Exec("TRUNCATE TABLE " + tbl); err != nil {
- t.Fatalf("truncate %s: %v", tbl, err)
- }
- }
- if _, err := database.Exec("SET FOREIGN_KEY_CHECKS = 1"); err != nil {
- t.Fatalf("re-enable FK: %v", err)
+ if _, err := database.Exec("TRUNCATE TABLE " + strings.Join(tables, ", ") + " CASCADE"); err != nil {
+ t.Fatalf("truncate: %v", err)
}
}
@@ -146,7 +139,7 @@ func truncateAll(t *testing.T, database *sqlx.DB) {
func seedDefaultCluster(t *testing.T, database *sqlx.DB) {
t.Helper()
if _, err := database.Exec(
- "INSERT INTO compute_clusters (id, name) VALUES (?, ?)",
+ "INSERT INTO compute_clusters (id, name) VALUES ($1, $2)",
testClusterID, "default-cluster",
); err != nil {
t.Fatalf("seed cluster: %v", err)
@@ -263,7 +256,7 @@ func countAuditActions(t *testing.T, database *sqlx.DB, packetID string, action
if err := database.Get(&n,
`SELECT COUNT(*) FROM audit_events ae
JOIN amie_audit_extras x ON x.audit_event_id = ae.id
- WHERE x.packet_id = ? AND ae.event_type = ?`,
+ WHERE x.packet_id = $1 AND ae.event_type = $2`,
packetID, string(action),
); err != nil {
t.Fatalf("count audit %s: %v", action, err)
diff --git a/connectors/ACCESS/AMIE-Processor/handler/request_account_create_integration_test.go b/connectors/ACCESS/AMIE-Processor/handler/request_account_create_integration_test.go
index 1fdc753cb..7b332294c 100644
--- a/connectors/ACCESS/AMIE-Processor/handler/request_account_create_integration_test.go
+++ b/connectors/ACCESS/AMIE-Processor/handler/request_account_create_integration_test.go
@@ -154,7 +154,7 @@ func TestRequestAccountCreate_HappyPath(t *testing.T) {
`SELECT u.id, u.first_name, u.last_name, u.email, u.status
FROM users u
JOIN user_identities ui ON ui.user_id = u.id
- WHERE ui.source = ? AND ui.external_id = ?`,
+ WHERE ui.source = $1 AND ui.external_id = $2`,
"access", "bl-user-001",
); err != nil {
t.Fatalf("read account user: %v", err)
@@ -177,7 +177,7 @@ func TestRequestAccountCreate_HappyPath(t *testing.T) {
LocalUsername string `db:"local_username"`
}
if err := database.Get(&cu,
- "SELECT user_id, compute_cluster_id, local_username FROM compute_cluster_users WHERE user_id = ?",
+ "SELECT user_id, compute_cluster_id, local_username FROM compute_cluster_users WHERE user_id = $1",
user.ID,
); err != nil {
t.Fatalf("read compute_cluster_user: %v", err)
@@ -196,14 +196,14 @@ func TestRequestAccountCreate_HappyPath(t *testing.T) {
ComputeAllocationID string `db:"compute_allocation_id"`
}
if err := database.Get(&mem,
- "SELECT user_id, compute_allocation_id FROM compute_allocation_memberships WHERE user_id = ?",
+ "SELECT user_id, compute_allocation_id FROM compute_allocation_memberships WHERE user_id = $1",
user.ID,
); err != nil {
t.Fatalf("read membership: %v", err)
}
var allocID string
if err := database.Get(&allocID,
- "SELECT id FROM compute_allocations WHERE project_id = ? LIMIT 1", projectID,
+ "SELECT id FROM compute_allocations WHERE project_id = $1 LIMIT 1", projectID,
); err != nil {
t.Fatalf("read allocation id: %v", err)
}
diff --git a/connectors/ACCESS/AMIE-Processor/handler/request_account_inactivate_integration_test.go b/connectors/ACCESS/AMIE-Processor/handler/request_account_inactivate_integration_test.go
index 2020c8a4a..85709bf9a 100644
--- a/connectors/ACCESS/AMIE-Processor/handler/request_account_inactivate_integration_test.go
+++ b/connectors/ACCESS/AMIE-Processor/handler/request_account_inactivate_integration_test.go
@@ -140,7 +140,7 @@ func getMembershipStatus(t *testing.T, database *sqlx.DB, membershipID string) s
t.Helper()
var status string
if err := database.Get(&status,
- "SELECT membership_status FROM compute_allocation_memberships WHERE id = ?", membershipID,
+ "SELECT membership_status FROM compute_allocation_memberships WHERE id = $1", membershipID,
); err != nil {
t.Fatalf("read membership %s: %v", membershipID, err)
}
@@ -150,7 +150,7 @@ func getMembershipStatus(t *testing.T, database *sqlx.DB, membershipID string) s
func getUserStatus(t *testing.T, database *sqlx.DB, userID string) string {
t.Helper()
var status string
- if err := database.Get(&status, "SELECT status FROM users WHERE id = ?", userID); err != nil {
+ if err := database.Get(&status, "SELECT status FROM users WHERE id = $1", userID); err != nil {
t.Fatalf("read user %s: %v", userID, err)
}
return status
diff --git a/connectors/ACCESS/AMIE-Processor/handler/request_person_merge_integration_test.go b/connectors/ACCESS/AMIE-Processor/handler/request_person_merge_integration_test.go
index 92b14a252..c3b933c1c 100644
--- a/connectors/ACCESS/AMIE-Processor/handler/request_person_merge_integration_test.go
+++ b/connectors/ACCESS/AMIE-Processor/handler/request_person_merge_integration_test.go
@@ -94,37 +94,37 @@ func seedMergeUsers(t *testing.T, database *sqlx.DB) mergeTestUsers {
}
}
- exec("INSERT INTO organizations (id, originated_id, name) VALUES (?, ?, ?)",
+ exec("INSERT INTO organizations (id, originated_id, name) VALUES ($1, $2, $3)",
survivingOrg, "SURV", "Surviving Org")
- exec("INSERT INTO organizations (id, originated_id, name) VALUES (?, ?, ?)",
+ exec("INSERT INTO organizations (id, originated_id, name) VALUES ($1, $2, $3)",
retiringOrg, "RETR", "Retiring Org")
- exec("INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status) VALUES (?, ?, ?, ?, ?, ?, ?)",
+ exec("INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status) VALUES ($1, $2, $3, $4, $5, $6, $7)",
out.survivingID, survivingOrg, "Pat", "Survivor", "", "pat.survivor@example.edu", string(models.UserActive))
- exec("INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status) VALUES (?, ?, ?, ?, ?, ?, ?)",
+ exec("INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status) VALUES ($1, $2, $3, $4, $5, $6, $7)",
out.retiringID, retiringOrg, "Pat", "Retiring", "", "pat.retiring@example.edu", string(models.UserActive))
- exec("INSERT INTO user_identities (id, user_id, source, external_id) VALUES (?, ?, ?, ?)",
+ exec("INSERT INTO user_identities (id, user_id, source, external_id) VALUES ($1, $2, $3, $4)",
survivingIdentity, out.survivingID, amieIdentitySource, out.survivingGlobal)
- exec("INSERT INTO user_identities (id, user_id, source, external_id) VALUES (?, ?, ?, ?)",
+ exec("INSERT INTO user_identities (id, user_id, source, external_id) VALUES ($1, $2, $3, $4)",
retiringIdentity, out.retiringID, amieIdentitySource, out.retiringGlobal)
- exec("INSERT INTO compute_cluster_users (id, compute_cluster_id, user_id, local_username) VALUES (?, ?, ?, ?)",
+ exec("INSERT INTO compute_cluster_users (id, compute_cluster_id, user_id, local_username) VALUES ($1, $2, $3, $4)",
survivingClusterUser, testClusterID, out.survivingID, "psurvivor")
- exec("INSERT INTO compute_cluster_users (id, compute_cluster_id, user_id, local_username) VALUES (?, ?, ?, ?)",
+ exec("INSERT INTO compute_cluster_users (id, compute_cluster_id, user_id, local_username) VALUES ($1, $2, $3, $4)",
retiringClusterUser, testClusterID, out.retiringID, "pretiring")
- exec("INSERT INTO amie_user_dns (id, user_id, dn) VALUES (?, ?, ?)",
+ exec("INSERT INTO amie_user_dns (id, user_id, dn) VALUES ($1, $2, $3)",
uuid.NewString(), out.survivingID, out.survivingDN)
- exec("INSERT INTO amie_user_dns (id, user_id, dn) VALUES (?, ?, ?)",
+ exec("INSERT INTO amie_user_dns (id, user_id, dn) VALUES ($1, $2, $3)",
uuid.NewString(), out.retiringID, out.retiringDN)
// Two projects: one PI'd by each user. The retiring user's project should
// be reassigned to the surviving user after the merge per the
// service.MergeUsers contract (projs.ReassignPI).
- exec("INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES (?, ?, ?, ?, ?, ?)",
+ exec("INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES ($1, $2, $3, $4, $5, $6)",
out.survivingProject, "TG-SURV-001", "Surviving Project", "ACCESS", out.survivingID, string(models.ProjectActive))
- exec("INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES (?, ?, ?, ?, ?, ?)",
+ exec("INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES ($1, $2, $3, $4, $5, $6)",
out.retiringProject, "TG-RETR-001", "Retiring Project", "ACCESS", out.retiringID, string(models.ProjectActive))
// One allocation under the surviving project, with both users as members.
@@ -134,17 +134,17 @@ func seedMergeUsers(t *testing.T, database *sqlx.DB) mergeTestUsers {
endTime := startTime.Add(365 * 24 * time.Hour)
exec(`INSERT INTO compute_allocations
(id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
out.allocationID, out.survivingProject, "TG-SURV-001-alloc",
string(models.ACTIVE), testClusterID, int64(1000), startTime, endTime)
exec(`INSERT INTO compute_allocation_memberships
(id, compute_allocation_id, user_id, start_time, end_time, membership_status)
- VALUES (?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6)`,
survivingMembership, out.allocationID, out.survivingID, startTime, endTime, string(models.ACTIVE))
exec(`INSERT INTO compute_allocation_memberships
(id, compute_allocation_id, user_id, start_time, end_time, membership_status)
- VALUES (?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6)`,
retiringMembership, out.allocationID, out.retiringID, startTime, endTime, string(models.ACTIVE))
if err := tx.Commit(); err != nil {
@@ -199,14 +199,14 @@ func TestRequestPersonMerge_HappyPath(t *testing.T) {
// Retiring user's status must be MERGED (soft-delete). The surviving user
// stays ACTIVE.
var retiringStatus string
- if err := database.Get(&retiringStatus, "SELECT status FROM users WHERE id = ?", seed.retiringID); err != nil {
+ if err := database.Get(&retiringStatus, "SELECT status FROM users WHERE id = $1", seed.retiringID); err != nil {
t.Fatalf("read retiring user status: %v", err)
}
if retiringStatus != string(models.UserMerged) {
t.Errorf("retiring user.status: got %q, want %q", retiringStatus, string(models.UserMerged))
}
var survivingStatus string
- if err := database.Get(&survivingStatus, "SELECT status FROM users WHERE id = ?", seed.survivingID); err != nil {
+ if err := database.Get(&survivingStatus, "SELECT status FROM users WHERE id = $1", seed.survivingID); err != nil {
t.Fatalf("read surviving user status: %v", err)
}
if survivingStatus != string(models.UserActive) {
@@ -217,7 +217,7 @@ func TestRequestPersonMerge_HappyPath(t *testing.T) {
// at the surviving user.
var identitiesOnRetiring int
if err := database.Get(&identitiesOnRetiring,
- "SELECT COUNT(*) FROM user_identities WHERE user_id = ?", seed.retiringID); err != nil {
+ "SELECT COUNT(*) FROM user_identities WHERE user_id = $1", seed.retiringID); err != nil {
t.Fatalf("count identities on retiring: %v", err)
}
if identitiesOnRetiring != 0 {
@@ -225,7 +225,7 @@ func TestRequestPersonMerge_HappyPath(t *testing.T) {
}
var identitiesOnSurviving int
if err := database.Get(&identitiesOnSurviving,
- "SELECT COUNT(*) FROM user_identities WHERE user_id = ?", seed.survivingID); err != nil {
+ "SELECT COUNT(*) FROM user_identities WHERE user_id = $1", seed.survivingID); err != nil {
t.Fatalf("count identities on surviving: %v", err)
}
// Survivor had 1 (KeepGlobalID), retiring had 1 (DeleteGlobalID) → 2 after.
@@ -238,7 +238,7 @@ func TestRequestPersonMerge_HappyPath(t *testing.T) {
// must still be present).
var dnsOnRetiring int
if err := database.Get(&dnsOnRetiring,
- "SELECT COUNT(*) FROM amie_user_dns WHERE user_id = ?", seed.retiringID); err != nil {
+ "SELECT COUNT(*) FROM amie_user_dns WHERE user_id = $1", seed.retiringID); err != nil {
t.Fatalf("count dns on retiring: %v", err)
}
if dnsOnRetiring != 0 {
@@ -246,7 +246,7 @@ func TestRequestPersonMerge_HappyPath(t *testing.T) {
}
var dnsOnSurviving int
if err := database.Get(&dnsOnSurviving,
- "SELECT COUNT(*) FROM amie_user_dns WHERE user_id = ?", seed.survivingID); err != nil {
+ "SELECT COUNT(*) FROM amie_user_dns WHERE user_id = $1", seed.survivingID); err != nil {
t.Fatalf("count dns on surviving: %v", err)
}
if dnsOnSurviving != 2 {
@@ -259,7 +259,7 @@ func TestRequestPersonMerge_HappyPath(t *testing.T) {
// ends with exactly one membership on that allocation.
var membershipsOnRetiring int
if err := database.Get(&membershipsOnRetiring,
- "SELECT COUNT(*) FROM compute_allocation_memberships WHERE user_id = ?", seed.retiringID); err != nil {
+ "SELECT COUNT(*) FROM compute_allocation_memberships WHERE user_id = $1", seed.retiringID); err != nil {
t.Fatalf("count memberships on retiring: %v", err)
}
if membershipsOnRetiring != 0 {
@@ -267,7 +267,7 @@ func TestRequestPersonMerge_HappyPath(t *testing.T) {
}
var membershipsOnSurviving int
if err := database.Get(&membershipsOnSurviving,
- "SELECT COUNT(*) FROM compute_allocation_memberships WHERE user_id = ?", seed.survivingID); err != nil {
+ "SELECT COUNT(*) FROM compute_allocation_memberships WHERE user_id = $1", seed.survivingID); err != nil {
t.Fatalf("count memberships on surviving: %v", err)
}
if membershipsOnSurviving < 1 {
@@ -280,7 +280,7 @@ func TestRequestPersonMerge_HappyPath(t *testing.T) {
// implements that via projs.ReassignPI.)
var retiringProjectPI string
if err := database.Get(&retiringProjectPI,
- "SELECT project_pi_id FROM projects WHERE id = ?", seed.retiringProject); err != nil {
+ "SELECT project_pi_id FROM projects WHERE id = $1", seed.retiringProject); err != nil {
t.Fatalf("read retiring project PI: %v", err)
}
if retiringProjectPI != seed.survivingID {
@@ -343,7 +343,7 @@ func TestRequestPersonMerge_ReplaySafeAfterMerge(t *testing.T) {
identitiesOnSurviving := func() int {
var n int
if err := database.Get(&n,
- "SELECT COUNT(*) FROM user_identities WHERE user_id = ?", seed.survivingID); err != nil {
+ "SELECT COUNT(*) FROM user_identities WHERE user_id = $1", seed.survivingID); err != nil {
t.Fatalf("count identities: %v", err)
}
return n
@@ -351,7 +351,7 @@ func TestRequestPersonMerge_ReplaySafeAfterMerge(t *testing.T) {
dnsOnSurviving := func() int {
var n int
if err := database.Get(&n,
- "SELECT COUNT(*) FROM amie_user_dns WHERE user_id = ?", seed.survivingID); err != nil {
+ "SELECT COUNT(*) FROM amie_user_dns WHERE user_id = $1", seed.survivingID); err != nil {
t.Fatalf("count dns: %v", err)
}
return n
@@ -432,7 +432,7 @@ func TestRequestPersonMerge_SameKeepAndDelete(t *testing.T) {
// No state change: surviving user keeps every identity/DN row, status
// untouched, audit log empty for this packet.
var survivingStatus string
- if err := database.Get(&survivingStatus, "SELECT status FROM users WHERE id = ?", seed.survivingID); err != nil {
+ if err := database.Get(&survivingStatus, "SELECT status FROM users WHERE id = $1", seed.survivingID); err != nil {
t.Fatalf("read surviving user status: %v", err)
}
if survivingStatus != string(models.UserActive) {
@@ -487,7 +487,7 @@ func TestRequestPersonMerge_UnknownRetiringOrSurviving(t *testing.T) {
// State unchanged.
var retiringStatus string
- if err := database.Get(&retiringStatus, "SELECT status FROM users WHERE id = ?", seed.retiringID); err != nil {
+ if err := database.Get(&retiringStatus, "SELECT status FROM users WHERE id = $1", seed.retiringID); err != nil {
t.Fatalf("read retiring user status: %v", err)
}
if retiringStatus != string(models.UserActive) {
diff --git a/connectors/ACCESS/AMIE-Processor/handler/request_project_create_integration_test.go b/connectors/ACCESS/AMIE-Processor/handler/request_project_create_integration_test.go
index f281fc936..65a2869f6 100644
--- a/connectors/ACCESS/AMIE-Processor/handler/request_project_create_integration_test.go
+++ b/connectors/ACCESS/AMIE-Processor/handler/request_project_create_integration_test.go
@@ -206,7 +206,7 @@ func TestRequestProjectCreate_HappyPath(t *testing.T) {
LocalUsername string `db:"local_username"`
}
if err := database.Get(&piCU,
- "SELECT local_username FROM compute_cluster_users WHERE user_id = ? AND compute_cluster_id = ?",
+ "SELECT local_username FROM compute_cluster_users WHERE user_id = $1 AND compute_cluster_id = $2",
user.ID, testClusterID,
); err != nil {
t.Fatalf("read PI compute_cluster_user: %v", err)
diff --git a/connectors/ACCESS/AMIE-Processor/handler/request_project_inactivate_integration_test.go b/connectors/ACCESS/AMIE-Processor/handler/request_project_inactivate_integration_test.go
index 454c677bb..9b45118ff 100644
--- a/connectors/ACCESS/AMIE-Processor/handler/request_project_inactivate_integration_test.go
+++ b/connectors/ACCESS/AMIE-Processor/handler/request_project_inactivate_integration_test.go
@@ -170,7 +170,7 @@ func membershipStatus(t *testing.T, database *sqlx.DB, id string) models.Allocat
t.Helper()
var status string
if err := database.Get(&status,
- "SELECT membership_status FROM compute_allocation_memberships WHERE id = ?", id,
+ "SELECT membership_status FROM compute_allocation_memberships WHERE id = $1", id,
); err != nil {
t.Fatalf("read membership %s: %v", id, err)
}
diff --git a/connectors/ACCESS/AMIE-Processor/handler/request_user_modify_integration_test.go b/connectors/ACCESS/AMIE-Processor/handler/request_user_modify_integration_test.go
index 6e98f5133..39feb9110 100644
--- a/connectors/ACCESS/AMIE-Processor/handler/request_user_modify_integration_test.go
+++ b/connectors/ACCESS/AMIE-Processor/handler/request_user_modify_integration_test.go
@@ -44,14 +44,14 @@ func seedUser(t *testing.T, database *sqlx.DB, userGlobalID, firstName, lastName
t.Helper()
orgID := uuid.NewString()
if _, err := database.Exec(
- "INSERT INTO organizations (id, originated_id, name) VALUES (?, ?, ?)",
+ "INSERT INTO organizations (id, originated_id, name) VALUES ($1, $2, $3)",
orgID, "SEED-ORG", "Seed Org",
); err != nil {
t.Fatalf("seed organization: %v", err)
}
userID = uuid.NewString()
if _, err := database.Exec(
- "INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status) VALUES (?, ?, ?, ?, ?, ?, ?)",
+ "INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status) VALUES ($1, $2, $3, $4, $5, $6, $7)",
userID, orgID, firstName, lastName, "", email, string(models.UserActive),
); err != nil {
t.Fatalf("seed user: %v", err)
@@ -66,7 +66,7 @@ func seedUser(t *testing.T, database *sqlx.DB, userGlobalID, firstName, lastName
t.Fatalf("marshal seed metadata: %v", err)
}
if _, err := database.Exec(
- "INSERT INTO user_identities (id, user_id, source, external_id, email, metadata) VALUES (?, ?, ?, ?, ?, ?)",
+ "INSERT INTO user_identities (id, user_id, source, external_id, email, metadata) VALUES ($1, $2, $3, $4, $5, $6)",
identityID, userID, "access", userGlobalID, email, string(seedMeta),
); err != nil {
t.Fatalf("seed user_identity: %v", err)
@@ -74,7 +74,7 @@ func seedUser(t *testing.T, database *sqlx.DB, userGlobalID, firstName, lastName
for _, dn := range dns {
dnID := uuid.NewString()
if _, err := database.Exec(
- "INSERT INTO amie_user_dns (id, user_id, dn) VALUES (?, ?, ?)",
+ "INSERT INTO amie_user_dns (id, user_id, dn) VALUES ($1, $2, $3)",
dnID, userID, dn,
); err != nil {
t.Fatalf("seed amie_user_dns: %v", err)
@@ -90,7 +90,7 @@ func listUserDNs(t *testing.T, database *sqlx.DB, userID string) []string {
t.Helper()
var dns []string
if err := database.Select(&dns,
- "SELECT dn FROM amie_user_dns WHERE user_id = ? ORDER BY dn",
+ "SELECT dn FROM amie_user_dns WHERE user_id = $1 ORDER BY dn",
userID,
); err != nil {
t.Fatalf("list amie_user_dns: %v", err)
@@ -168,7 +168,7 @@ func TestRequestUserModify_ReplaceHappyPath(t *testing.T) {
LastName string `db:"last_name"`
Email string `db:"email"`
}
- if err := database.Get(&user, "SELECT first_name, last_name, email FROM users WHERE id = ?", userID); err != nil {
+ if err := database.Get(&user, "SELECT first_name, last_name, email FROM users WHERE id = $1", userID); err != nil {
t.Fatalf("read user: %v", err)
}
if user.FirstName != "New" {
@@ -186,7 +186,7 @@ func TestRequestUserModify_ReplaceHappyPath(t *testing.T) {
Email *string `db:"email"`
Metadata *string `db:"metadata"`
}
- if err := database.Get(&ident, "SELECT email, metadata FROM user_identities WHERE id = ?", identityID); err != nil {
+ if err := database.Get(&ident, "SELECT email, metadata FROM user_identities WHERE id = $1", identityID); err != nil {
t.Fatalf("read user_identity: %v", err)
}
if ident.Email == nil || *ident.Email != "new@example.edu" {
@@ -423,7 +423,7 @@ func TestRequestUserModify_DeleteHappyPath(t *testing.T) {
Email string `db:"email"`
Status string `db:"status"`
}
- if err := database.Get(&user, "SELECT first_name, last_name, email, status FROM users WHERE id = ?", userID); err != nil {
+ if err := database.Get(&user, "SELECT first_name, last_name, email, status FROM users WHERE id = $1", userID); err != nil {
t.Fatalf("read user: %v", err)
}
if user.FirstName != origFirst || user.LastName != origLast || user.Email != origEmail {
@@ -438,7 +438,7 @@ func TestRequestUserModify_DeleteHappyPath(t *testing.T) {
t.Errorf("user_identities: got %d, want 1", got)
}
var identUserID string
- if err := database.Get(&identUserID, "SELECT user_id FROM user_identities WHERE id = ?", identityID); err != nil {
+ if err := database.Get(&identUserID, "SELECT user_id FROM user_identities WHERE id = $1", identityID); err != nil {
t.Fatalf("read user_identity: %v", err)
}
if identUserID != userID {
@@ -543,7 +543,7 @@ func TestRequestUserModify_UnsupportedActionType(t *testing.T) {
FirstName string `db:"first_name"`
Email string `db:"email"`
}
- if err := database.Get(&user, "SELECT first_name, email FROM users WHERE id = ?", userID); err != nil {
+ if err := database.Get(&user, "SELECT first_name, email FROM users WHERE id = $1", userID); err != nil {
t.Fatalf("read user: %v", err)
}
if user.FirstName != "Stay" || user.Email != "user1005@example.edu" {
diff --git a/connectors/ACCESS/AMIE-Processor/pipeline/baseline_integration_test.go b/connectors/ACCESS/AMIE-Processor/pipeline/baseline_integration_test.go
index 5f5d57a18..e78ddee6e 100644
--- a/connectors/ACCESS/AMIE-Processor/pipeline/baseline_integration_test.go
+++ b/connectors/ACCESS/AMIE-Processor/pipeline/baseline_integration_test.go
@@ -95,7 +95,7 @@ func TestPipeline_BaselineDeterminism(t *testing.T) {
for _, ac := range actionCounts {
var n int
if err := pipe.db.Get(&n,
- "SELECT COUNT(*) FROM audit_events WHERE source = 'amie' AND event_type = ?", ac.action,
+ "SELECT COUNT(*) FROM audit_events WHERE source = 'amie' AND event_type = $1", ac.action,
); err != nil {
t.Fatalf("count audit action %s: %v", ac.action, err)
}
diff --git a/connectors/ACCESS/AMIE-Processor/pipeline/integration_common.go b/connectors/ACCESS/AMIE-Processor/pipeline/integration_common.go
index cd6d2383c..c07e04b6b 100644
--- a/connectors/ACCESS/AMIE-Processor/pipeline/integration_common.go
+++ b/connectors/ACCESS/AMIE-Processor/pipeline/integration_common.go
@@ -25,6 +25,7 @@ import (
"log/slog"
"net/http"
"os"
+ "strings"
"sync"
"testing"
"time"
@@ -143,23 +144,15 @@ func truncateAll(t *testing.T, database *sqlx.DB) {
"users",
"organizations",
}
- if _, err := database.Exec("SET FOREIGN_KEY_CHECKS = 0"); err != nil {
- t.Fatalf("disable FK: %v", err)
- }
- for _, tbl := range tables {
- if _, err := database.Exec("TRUNCATE TABLE " + tbl); err != nil {
- t.Fatalf("truncate %s: %v", tbl, err)
- }
- }
- if _, err := database.Exec("SET FOREIGN_KEY_CHECKS = 1"); err != nil {
- t.Fatalf("re-enable FK: %v", err)
+ if _, err := database.Exec("TRUNCATE TABLE " + strings.Join(tables, ", ") + " CASCADE"); err != nil {
+ t.Fatalf("truncate: %v", err)
}
}
func seedCluster(t *testing.T, database *sqlx.DB) {
t.Helper()
if _, err := database.Exec(
- "INSERT INTO compute_clusters (id, name) VALUES (?, ?)",
+ "INSERT INTO compute_clusters (id, name) VALUES ($1, $2)",
testClusterID, "default-cluster",
); err != nil {
t.Fatalf("seed cluster: %v", err)
@@ -167,7 +160,7 @@ func seedCluster(t *testing.T, database *sqlx.DB) {
// Catalog entry matching the mock packets' ResourceList, so
// request_project_create exercises the resource-mapping path.
if _, err := database.Exec(
- "INSERT INTO compute_allocation_resources (id, name, resource_type, resource_amount, compute_cluster_id) VALUES (?, ?, ?, ?, ?)",
+ "INSERT INTO compute_allocation_resources (id, name, resource_type, resource_amount, compute_cluster_id) VALUES ($1, $2, $3, $4, $5)",
"baseline-resource", "baseline-cluster.example.edu", "CPU_HOURS", 0, testClusterID,
); err != nil {
t.Fatalf("seed catalog resource: %v", err)
diff --git a/connectors/ACCESS/AMIE-Processor/store/audit_store.go b/connectors/ACCESS/AMIE-Processor/store/audit_store.go
index 7fd1eca1f..dcb6bafc8 100644
--- a/connectors/ACCESS/AMIE-Processor/store/audit_store.go
+++ b/connectors/ACCESS/AMIE-Processor/store/audit_store.go
@@ -32,17 +32,17 @@ type AuditExtrasStore interface {
Save(ctx context.Context, tx *sql.Tx, e *model.AmieAuditExtras) error
}
-type mariaDBauditExtrasStore struct {
+type pgAuditExtrasStore struct {
db *sqlx.DB
}
func NewAuditExtrasStore(db *sqlx.DB) AuditExtrasStore {
- return &mariaDBauditExtrasStore{db: db}
+ return &pgAuditExtrasStore{db: db}
}
-func (s *mariaDBauditExtrasStore) Save(ctx context.Context, tx *sql.Tx, e *model.AmieAuditExtras) error {
+func (s *pgAuditExtrasStore) Save(ctx context.Context, tx *sql.Tx, e *model.AmieAuditExtras) error {
_, err := tx.ExecContext(ctx,
- `INSERT INTO amie_audit_extras (audit_event_id, packet_id, event_id) VALUES (?, ?, ?)`,
+ `INSERT INTO amie_audit_extras (audit_event_id, packet_id, event_id) VALUES ($1, $2, $3)`,
e.AuditEventID, e.PacketID, e.EventID)
return err
}
diff --git a/connectors/ACCESS/AMIE-Processor/store/error_store.go b/connectors/ACCESS/AMIE-Processor/store/error_store.go
index 7b083afac..05f5aa28f 100644
--- a/connectors/ACCESS/AMIE-Processor/store/error_store.go
+++ b/connectors/ACCESS/AMIE-Processor/store/error_store.go
@@ -29,18 +29,18 @@ type ProcessingErrorStore interface {
Save(ctx context.Context, tx *sql.Tx, e *model.ProcessingError) error
}
-type mariaDBProcessingErrorStore struct {
+type pgProcessingErrorStore struct {
db *sqlx.DB
}
func NewProcessingErrorStore(db *sqlx.DB) ProcessingErrorStore {
- return &mariaDBProcessingErrorStore{db: db}
+ return &pgProcessingErrorStore{db: db}
}
-func (s *mariaDBProcessingErrorStore) Save(ctx context.Context, tx *sql.Tx, e *model.ProcessingError) error {
+func (s *pgProcessingErrorStore) Save(ctx context.Context, tx *sql.Tx, e *model.ProcessingError) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO amie_processing_errors (packet_id, event_id, occurred_at, summary, detail)
- VALUES (?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5)`,
e.PacketID, e.EventID, e.OccurredAt, e.Summary, e.Detail)
return err
}
diff --git a/connectors/ACCESS/AMIE-Processor/store/event_store.go b/connectors/ACCESS/AMIE-Processor/store/event_store.go
index 547137a05..df7b08420 100644
--- a/connectors/ACCESS/AMIE-Processor/store/event_store.go
+++ b/connectors/ACCESS/AMIE-Processor/store/event_store.go
@@ -34,19 +34,19 @@ type EventStore interface {
Update(ctx context.Context, tx *sql.Tx, e *model.ProcessingEvent) error
}
-type mariaDBEventStore struct {
+type pgEventStore struct {
db *sqlx.DB
}
func NewEventStore(db *sqlx.DB) EventStore {
- return &mariaDBEventStore{db: db}
+ return &pgEventStore{db: db}
}
-func (s *mariaDBEventStore) FindByID(ctx context.Context, id string) (*model.ProcessingEvent, error) {
+func (s *pgEventStore) FindByID(ctx context.Context, id string) (*model.ProcessingEvent, error) {
var e model.ProcessingEvent
err := s.db.GetContext(ctx, &e,
`SELECT id, packet_id, type, status, attempts, created_at, started_at, finished_at, last_error, next_retry_at
- FROM amie_processing_events WHERE id = ?`, id)
+ FROM amie_processing_events WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -56,7 +56,7 @@ func (s *mariaDBEventStore) FindByID(ctx context.Context, id string) (*model.Pro
return &e, nil
}
-func (s *mariaDBEventStore) FindTop50EventsToProcess(ctx context.Context, statuses []model.ProcessingStatus, now time.Time) ([]model.EventWithPacket, error) {
+func (s *pgEventStore) FindTop50EventsToProcess(ctx context.Context, statuses []model.ProcessingStatus, now time.Time) ([]model.EventWithPacket, error) {
query := `SELECT e.id, e.packet_id, e.type, e.status, e.attempts, e.created_at, e.started_at, e.finished_at, e.last_error, e.next_retry_at,
p.amie_id AS packet_amie_id, p.type AS packet_type, p.raw_json AS packet_raw_json
FROM amie_processing_events e
@@ -71,7 +71,7 @@ func (s *mariaDBEventStore) FindTop50EventsToProcess(ctx context.Context, status
if err != nil {
return nil, err
}
- // Rebind for the MySQL/MariaDB driver.
+ // Rebind numbers the expanded placeholders for the driver.
query = s.db.Rebind(query)
var results []model.EventWithPacket
@@ -82,20 +82,20 @@ func (s *mariaDBEventStore) FindTop50EventsToProcess(ctx context.Context, status
return results, nil
}
-func (s *mariaDBEventStore) Save(ctx context.Context, tx *sql.Tx, e *model.ProcessingEvent) error {
+func (s *pgEventStore) Save(ctx context.Context, tx *sql.Tx, e *model.ProcessingEvent) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO amie_processing_events (id, packet_id, type, status, attempts, created_at, started_at, finished_at, last_error, next_retry_at)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
e.ID, e.PacketID, e.Type, e.Status, e.Attempts,
e.CreatedAt, e.StartedAt, e.FinishedAt,
e.LastError, e.NextRetryAt)
return err
}
-func (s *mariaDBEventStore) Update(ctx context.Context, tx *sql.Tx, e *model.ProcessingEvent) error {
+func (s *pgEventStore) Update(ctx context.Context, tx *sql.Tx, e *model.ProcessingEvent) error {
_, err := tx.ExecContext(ctx,
- `UPDATE amie_processing_events SET status = ?, attempts = ?, started_at = ?, finished_at = ?, last_error = ?, next_retry_at = ?
- WHERE id = ?`,
+ `UPDATE amie_processing_events SET status = $1, attempts = $2, started_at = $3, finished_at = $4, last_error = $5, next_retry_at = $6
+ WHERE id = $7`,
e.Status, e.Attempts,
e.StartedAt, e.FinishedAt,
e.LastError, e.NextRetryAt, e.ID)
diff --git a/connectors/ACCESS/AMIE-Processor/store/packet_audit_store.go b/connectors/ACCESS/AMIE-Processor/store/packet_audit_store.go
index 286349927..20d7d7029 100644
--- a/connectors/ACCESS/AMIE-Processor/store/packet_audit_store.go
+++ b/connectors/ACCESS/AMIE-Processor/store/packet_audit_store.go
@@ -34,12 +34,12 @@ type PacketAuditStore interface {
ListAuditsForPacket(ctx context.Context, packetID string) ([]models.TraceEvent, error)
}
-type mysqlPacketAuditStore struct {
+type pgPacketAuditStore struct {
db *sqlx.DB
}
func NewPacketAuditStore(db *sqlx.DB) PacketAuditStore {
- return &mysqlPacketAuditStore{db: db}
+ return &pgPacketAuditStore{db: db}
}
const packetAuditSelect = `
@@ -54,11 +54,11 @@ SELECT
ae.event_time AS created_at
FROM audit_events ae
JOIN amie_audit_extras x ON x.audit_event_id = ae.id
-WHERE x.packet_id = ?
+WHERE x.packet_id = $1
ORDER BY ae.event_time ASC, ae.span_id ASC
`
-func (s *mysqlPacketAuditStore) ListAuditsForPacket(ctx context.Context, packetID string) ([]models.TraceEvent, error) {
+func (s *pgPacketAuditStore) ListAuditsForPacket(ctx context.Context, packetID string) ([]models.TraceEvent, error) {
type row struct {
SpanID string `db:"span_id"`
ParentSpanID string `db:"parent_span_id"`
diff --git a/connectors/ACCESS/AMIE-Processor/store/packet_store.go b/connectors/ACCESS/AMIE-Processor/store/packet_store.go
index 511482e20..06df401af 100644
--- a/connectors/ACCESS/AMIE-Processor/store/packet_store.go
+++ b/connectors/ACCESS/AMIE-Processor/store/packet_store.go
@@ -62,20 +62,20 @@ type PacketStore interface {
GetStats(ctx context.Context, window time.Duration) ([]StatBucket, error)
}
-type mariaDBPacketStore struct {
+type pgPacketStore struct {
db *sqlx.DB
}
func NewPacketStore(db *sqlx.DB) PacketStore {
- return &mariaDBPacketStore{db: db}
+ return &pgPacketStore{db: db}
}
const packetColumns = `id, amie_id, type, status, raw_json, received_at, decoded_at, processed_at, retries, last_error`
-func (s *mariaDBPacketStore) FindByAmieID(ctx context.Context, amieID int64) (*model.Packet, error) {
+func (s *pgPacketStore) FindByAmieID(ctx context.Context, amieID int64) (*model.Packet, error) {
var p model.Packet
err := s.db.GetContext(ctx, &p,
- `SELECT `+packetColumns+` FROM amie_packets WHERE amie_id = ?`, amieID)
+ `SELECT `+packetColumns+` FROM amie_packets WHERE amie_id = $1`, amieID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -85,10 +85,10 @@ func (s *mariaDBPacketStore) FindByAmieID(ctx context.Context, amieID int64) (*m
return &p, nil
}
-func (s *mariaDBPacketStore) FindByID(ctx context.Context, id string) (*model.Packet, error) {
+func (s *pgPacketStore) FindByID(ctx context.Context, id string) (*model.Packet, error) {
var p model.Packet
err := s.db.GetContext(ctx, &p,
- `SELECT `+packetColumns+` FROM amie_packets WHERE id = ?`, id)
+ `SELECT `+packetColumns+` FROM amie_packets WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -98,30 +98,30 @@ func (s *mariaDBPacketStore) FindByID(ctx context.Context, id string) (*model.Pa
return &p, nil
}
-func (s *mariaDBPacketStore) Save(ctx context.Context, tx *sql.Tx, p *model.Packet) error {
+func (s *pgPacketStore) Save(ctx context.Context, tx *sql.Tx, p *model.Packet) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO amie_packets (id, amie_id, type, status, raw_json, received_at, decoded_at, processed_at, retries, last_error)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
p.ID, p.AmieID, p.Type, p.Status, p.RawJSON,
p.ReceivedAt, p.DecodedAt, p.ProcessedAt,
p.Retries, p.LastError)
return err
}
-func (s *mariaDBPacketStore) Update(ctx context.Context, tx *sql.Tx, p *model.Packet) error {
+func (s *pgPacketStore) Update(ctx context.Context, tx *sql.Tx, p *model.Packet) error {
_, err := tx.ExecContext(ctx,
- `UPDATE amie_packets SET type = ?, status = ?, raw_json = ?, decoded_at = ?, processed_at = ?, retries = ?, last_error = ?
- WHERE id = ?`,
+ `UPDATE amie_packets SET type = $1, status = $2, raw_json = $3, decoded_at = $4, processed_at = $5, retries = $6, last_error = $7
+ WHERE id = $8`,
p.Type, p.Status, p.RawJSON, p.DecodedAt, p.ProcessedAt,
p.Retries, p.LastError, p.ID)
return err
}
-func (s *mariaDBPacketStore) ListPackets(ctx context.Context, f PacketListFilter) ([]model.Packet, int, error) {
+func (s *pgPacketStore) ListPackets(ctx context.Context, f PacketListFilter) ([]model.Packet, int, error) {
where, args := buildPacketFilter(f)
var total int
- if err := s.db.GetContext(ctx, &total, `SELECT COUNT(*) FROM amie_packets`+where, args...); err != nil {
+ if err := s.db.GetContext(ctx, &total, s.db.Rebind(`SELECT COUNT(*) FROM amie_packets`+where), args...); err != nil {
return nil, 0, err
}
@@ -137,7 +137,7 @@ func (s *mariaDBPacketStore) ListPackets(ctx context.Context, f PacketListFilter
packetColumns, where, limit, offset)
var rows []model.Packet
- if err := s.db.SelectContext(ctx, &rows, q, args...); err != nil {
+ if err := s.db.SelectContext(ctx, &rows, s.db.Rebind(q), args...); err != nil {
return nil, 0, err
}
return rows, total, nil
@@ -155,7 +155,7 @@ func buildPacketFilter(f PacketListFilter) (string, []any) {
args = append(args, f.Type)
}
if f.Query != "" {
- clauses = append(clauses, "(id LIKE ? OR CAST(amie_id AS CHAR) LIKE ?)")
+ clauses = append(clauses, "(id ILIKE ? OR CAST(amie_id AS TEXT) ILIKE ?)")
like := "%" + f.Query + "%"
args = append(args, like, like)
}
@@ -173,28 +173,28 @@ func buildPacketFilter(f PacketListFilter) (string, []any) {
return " WHERE " + strings.Join(clauses, " AND "), args
}
-func (s *mariaDBPacketStore) ListPacketEvents(ctx context.Context, packetID string) ([]model.ProcessingEvent, error) {
+func (s *pgPacketStore) ListPacketEvents(ctx context.Context, packetID string) ([]model.ProcessingEvent, error) {
var rows []model.ProcessingEvent
err := s.db.SelectContext(ctx, &rows,
`SELECT id, packet_id, type, status, attempts, created_at, started_at, finished_at, last_error, next_retry_at
- FROM amie_processing_events WHERE packet_id = ? ORDER BY created_at ASC`, packetID)
+ FROM amie_processing_events WHERE packet_id = $1 ORDER BY created_at ASC`, packetID)
if err != nil {
return nil, err
}
return rows, nil
}
-func (s *mariaDBPacketStore) GetStats(ctx context.Context, window time.Duration) ([]StatBucket, error) {
+func (s *pgPacketStore) GetStats(ctx context.Context, window time.Duration) ([]StatBucket, error) {
if window <= 0 {
window = 30 * 24 * time.Hour
}
since := time.Now().UTC().Add(-window)
var rows []StatBucket
err := s.db.SelectContext(ctx, &rows,
- `SELECT DATE(received_at) AS date, status, type, COUNT(*) AS count
+ `SELECT TO_CHAR(received_at, 'YYYY-MM-DD') AS date, status, type, COUNT(*) AS count
FROM amie_packets
- WHERE received_at >= ?
- GROUP BY DATE(received_at), status, type
+ WHERE received_at >= $1
+ GROUP BY TO_CHAR(received_at, 'YYYY-MM-DD'), status, type
ORDER BY date ASC`, since)
if err != nil {
return nil, err
diff --git a/connectors/ACCESS/AMIE-Processor/store/user_dn_store.go b/connectors/ACCESS/AMIE-Processor/store/user_dn_store.go
index 8f8ab241d..bff8908b3 100644
--- a/connectors/ACCESS/AMIE-Processor/store/user_dn_store.go
+++ b/connectors/ACCESS/AMIE-Processor/store/user_dn_store.go
@@ -28,7 +28,7 @@ import (
)
type UserDNStore interface {
- // Add inserts a DN row. Uses INSERT IGNORE so a duplicate DN is a silent
+ // Add inserts a DN row. ON CONFLICT DO NOTHING keeps a duplicate DN a silent
// no-op rather than an error.
Add(ctx context.Context, tx *sql.Tx, d *model.UserDN) error
// ListByUser returns every DN bound to userID, oldest first.
@@ -40,30 +40,30 @@ type UserDNStore interface {
ReassignUser(ctx context.Context, tx *sql.Tx, fromUserID, toUserID string) error
}
-type mariaDBUserDNStore struct {
+type pgUserDNStore struct {
db *sqlx.DB
}
func NewUserDNStore(db *sqlx.DB) UserDNStore {
- return &mariaDBUserDNStore{db: db}
+ return &pgUserDNStore{db: db}
}
-func (s *mariaDBUserDNStore) Add(ctx context.Context, tx *sql.Tx, d *model.UserDN) error {
+func (s *pgUserDNStore) Add(ctx context.Context, tx *sql.Tx, d *model.UserDN) error {
if d.ID == "" {
d.ID = uuid.NewString()
}
_, err := tx.ExecContext(ctx,
- `INSERT IGNORE INTO amie_user_dns (id, user_id, dn) VALUES (?, ?, ?)`,
+ `INSERT INTO amie_user_dns (id, user_id, dn) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING`,
d.ID, d.UserID, d.DN)
return err
}
-func (s *mariaDBUserDNStore) ListByUser(ctx context.Context, userID string) ([]model.UserDN, error) {
+func (s *pgUserDNStore) ListByUser(ctx context.Context, userID string) ([]model.UserDN, error) {
var out []model.UserDN
err := s.db.SelectContext(ctx, &out,
`SELECT id, user_id, dn, created_at
FROM amie_user_dns
- WHERE user_id = ?
+ WHERE user_id = $1
ORDER BY created_at ASC`, userID)
if err != nil {
return nil, err
@@ -71,24 +71,24 @@ func (s *mariaDBUserDNStore) ListByUser(ctx context.Context, userID string) ([]m
return out, nil
}
-func (s *mariaDBUserDNStore) DeleteByID(ctx context.Context, tx *sql.Tx, id string) error {
+func (s *pgUserDNStore) DeleteByID(ctx context.Context, tx *sql.Tx, id string) error {
_, err := tx.ExecContext(ctx,
- `DELETE FROM amie_user_dns WHERE id = ?`, id)
+ `DELETE FROM amie_user_dns WHERE id = $1`, id)
return err
}
-func (s *mariaDBUserDNStore) ReassignUser(ctx context.Context, tx *sql.Tx, fromUserID, toUserID string) error {
+func (s *pgUserDNStore) ReassignUser(ctx context.Context, tx *sql.Tx, fromUserID, toUserID string) error {
// Drop fromUserID's rows whose DN is already held by toUserID; UNIQUE(dn)
// would otherwise reject the UPDATE.
if _, err := tx.ExecContext(ctx,
`DELETE FROM amie_user_dns
- WHERE user_id = ?
- AND dn IN (SELECT dn FROM (SELECT dn FROM amie_user_dns WHERE user_id = ?) AS s)`,
+ WHERE user_id = $1
+ AND dn IN (SELECT dn FROM (SELECT dn FROM amie_user_dns WHERE user_id = $2) AS s)`,
fromUserID, toUserID); err != nil {
return err
}
_, err := tx.ExecContext(ctx,
- `UPDATE amie_user_dns SET user_id = ? WHERE user_id = ?`,
+ `UPDATE amie_user_dns SET user_id = $1 WHERE user_id = $2`,
toUserID, fromUserID)
return err
}
diff --git a/connectors/ACCESS/AMIE-Processor/worker/integration_common.go b/connectors/ACCESS/AMIE-Processor/worker/integration_common.go
index 726792463..274976689 100644
--- a/connectors/ACCESS/AMIE-Processor/worker/integration_common.go
+++ b/connectors/ACCESS/AMIE-Processor/worker/integration_common.go
@@ -22,6 +22,7 @@ package worker
import (
"context"
"os"
+ "strings"
"sync"
"testing"
@@ -88,23 +89,16 @@ func setupTestDB(t *testing.T) *sqlx.DB {
// core domain tables.
func truncateAMIETables(t *testing.T, database *sqlx.DB) {
t.Helper()
- if _, err := database.Exec("SET FOREIGN_KEY_CHECKS = 0"); err != nil {
- t.Fatalf("disable FK: %v", err)
- }
- for _, tbl := range []string{
+ tables := []string{
"amie_audit_extras",
"audit_events",
"amie_processing_errors",
"amie_processing_events",
"amie_packets",
"amie_user_dns",
- } {
- if _, err := database.Exec("TRUNCATE TABLE " + tbl); err != nil {
- t.Fatalf("truncate %s: %v", tbl, err)
- }
}
- if _, err := database.Exec("SET FOREIGN_KEY_CHECKS = 1"); err != nil {
- t.Fatalf("re-enable FK: %v", err)
+ if _, err := database.Exec("TRUNCATE TABLE " + strings.Join(tables, ", ") + " CASCADE"); err != nil {
+ t.Fatalf("truncate: %v", err)
}
}
diff --git a/connectors/ACCESS/AMIE-Processor/worker/processor_failures_integration_test.go b/connectors/ACCESS/AMIE-Processor/worker/processor_failures_integration_test.go
index 7e8b45609..b5e68c303 100644
--- a/connectors/ACCESS/AMIE-Processor/worker/processor_failures_integration_test.go
+++ b/connectors/ACCESS/AMIE-Processor/worker/processor_failures_integration_test.go
@@ -46,7 +46,7 @@ func TestProcessor_RouterWriteRolledBackOnError(t *testing.T) {
work: func(ctx context.Context, tx *sql.Tx) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO audit_events (id, event_type, event_time, entity_id, entity_type, details, source)
- VALUES (?, ?, UTC_TIMESTAMP(6), ?, ?, ?, ?)`,
+ VALUES ($1, $2, NOW(), $3, $4, $5, $6)`,
probeID, "CREATE_PROJECT", packetID, "probe", auditProbe, "amie",
)
return err
@@ -60,7 +60,7 @@ func TestProcessor_RouterWriteRolledBackOnError(t *testing.T) {
// The audit write must NOT be visible, tx rolled back.
var hits int
if err := database.Get(&hits,
- "SELECT COUNT(*) FROM audit_events WHERE details = ?", auditProbe,
+ "SELECT COUNT(*) FROM audit_events WHERE details = $1", auditProbe,
); err != nil {
t.Fatalf("query audit probe: %v", err)
}
diff --git a/connectors/ACCESS/AMIE-Processor/worker/processor_integration_test.go b/connectors/ACCESS/AMIE-Processor/worker/processor_integration_test.go
index ce08c2844..f61dc2674 100644
--- a/connectors/ACCESS/AMIE-Processor/worker/processor_integration_test.go
+++ b/connectors/ACCESS/AMIE-Processor/worker/processor_integration_test.go
@@ -67,7 +67,7 @@ func seedNewEvent(t *testing.T, database *sqlx.DB) (packetID, eventID string) {
if _, err := database.Exec(
`INSERT INTO amie_packets (id, amie_id, type, status, raw_json, received_at, retries)
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
packetID, time.Now().UnixNano(), "request_project_create", string(model.PacketStatusNew),
`{"type":"request_project_create","body":{}}`, now, 0,
); err != nil {
@@ -76,7 +76,7 @@ func seedNewEvent(t *testing.T, database *sqlx.DB) (packetID, eventID string) {
if _, err := database.Exec(
`INSERT INTO amie_processing_events (id, packet_id, type, status, attempts, created_at)
- VALUES (?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6)`,
eventID, packetID, string(model.EventTypeDecodePacket), string(model.ProcessingStatusNew), 0, now,
); err != nil {
t.Fatalf("seed event: %v", err)
@@ -103,7 +103,7 @@ func readEvent(t *testing.T, database *sqlx.DB, id string) *model.ProcessingEven
var e model.ProcessingEvent
if err := database.Get(&e,
`SELECT id, packet_id, type, status, attempts, created_at, started_at, finished_at, last_error, next_retry_at
- FROM amie_processing_events WHERE id = ?`, id,
+ FROM amie_processing_events WHERE id = $1`, id,
); err != nil {
t.Fatalf("read event %s: %v", id, err)
}
@@ -115,7 +115,7 @@ func readPacket(t *testing.T, database *sqlx.DB, id string) *model.Packet {
var p model.Packet
if err := database.Get(&p,
`SELECT id, amie_id, type, status, raw_json, received_at, decoded_at, processed_at, retries, last_error
- FROM amie_packets WHERE id = ?`, id,
+ FROM amie_packets WHERE id = $1`, id,
); err != nil {
t.Fatalf("read packet %s: %v", id, err)
}
@@ -128,7 +128,7 @@ func readPacket(t *testing.T, database *sqlx.DB, id string) *model.Packet {
func forceRetryNow(t *testing.T, database *sqlx.DB, eventID string) {
t.Helper()
if _, err := database.Exec(
- "UPDATE amie_processing_events SET next_retry_at = NULL WHERE id = ?", eventID,
+ "UPDATE amie_processing_events SET next_retry_at = NULL WHERE id = $1", eventID,
); err != nil {
t.Fatalf("force retry: %v", err)
}
diff --git a/connectors/Analytics/pkg/analytics/analytics_integration_test.go b/connectors/Analytics/pkg/analytics/analytics_integration_test.go
index 482076ad3..95df7baa3 100644
--- a/connectors/Analytics/pkg/analytics/analytics_integration_test.go
+++ b/connectors/Analytics/pkg/analytics/analytics_integration_test.go
@@ -36,7 +36,7 @@ import (
func seedCluster(t *testing.T, db *sqlx.DB) string {
t.Helper()
id := uuid.NewString()
- if _, err := db.Exec(`INSERT INTO compute_clusters (id, name) VALUES (?, ?)`, id, "Cluster "+id[:8]); err != nil {
+ if _, err := db.Exec(`INSERT INTO compute_clusters (id, name) VALUES ($1, $2)`, id, "Cluster "+id[:8]); err != nil {
t.Fatalf("seed cluster: %v", err)
}
return id
@@ -47,7 +47,7 @@ func seedProject(t *testing.T, db *sqlx.DB, piUserID string) string {
id := uuid.NewString()
if _, err := db.Exec(
`INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status, created_time)
- VALUES (?, ?, ?, ?, ?, ?, NOW(6))`,
+ VALUES ($1, $2, $3, $4, $5, $6, NOW())`,
id, "REC-"+id[:8], "Project "+id[:8], "TEST", piUserID, string(models.ProjectActive),
); err != nil {
t.Fatalf("seed project: %v", err)
@@ -58,7 +58,7 @@ func seedProject(t *testing.T, db *sqlx.DB, piUserID string) string {
func seedProjectRole(t *testing.T, db *sqlx.DB, projectID, userID string, role models.ProjectRole) {
t.Helper()
if _, err := db.Exec(
- `INSERT INTO project_memberships (project_id, user_id, role, added_time) VALUES (?, ?, ?, NOW(6))`,
+ `INSERT INTO project_memberships (project_id, user_id, role, added_time) VALUES ($1, $2, $3, NOW())`,
projectID, userID, string(role),
); err != nil {
t.Fatalf("seed project role: %v", err)
@@ -71,7 +71,7 @@ func seedAllocation(t *testing.T, db *sqlx.DB, projectID, clusterID string, init
if _, err := db.Exec(
`INSERT INTO compute_allocations
(id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
id, projectID, "alloc-"+id[:8], string(models.ACTIVE), clusterID, initialSU, start, end,
); err != nil {
t.Fatalf("seed allocation: %v", err)
@@ -85,7 +85,7 @@ func seedAllocMember(t *testing.T, db *sqlx.DB, allocID, userID string) {
if _, err := db.Exec(
`INSERT INTO compute_allocation_memberships
(id, compute_allocation_id, user_id, start_time, end_time, membership_status)
- VALUES (?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6)`,
uuid.NewString(), allocID, userID, now, now.AddDate(1, 0, 0), string(models.ACTIVE),
); err != nil {
t.Fatalf("seed alloc member: %v", err)
@@ -97,7 +97,7 @@ func seedResource(t *testing.T, db *sqlx.DB, clusterID, name, resourceType strin
id := uuid.NewString()
if _, err := db.Exec(
`INSERT INTO compute_allocation_resources (id, name, resource_type, resource_amount, compute_cluster_id)
- VALUES (?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5)`,
id, name, resourceType, 0, clusterID,
); err != nil {
t.Fatalf("seed resource: %v", err)
@@ -110,7 +110,7 @@ func seedUsage(t *testing.T, db *sqlx.DB, allocID, resourceID, userID string, su
if _, err := db.Exec(
`INSERT INTO compute_allocation_usages
(id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
uuid.NewString(), allocID, raw, su, at, userID, "job-"+uuid.NewString()[:8], resourceID,
); err != nil {
t.Fatalf("seed usage: %v", err)
@@ -308,7 +308,7 @@ func TestUsageSummary_InactiveMember_404(t *testing.T) {
if _, err := database.Exec(
`INSERT INTO compute_allocation_memberships
(id, compute_allocation_id, user_id, start_time, end_time, membership_status)
- VALUES (?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6)`,
uuid.NewString(), alloc, former, time.Now().UTC().AddDate(0, 0, -30), time.Now().UTC(), string(models.INACTIVE),
); err != nil {
t.Fatalf("seed inactive membership: %v", err)
@@ -419,7 +419,7 @@ func TestUsageSummary_MemberWithoutNameFallsBackToEmail(t *testing.T) {
start := time.Now().UTC().AddDate(0, 0, -2)
alloc := seedAllocation(t, database, project, cluster, 1000, start, time.Now().UTC().AddDate(0, 0, 28))
res := seedResource(t, database, cluster, "gpu-01", "GPU_HOURS")
- if _, err := database.Exec("UPDATE users SET first_name = '', last_name = '' WHERE id = ?", pi); err != nil {
+ if _, err := database.Exec("UPDATE users SET first_name = '', last_name = '' WHERE id = $1", pi); err != nil {
t.Fatalf("blank name: %v", err)
}
seedUsage(t, database, alloc, res, pi, 400, 40, time.Now().UTC().AddDate(0, 0, -1))
diff --git a/connectors/Analytics/pkg/analytics/integration_common_test.go b/connectors/Analytics/pkg/analytics/integration_common_test.go
index b082a861d..b8d858b43 100644
--- a/connectors/Analytics/pkg/analytics/integration_common_test.go
+++ b/connectors/Analytics/pkg/analytics/integration_common_test.go
@@ -22,6 +22,7 @@ package analytics
import (
"net/http"
"os"
+ "strings"
"sync"
"testing"
@@ -98,16 +99,8 @@ func truncateAll(t *testing.T, database *sqlx.DB) {
"users",
"organizations",
}
- if _, err := database.Exec("SET FOREIGN_KEY_CHECKS = 0"); err != nil {
- t.Fatalf("disable FK: %v", err)
- }
- for _, tbl := range tables {
- if _, err := database.Exec("TRUNCATE TABLE " + tbl); err != nil {
- t.Fatalf("truncate %s: %v", tbl, err)
- }
- }
- if _, err := database.Exec("SET FOREIGN_KEY_CHECKS = 1"); err != nil {
- t.Fatalf("re-enable FK: %v", err)
+ if _, err := database.Exec("TRUNCATE TABLE " + strings.Join(tables, ", ") + " CASCADE"); err != nil {
+ t.Fatalf("truncate: %v", err)
}
}
@@ -115,14 +108,14 @@ func seedUser(t *testing.T, database *sqlx.DB, email string) string {
t.Helper()
orgID := uuid.NewString()
if _, err := database.Exec(
- "INSERT INTO organizations (id, originated_id, name) VALUES (?, ?, ?)",
+ "INSERT INTO organizations (id, originated_id, name) VALUES ($1, $2, $3)",
orgID, "TEST-ORG-"+orgID[:8], "Test Org",
); err != nil {
t.Fatalf("seed org: %v", err)
}
userID := uuid.NewString()
if _, err := database.Exec(
- "INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status) VALUES (?, ?, ?, ?, ?, ?, ?)",
+ "INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status) VALUES ($1, $2, $3, $4, $5, $6, $7)",
userID, orgID, "Test", "User", "", email, string(models.UserActive),
); err != nil {
t.Fatalf("seed user %s: %v", email, err)
diff --git a/connectors/Analytics/pkg/analytics/store.go b/connectors/Analytics/pkg/analytics/store.go
index ba768d4d9..da5490d02 100644
--- a/connectors/Analytics/pkg/analytics/store.go
+++ b/connectors/Analytics/pkg/analytics/store.go
@@ -117,29 +117,29 @@ type Store interface {
IsActiveMember(ctx context.Context, allocationID, userID string) (bool, error)
}
-type mysqlStore struct {
+type pgStore struct {
db *sqlx.DB
}
-// NewStore returns a MySQL-backed analytics Store.
+// NewStore returns a PostgreSQL-backed analytics Store.
func NewStore(db *sqlx.DB) Store {
- return &mysqlStore{db: db}
+ return &pgStore{db: db}
}
-func (s *mysqlStore) ProjectsForUser(ctx context.Context, userID string) ([]ProjectRow, error) {
+func (s *pgStore) ProjectsForUser(ctx context.Context, userID string) ([]ProjectRow, error) {
var rows []ProjectRow
err := s.db.SelectContext(ctx, &rows,
`SELECT p.id, p.title, pm.role
FROM projects p
LEFT JOIN project_memberships pm
- ON pm.project_id = p.id AND pm.user_id = ?
+ ON pm.project_id = p.id AND pm.user_id = $1
WHERE p.id IN (
- SELECT project_id FROM project_memberships WHERE user_id = ?
+ SELECT project_id FROM project_memberships WHERE user_id = $2
UNION
SELECT ca.project_id
FROM compute_allocation_memberships cam
JOIN compute_allocations ca ON ca.id = cam.compute_allocation_id
- WHERE cam.user_id = ? AND cam.membership_status = 'ACTIVE'
+ WHERE cam.user_id = $3 AND cam.membership_status = 'ACTIVE'
)
ORDER BY p.title`, userID, userID, userID)
if err != nil {
@@ -148,7 +148,7 @@ func (s *mysqlStore) ProjectsForUser(ctx context.Context, userID string) ([]Proj
return rows, nil
}
-func (s *mysqlStore) AllocationsForProjects(ctx context.Context, projectIDs []string, userID string) ([]AllocationRow, error) {
+func (s *pgStore) AllocationsForProjects(ctx context.Context, projectIDs []string, userID string) ([]AllocationRow, error) {
if len(projectIDs) == 0 {
return nil, nil
}
@@ -183,26 +183,26 @@ func (s *mysqlStore) AllocationsForProjects(ctx context.Context, projectIDs []st
return rows, nil
}
-func (s *mysqlStore) TotalUsed(ctx context.Context, allocationID string) (float64, error) {
+func (s *pgStore) TotalUsed(ctx context.Context, allocationID string) (float64, error) {
var total float64
err := s.db.GetContext(ctx, &total,
`SELECT COALESCE(SUM(used_su_amount), 0)
FROM compute_allocation_usages
- WHERE compute_allocation_id = ?`, allocationID)
+ WHERE compute_allocation_id = $1`, allocationID)
if err != nil {
return 0, err
}
return total, nil
}
-func (s *mysqlStore) DailyUsage(ctx context.Context, allocationID string) ([]DailyRow, error) {
+func (s *pgStore) DailyUsage(ctx context.Context, allocationID string) ([]DailyRow, error) {
var rows []DailyRow
err := s.db.SelectContext(ctx, &rows,
`SELECT DATE(u.calculated_time) AS day,
u.compute_allocation_resource_id AS resource_id,
SUM(u.used_su_amount) AS credits
FROM compute_allocation_usages u
- WHERE u.compute_allocation_id = ?
+ WHERE u.compute_allocation_id = $1
GROUP BY day, resource_id
ORDER BY day`, allocationID)
if err != nil {
@@ -211,17 +211,17 @@ func (s *mysqlStore) DailyUsage(ctx context.Context, allocationID string) ([]Dai
return rows, nil
}
-func (s *mysqlStore) ResourceUsage(ctx context.Context, allocationID, callerID string) ([]ResourceRow, error) {
+func (s *pgStore) ResourceUsage(ctx context.Context, allocationID, callerID string) ([]ResourceRow, error) {
var rows []ResourceRow
err := s.db.SelectContext(ctx, &rows,
`SELECT r.id, r.name, r.resource_type,
SUM(u.used_su_amount) AS used,
COALESCE(SUM(u.used_raw_amount), 0) AS used_native,
- SUM(CASE WHEN u.user_id = ? THEN u.used_su_amount ELSE 0 END) AS used_by_caller
+ SUM(CASE WHEN u.user_id = $1 THEN u.used_su_amount ELSE 0 END) AS used_by_caller
FROM compute_allocation_usages u
JOIN compute_allocation_resources r
ON r.id = u.compute_allocation_resource_id
- WHERE u.compute_allocation_id = ?
+ WHERE u.compute_allocation_id = $2
GROUP BY r.id, r.name, r.resource_type
ORDER BY r.name`, callerID, allocationID)
if err != nil {
@@ -230,7 +230,7 @@ func (s *mysqlStore) ResourceUsage(ctx context.Context, allocationID, callerID s
return rows, nil
}
-func (s *mysqlStore) Jobs(ctx context.Context, allocationID string, userID *string, limit, offset int) ([]JobRow, int, error) {
+func (s *pgStore) Jobs(ctx context.Context, allocationID string, userID *string, limit, offset int) ([]JobRow, int, error) {
where := "u.compute_allocation_id = ?"
args := []any{allocationID}
if userID != nil {
@@ -240,13 +240,13 @@ func (s *mysqlStore) Jobs(ctx context.Context, allocationID string, userID *stri
var total int
if err := s.db.GetContext(ctx, &total,
- "SELECT COUNT(*) FROM compute_allocation_usages u WHERE "+where, args...); err != nil {
+ s.db.Rebind("SELECT COUNT(*) FROM compute_allocation_usages u WHERE "+where), args...); err != nil {
return nil, 0, err
}
var rows []JobRow
err := s.db.SelectContext(ctx, &rows,
- `SELECT u.id, u.job_id, u.calculated_time, u.user_id,
+ s.db.Rebind(`SELECT u.id, u.job_id, u.calculated_time, u.user_id,
TRIM(CONCAT(COALESCE(usr.first_name, ''), ' ', COALESCE(usr.last_name, ''))) AS user_name,
u.compute_allocation_resource_id AS resource_id,
COALESCE(r.name, '') AS resource_name,
@@ -258,14 +258,14 @@ func (s *mysqlStore) Jobs(ctx context.Context, allocationID string, userID *stri
LEFT JOIN compute_allocation_resources r ON r.id = u.compute_allocation_resource_id
WHERE `+where+`
ORDER BY u.calculated_time DESC, u.id
- LIMIT ? OFFSET ?`, append(args, limit, offset)...)
+ LIMIT ? OFFSET ?`), append(args, limit, offset)...)
if err != nil {
return nil, 0, err
}
return rows, total, nil
}
-func (s *mysqlStore) MemberUsage(ctx context.Context, allocationID string) ([]MemberRow, error) {
+func (s *pgStore) MemberUsage(ctx context.Context, allocationID string) ([]MemberRow, error) {
var rows []MemberRow
err := s.db.SelectContext(ctx, &rows,
`SELECT u.user_id,
@@ -273,7 +273,7 @@ func (s *mysqlStore) MemberUsage(ctx context.Context, allocationID string) ([]Me
SUM(u.used_su_amount) AS used
FROM compute_allocation_usages u
JOIN users usr ON usr.id = u.user_id
- WHERE u.compute_allocation_id = ?
+ WHERE u.compute_allocation_id = $1
GROUP BY u.user_id, name
ORDER BY used DESC`, allocationID)
if err != nil {
@@ -282,10 +282,10 @@ func (s *mysqlStore) MemberUsage(ctx context.Context, allocationID string) ([]Me
return rows, nil
}
-func (s *mysqlStore) ProjectRole(ctx context.Context, projectID, userID string) (string, error) {
+func (s *pgStore) ProjectRole(ctx context.Context, projectID, userID string) (string, error) {
var role string
err := s.db.GetContext(ctx, &role,
- `SELECT role FROM project_memberships WHERE project_id = ? AND user_id = ?`,
+ `SELECT role FROM project_memberships WHERE project_id = $1 AND user_id = $2`,
projectID, userID)
if err == sql.ErrNoRows {
return "", nil
@@ -296,12 +296,12 @@ func (s *mysqlStore) ProjectRole(ctx context.Context, projectID, userID string)
return role, nil
}
-func (s *mysqlStore) IsActiveMember(ctx context.Context, allocationID, userID string) (bool, error) {
+func (s *pgStore) IsActiveMember(ctx context.Context, allocationID, userID string) (bool, error) {
var exists bool
err := s.db.GetContext(ctx, &exists,
`SELECT EXISTS(
SELECT 1 FROM compute_allocation_memberships
- WHERE compute_allocation_id = ? AND user_id = ?
+ WHERE compute_allocation_id = $1 AND user_id = $2
AND membership_status = 'ACTIVE')`,
allocationID, userID)
if err != nil {
diff --git a/dev-ops/compose/dbinit/init-db.sh b/dev-ops/compose/dbinit/init-db.sh
index 772d743f7..259161cbf 100755
--- a/dev-ops/compose/dbinit/init-db.sh
+++ b/dev-ops/compose/dbinit/init-db.sh
@@ -17,17 +17,20 @@
# specific language governing permissions and limitations
# under the License.
-echo "Creating databases and users..."
+# Runs once on first container start (empty data dir). The custos database
+# and the admin superuser come from POSTGRES_DB / POSTGRES_USER.
+set -e
-mariadb -u root -p"${MARIADB_ROOT_PASSWORD}" <<-EOSQL
- CREATE DATABASE IF NOT EXISTS custos;
- CREATE DATABASE IF NOT EXISTS keycloak;
- CREATE DATABASE IF NOT EXISTS custos_signer;
- CREATE USER IF NOT EXISTS 'admin'@'%' IDENTIFIED BY 'admin';
- GRANT ALL PRIVILEGES ON custos.* TO 'admin'@'%';
- GRANT ALL PRIVILEGES ON keycloak.* TO 'admin'@'%';
- GRANT ALL PRIVILEGES ON custos_signer.* TO 'admin'@'%';
- FLUSH PRIVILEGES;
+echo "Creating databases..."
+
+psql -v ON_ERROR_STOP=1 -U "${POSTGRES_USER}" -d postgres <<-EOSQL
+ CREATE DATABASE keycloak;
+ CREATE DATABASE custos_signer;
+ CREATE DATABASE custos_test;
+ ALTER DATABASE custos SET timezone = 'UTC';
+ ALTER DATABASE keycloak SET timezone = 'UTC';
+ ALTER DATABASE custos_signer SET timezone = 'UTC';
+ ALTER DATABASE custos_test SET timezone = 'UTC';
EOSQL
-echo "Databases and users created"
\ No newline at end of file
+echo "Databases created"
diff --git a/dev-ops/compose/docker-compose.yml b/dev-ops/compose/docker-compose.yml
index 03df6c2e5..1ce3a8a00 100644
--- a/dev-ops/compose/docker-compose.yml
+++ b/dev-ops/compose/docker-compose.yml
@@ -16,24 +16,23 @@
# specific language governing permissions and limitations
# under the License.
-version: "3.8"
services:
db:
container_name: custos_db
- image: mariadb:11.2
+ # 17 is the newest major with a full year of patch releases; 18 is still young.
+ image: postgres:17
restart: always
- command: --character-set-server=utf8 --collation-server=utf8_general_ci
ports:
- - "127.0.0.1:3306:3306"
+ - "127.0.0.1:5432:5432"
environment:
- MARIADB_ROOT_PASSWORD: ${CUSTOS_DB_ROOT_PASSWORD:-root}
- MARIADB_USER: admin
- MARIADB_PASSWORD: ${CUSTOS_DB_PASSWORD:-admin}
- MAX_ALLOWED_PACKET: 1073741824
+ POSTGRES_USER: admin
+ POSTGRES_PASSWORD: ${CUSTOS_DB_PASSWORD:-admin}
+ POSTGRES_DB: custos
+ TZ: UTC
volumes:
- ./dbinit:/docker-entrypoint-initdb.d
healthcheck:
- test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
+ test: ["CMD-SHELL", "pg_isready -U admin -d custos"]
interval: 5s
timeout: 5s
retries: 20
@@ -47,8 +46,8 @@ services:
environment:
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: admin
- KC_DB: mariadb
- KC_DB_URL: jdbc:mariadb://db:3306/keycloak
+ KC_DB: postgres
+ KC_DB_URL: jdbc:postgresql://db:5432/keycloak
KC_DB_USERNAME: admin
KC_DB_PASSWORD: ${CUSTOS_DB_PASSWORD:-admin}
KC_HOSTNAME_URL: http://localhost:8081
diff --git a/dev-ops/compose/grafana/dashboards/amie-service.json b/dev-ops/compose/grafana/dashboards/amie-service.json
index aef134d30..505b16167 100644
--- a/dev-ops/compose/grafana/dashboards/amie-service.json
+++ b/dev-ops/compose/grafana/dashboards/amie-service.json
@@ -16,7 +16,7 @@
}],
"fieldConfig": { "defaults": { "thresholds": { "steps": [{ "color": "blue", "value": null }] } } },
"options": { "colorMode": "background", "textMode": "value_and_name" },
- "datasource": { "type": "mysql", "uid": "mariadb-amie" }
+ "datasource": { "type": "postgres", "uid": "postgres-amie" }
},
{
"type": "stat",
@@ -29,7 +29,7 @@
}],
"fieldConfig": { "defaults": { "thresholds": { "steps": [{ "color": "green", "value": null }] } } },
"options": { "colorMode": "background", "textMode": "value_and_name" },
- "datasource": { "type": "mysql", "uid": "mariadb-amie" }
+ "datasource": { "type": "postgres", "uid": "postgres-amie" }
},
{
"type": "stat",
@@ -42,7 +42,7 @@
}],
"fieldConfig": { "defaults": { "thresholds": { "steps": [{ "color": "green", "value": null }, { "color": "red", "value": 1 }] } } },
"options": { "colorMode": "background", "textMode": "value_and_name" },
- "datasource": { "type": "mysql", "uid": "mariadb-amie" }
+ "datasource": { "type": "postgres", "uid": "postgres-amie" }
},
{
"type": "stat",
@@ -55,7 +55,7 @@
}],
"fieldConfig": { "defaults": { "thresholds": { "steps": [{ "color": "green", "value": null }, { "color": "orange", "value": 1 }, { "color": "red", "value": 5 }] } } },
"options": { "colorMode": "background", "textMode": "value_and_name" },
- "datasource": { "type": "mysql", "uid": "mariadb-amie" }
+ "datasource": { "type": "postgres", "uid": "postgres-amie" }
},
{
"type": "timeseries",
@@ -68,7 +68,7 @@
}],
"fieldConfig": { "defaults": { "custom": { "drawStyle": "line", "lineWidth": 2, "fillOpacity": 15, "pointSize": 5, "showPoints": "auto" } } },
"options": { "tooltip": { "mode": "multi" }, "legend": { "displayMode": "table", "placement": "bottom", "calcs": ["sum"] } },
- "datasource": { "type": "mysql", "uid": "mariadb-amie" }
+ "datasource": { "type": "postgres", "uid": "postgres-amie" }
},
{
"type": "timeseries",
@@ -88,7 +88,7 @@
],
"fieldConfig": { "defaults": { "custom": { "drawStyle": "line", "lineWidth": 2, "fillOpacity": 15 }, "color": { "mode": "palette-classic" } } },
"options": { "tooltip": { "mode": "multi" }, "legend": { "displayMode": "table", "placement": "bottom", "calcs": ["sum"] } },
- "datasource": { "type": "mysql", "uid": "mariadb-amie" }
+ "datasource": { "type": "postgres", "uid": "postgres-amie" }
},
{
"type": "piechart",
@@ -103,7 +103,7 @@
{ "id": "rowsToFields", "options": {} }
],
"options": { "legend": { "displayMode": "table", "placement": "right", "values": ["value", "percent"] }, "pieType": "donut" },
- "datasource": { "type": "mysql", "uid": "mariadb-amie" }
+ "datasource": { "type": "postgres", "uid": "postgres-amie" }
},
{
"type": "piechart",
@@ -123,7 +123,7 @@
{ "matcher": { "id": "byName", "options": "retry_scheduled" }, "properties": [{ "id": "color", "value": { "fixedColor": "orange", "mode": "fixed" } }] }
] },
"options": { "legend": { "displayMode": "table", "placement": "right", "values": ["value", "percent"] }, "pieType": "donut" },
- "datasource": { "type": "mysql", "uid": "mariadb-amie" }
+ "datasource": { "type": "postgres", "uid": "postgres-amie" }
},
{
"type": "bargauge",
@@ -139,7 +139,7 @@
],
"fieldConfig": { "defaults": { "thresholds": { "steps": [{ "color": "blue", "value": null }] } } },
"options": { "displayMode": "gradient", "orientation": "horizontal", "showUnfilled": true },
- "datasource": { "type": "mysql", "uid": "mariadb-amie" }
+ "datasource": { "type": "postgres", "uid": "postgres-amie" }
},
{
"type": "timeseries",
@@ -179,7 +179,7 @@
"fieldConfig": { "overrides": [
{ "matcher": { "id": "byName", "options": "Outcome" }, "properties": [{ "id": "custom.cellOptions", "value": { "type": "color-text" } }, { "id": "mappings", "value": [{ "type": "value", "options": { "succeeded": { "color": "green", "text": "Succeeded" }, "permanently_failed": { "color": "red", "text": "Failed" }, "retry_scheduled": { "color": "orange", "text": "Retried" } } }] }] }
] },
- "datasource": { "type": "mysql", "uid": "mariadb-amie" }
+ "datasource": { "type": "postgres", "uid": "postgres-amie" }
}
],
"refresh": "10s",
diff --git a/dev-ops/compose/grafana/provisioning/datasources/mariadb.yml b/dev-ops/compose/grafana/provisioning/datasources/postgres.yml
similarity index 90%
rename from dev-ops/compose/grafana/provisioning/datasources/mariadb.yml
rename to dev-ops/compose/grafana/provisioning/datasources/postgres.yml
index abce4b30a..4be0a7017 100644
--- a/dev-ops/compose/grafana/provisioning/datasources/mariadb.yml
+++ b/dev-ops/compose/grafana/provisioning/datasources/postgres.yml
@@ -18,16 +18,17 @@
apiVersion: 1
datasources:
- - name: MariaDB-AMIE
- uid: mariadb-amie
- type: mysql
+ - name: Postgres-AMIE
+ uid: postgres-amie
+ type: postgres
access: proxy
- url: custos_db:3306
+ url: custos_db:5432
database: access_ci
user: admin
secureJsonData:
password: admin
jsonData:
+ sslmode: disable
maxOpenConns: 5
maxIdleConns: 2
connMaxLifetime: 14400
diff --git a/dev-ops/compose/seeds/default_cluster.sql b/dev-ops/compose/seeds/default_cluster.sql
index 598f24209..72c0435b9 100644
--- a/dev-ops/compose/seeds/default_cluster.sql
+++ b/dev-ops/compose/seeds/default_cluster.sql
@@ -21,9 +21,12 @@
-- reference. NOT applied automatically. Apply after core has run its
-- migrations (which create the compute_clusters table):
--
--- docker exec -i custos_db mariadb -u admin -padmin custos \
+-- docker exec -i custos_db psql -U admin -d custos \
-- < dev-ops/compose/seeds/default_cluster.sql
--
-INSERT IGNORE INTO compute_clusters (id, name)
-VALUES ('00000000-0000-0000-0000-000000000001', 'default-cluster');
+SET TIME ZONE 'UTC';
+
+INSERT INTO compute_clusters (id, name)
+VALUES ('00000000-0000-0000-0000-000000000001', 'default-cluster')
+ON CONFLICT DO NOTHING;
diff --git a/dev-ops/compose/seeds/dev_portal_data.sql b/dev-ops/compose/seeds/dev_portal_data.sql
index 3fb4b6fb4..c7391344e 100644
--- a/dev-ops/compose/seeds/dev_portal_data.sql
+++ b/dev-ops/compose/seeds/dev_portal_data.sql
@@ -8,26 +8,23 @@
-- AFTER the AMIE baseline scenario has run so the referenced cluster +
-- allocation UUIDs exist. Safe to re-apply (idempotent).
-SET NAMES utf8mb4;
-SET
-time_zone = '+00:00';
-- ---------------------------------------------------------------------------
-- Resources (cluster-scoped resource catalog)
-- ---------------------------------------------------------------------------
+SET TIME ZONE 'UTC';
+
INSERT INTO compute_allocation_resources (id, name, resource_type, resource_amount, compute_cluster_id)
SELECT 'res-cpu', 'CPU Service Units', 'CPU_HOURS', 1000000, id
FROM compute_clusters
-WHERE id = '00000000-0000-0000-0000-000000000001' ON DUPLICATE KEY
-UPDATE name =
-VALUES (name);
+WHERE id = '00000000-0000-0000-0000-000000000001'
+ON CONFLICT DO NOTHING;
INSERT INTO compute_allocation_resources (id, name, resource_type, resource_amount, compute_cluster_id)
SELECT 'res-gpu', 'GPU Service Units', 'GPU_HOURS', 100000, id
FROM compute_clusters
-WHERE id = '00000000-0000-0000-0000-000000000001' ON DUPLICATE KEY
-UPDATE name =
-VALUES (name);
+WHERE id = '00000000-0000-0000-0000-000000000001'
+ON CONFLICT DO NOTHING;
-- ---------------------------------------------------------------------------
-- Resource → Allocation mappings (one CPU mapping per baseline allocation,
@@ -37,9 +34,8 @@ INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, com
resource_amount, resource_time)
SELECT CONCAT('arm-cpu-', SUBSTRING(ca.id, 1, 8)), ca.id, 'res-cpu', 10000, 3600
FROM compute_allocations ca
-WHERE ca.compute_cluster_id = '00000000-0000-0000-0000-000000000001' ON DUPLICATE KEY
-UPDATE resource_amount =
-VALUES (resource_amount);
+WHERE ca.compute_cluster_id = '00000000-0000-0000-0000-000000000001'
+ON CONFLICT DO NOTHING;
INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id,
resource_amount, resource_time)
@@ -47,18 +43,16 @@ SELECT CONCAT('arm-gpu-', SUBSTRING(ca.id, 1, 8)), ca.id, 'res-gpu', 200, 3600
FROM compute_allocations ca
JOIN projects p ON p.id = ca.project_id
WHERE p.title = 'Baseline Project 1'
- OR p.originated_id = 'BL-001' ON DUPLICATE KEY
-UPDATE resource_amount =
-VALUES (resource_amount);
+ OR p.originated_id = 'BL-001'
+ON CONFLICT DO NOTHING;
-- ---------------------------------------------------------------------------
-- Resource rates
-- ---------------------------------------------------------------------------
INSERT INTO compute_allocation_resource_rates (id, compute_allocation_resource_id, rate, start_time, end_time)
VALUES ('rate-cpu', 'res-cpu', 1.0, '2026-01-01 00:00:00', '2026-12-31 23:59:59'),
- ('rate-gpu', 'res-gpu', 10.0, '2026-01-01 00:00:00', '2026-12-31 23:59:59') ON DUPLICATE KEY
-UPDATE rate =
-VALUES (rate);
+ ('rate-gpu', 'res-gpu', 10.0, '2026-01-01 00:00:00', '2026-12-31 23:59:59')
+ON CONFLICT DO NOTHING;
-- ---------------------------------------------------------------------------
-- Usage record (one sample job per baseline allocation)
@@ -77,9 +71,8 @@ SELECT CONCAT('usage-', SUBSTRING(ca.id, 1, 8)),
FROM compute_allocations ca
JOIN compute_cluster_users ccu ON ccu.compute_cluster_id = ca.compute_cluster_id
JOIN users u ON u.id = ccu.user_id
-WHERE ca.compute_cluster_id = '00000000-0000-0000-0000-000000000001' ON DUPLICATE KEY
-UPDATE used_raw_amount =
-VALUES (used_raw_amount);
+WHERE ca.compute_cluster_id = '00000000-0000-0000-0000-000000000001'
+ON CONFLICT DO NOTHING;
-- ---------------------------------------------------------------------------
-- Change request + event (one PENDING request on the supplemented allocation)
@@ -100,17 +93,14 @@ FROM compute_allocations ca
JOIN projects p ON p.id = ca.project_id
WHERE p.title = 'Baseline Project 1'
OR p.originated_id = 'BL-001' LIMIT 1
-ON DUPLICATE KEY
-UPDATE reason =
-VALUES (reason);
+ON CONFLICT DO NOTHING;
INSERT INTO compute_allocation_change_request_events
(id, compute_allocation_change_request_id, event_type, description, timestamp)
VALUES ('chreq-bl001-e1', 'chreq-bl001', 'CREATED', 'Change request submitted by researcher.', '2026-06-12 09:00:00'),
('chreq-bl001-e2', 'chreq-bl001', 'COMMENTED', 'PI reviewed and queued for approval.',
- '2026-06-13 11:30:00') ON DUPLICATE KEY
-UPDATE description =
-VALUES (description);
+ '2026-06-13 11:30:00')
+ON CONFLICT DO NOTHING;
-- ---------------------------------------------------------------------------
-- Membership override (one example: cap a member's GPU resource amount)
@@ -123,6 +113,4 @@ FROM compute_allocation_memberships m
JOIN projects p ON p.id = ca.project_id
WHERE p.title = 'Baseline Project 1'
OR p.originated_id = 'BL-001' LIMIT 1
-ON DUPLICATE KEY
-UPDATE override_resource_amount =
-VALUES (override_resource_amount);
+ON CONFLICT DO NOTHING;
diff --git a/dev-ops/compose/seeds/dev_users_and_roles.sql b/dev-ops/compose/seeds/dev_users_and_roles.sql
index fa92393fb..f75563417 100644
--- a/dev-ops/compose/seeds/dev_users_and_roles.sql
+++ b/dev-ops/compose/seeds/dev_users_and_roles.sql
@@ -20,7 +20,7 @@
-- have run; safe to re-apply (everything is idempotent).
--
-- Apply with:
--- docker exec -i custos_db mariadb -uadmin -padmin custos \
+-- docker exec -i custos_db psql -U admin -d custos \
-- < dev-ops/compose/seeds/dev_users_and_roles.sql
--
-- The bootstrap super_admin role is NOT created here — set
@@ -28,20 +28,21 @@
-- before starting the server and it will be created idempotently on boot
-- and granted to the user inserted below.
-SET NAMES utf8mb4;
-SET time_zone = '+00:00';
-- ---------------------------------------------------------------------------
-- Organization
-- ---------------------------------------------------------------------------
-INSERT IGNORE INTO organizations (id, originated_id, name)
-VALUES ('dev-org', 'DEV-ORG', 'Custos Dev Org');
+SET TIME ZONE 'UTC';
+
+INSERT INTO organizations (id, originated_id, name)
+VALUES ('dev-org', 'DEV-ORG', 'Custos Dev Org')
+ON CONFLICT DO NOTHING;
-- ---------------------------------------------------------------------------
-- Users (deterministic IDs so the portal dev-credentials provider can map each
-- dev level to a known backend user; OIDC mode resolves sub via user_identities)
-- ---------------------------------------------------------------------------
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status)
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status)
VALUES
-- site staff
('dev-admin', 'dev-org', 'Admin', 'Dev', '', 'admin@custos.local', 'ACTIVE'),
@@ -49,7 +50,8 @@ VALUES
('dev-auditor', 'dev-org', 'Auditor', 'Dev', '', 'auditor@custos.local', 'ACTIVE'),
-- researchers
('dev-pi', 'dev-org', 'PI', 'Dev', '', 'pi@custos.local', 'ACTIVE'),
- ('dev-researcher', 'dev-org', 'Researcher', 'Dev', '', 'researcher@custos.local', 'ACTIVE');
+ ('dev-researcher', 'dev-org', 'Researcher', 'Dev', '', 'researcher@custos.local', 'ACTIVE')
+ON CONFLICT DO NOTHING;
-- ---------------------------------------------------------------------------
-- Roles (deterministic UUIDs so API examples stay stable across re-applies)
@@ -57,14 +59,15 @@ VALUES
-- operator: AMIE + cluster read/write. Day-to-day operations of cluster + AMIE flows.
-- pi: Manages own projects/allocations + reads AMIE status.
-- auditor: Read-only across AMIE and cluster surfaces.
-INSERT IGNORE INTO roles (id, name, description, is_system)
+INSERT INTO roles (id, name, description, is_system)
VALUES
- ('11111111-1111-1111-1111-111111111111', 'operator', 'Day-to-day AMIE and cluster operations (read + write)', 0),
- ('22222222-2222-2222-2222-222222222222', 'auditor', 'Read-only access across AMIE and cluster surfaces', 0),
- ('33333333-3333-3333-3333-333333333333', 'pi', 'Principal Investigator - manages own projects and allocations', 0);
+ ('11111111-1111-1111-1111-111111111111', 'operator', 'Day-to-day AMIE and cluster operations (read + write)', FALSE),
+ ('22222222-2222-2222-2222-222222222222', 'auditor', 'Read-only access across AMIE and cluster surfaces', FALSE),
+ ('33333333-3333-3333-3333-333333333333', 'pi', 'Principal Investigator - manages own projects and allocations', FALSE)
+ON CONFLICT DO NOTHING;
-- operator privileges
-INSERT IGNORE INTO role_privileges (role_id, privilege) VALUES
+INSERT INTO role_privileges (role_id, privilege) VALUES
('11111111-1111-1111-1111-111111111111', 'amie:packets:read'),
('11111111-1111-1111-1111-111111111111', 'amie:packets:write'),
('11111111-1111-1111-1111-111111111111', 'amie:replies:read'),
@@ -80,10 +83,11 @@ INSERT IGNORE INTO role_privileges (role_id, privilege) VALUES
('11111111-1111-1111-1111-111111111111', 'core:users:read'),
('11111111-1111-1111-1111-111111111111', 'core:users:write'),
('11111111-1111-1111-1111-111111111111', 'core:organizations:read'),
- ('11111111-1111-1111-1111-111111111111', 'core:traces:read');
+ ('11111111-1111-1111-1111-111111111111', 'core:traces:read')
+ON CONFLICT DO NOTHING;
-- auditor privileges
-INSERT IGNORE INTO role_privileges (role_id, privilege) VALUES
+INSERT INTO role_privileges (role_id, privilege) VALUES
('22222222-2222-2222-2222-222222222222', 'amie:packets:read'),
('22222222-2222-2222-2222-222222222222', 'amie:replies:read'),
('22222222-2222-2222-2222-222222222222', 'amie:unmapped:read'),
@@ -92,28 +96,31 @@ INSERT IGNORE INTO role_privileges (role_id, privilege) VALUES
('22222222-2222-2222-2222-222222222222', 'core:projects:read'),
('22222222-2222-2222-2222-222222222222', 'core:users:read'),
('22222222-2222-2222-2222-222222222222', 'core:organizations:read'),
- ('22222222-2222-2222-2222-222222222222', 'core:traces:read');
+ ('22222222-2222-2222-2222-222222222222', 'core:traces:read')
+ON CONFLICT DO NOTHING;
-- pi privileges
-INSERT IGNORE INTO role_privileges (role_id, privilege) VALUES
+INSERT INTO role_privileges (role_id, privilege) VALUES
('33333333-3333-3333-3333-333333333333', 'amie:packets:read'),
('33333333-3333-3333-3333-333333333333', 'amie:replies:read'),
('33333333-3333-3333-3333-333333333333', 'core:clusters:read'),
('33333333-3333-3333-3333-333333333333', 'core:clusters:write'),
('33333333-3333-3333-3333-333333333333', 'core:allocations:read'),
('33333333-3333-3333-3333-333333333333', 'core:allocations:write'),
- ('33333333-3333-3333-3333-333333333333', 'core:projects:read');
+ ('33333333-3333-3333-3333-333333333333', 'core:projects:read')
+ON CONFLICT DO NOTHING;
-- ---------------------------------------------------------------------------
-- Role assignments
-- ---------------------------------------------------------------------------
-INSERT IGNORE INTO user_roles (user_id, role_id, granted_by, reason)
+INSERT INTO user_roles (user_id, role_id, granted_by, reason)
VALUES
-- site staff
('dev-operator', '11111111-1111-1111-1111-111111111111', 'dev-admin', 'dev seed'),
('dev-auditor', '22222222-2222-2222-2222-222222222222', 'dev-admin', 'dev seed'),
-- researchers
- ('dev-pi', '33333333-3333-3333-3333-333333333333', 'dev-admin', 'dev seed');
+ ('dev-pi', '33333333-3333-3333-3333-333333333333', 'dev-admin', 'dev seed')
+ON CONFLICT DO NOTHING;
-- dev-researcher holds no roles - used to exercise 403 paths from a
-- low-privilege caller.
@@ -123,7 +130,7 @@ VALUES
-- dev-ops/compose/keycloak/import/custos-realm.json so a Keycloak sign-in
-- resolves to the right backend user via identity_resolver.
-- ---------------------------------------------------------------------------
-INSERT IGNORE INTO user_identities (id, user_id, source, external_id, email, oidc_sub)
+INSERT INTO user_identities (id, user_id, source, external_id, email, oidc_sub)
VALUES
-- site staff
('ui-dev-admin', 'dev-admin', 'keycloak', 'a8f3c52d-7b1e-4d9a-8c3f-2e5b9d8a1c47', 'admin@custos.local', 'a8f3c52d-7b1e-4d9a-8c3f-2e5b9d8a1c47'),
@@ -131,4 +138,5 @@ VALUES
('ui-dev-auditor', 'dev-auditor', 'keycloak', 'c4d9e3f7-8b2a-4f5d-9e1c-3a8b7d6e2f14', 'auditor@custos.local', 'c4d9e3f7-8b2a-4f5d-9e1c-3a8b7d6e2f14'),
-- researchers
('ui-dev-pi', 'dev-pi', 'keycloak', 'e7b4c1a2-6d8f-4e9b-8a5c-7d3f9e2b1c08', 'pi@custos.local', 'e7b4c1a2-6d8f-4e9b-8a5c-7d3f9e2b1c08'),
- ('ui-dev-researcher', 'dev-researcher', 'keycloak', 'd5a8b2e6-4f9c-4d1a-9b3e-9f2c6e1b8d54', 'researcher@custos.local', 'd5a8b2e6-4f9c-4d1a-9b3e-9f2c6e1b8d54');
+ ('ui-dev-researcher', 'dev-researcher', 'keycloak', 'd5a8b2e6-4f9c-4d1a-9b3e-9f2c6e1b8d54', 'researcher@custos.local', 'd5a8b2e6-4f9c-4d1a-9b3e-9f2c6e1b8d54')
+ON CONFLICT DO NOTHING;
diff --git a/dev-ops/compose/seeds/sample_projects.sql b/dev-ops/compose/seeds/sample_projects.sql
index 2022609eb..e19776546 100644
--- a/dev-ops/compose/seeds/sample_projects.sql
+++ b/dev-ops/compose/seeds/sample_projects.sql
@@ -6,85 +6,117 @@
-- OPTIONAL sample data: ~20 research projects with allocations, members,
-- usage history, and change requests, for portal demos and manual testing.
-- Not required for a working deployment. Apply after migrations and
--- default_cluster.sql. Idempotent via INSERT IGNORE + deterministic IDs.
+-- default_cluster.sql. Idempotent via ON CONFLICT DO NOTHING + deterministic IDs.
-USE custos;
-INSERT IGNORE INTO organizations (id, originated_id, name) VALUES
- ('sample-org', 'sample-org', 'Sample Research Consortium');
+SET TIME ZONE 'UTC';
-INSERT IGNORE INTO compute_allocation_resources (id, name, resource_type, resource_amount, compute_cluster_id) VALUES
+INSERT INTO organizations (id, originated_id, name) VALUES
+ ('sample-org', 'sample-org', 'Sample Research Consortium')
+ON CONFLICT DO NOTHING;
+
+INSERT INTO compute_allocation_resources (id, name, resource_type, resource_amount, compute_cluster_id) VALUES
('res-cpu', 'CPU Service Units', 'CPU_HOURS', 1000000, '00000000-0000-0000-0000-000000000001'),
- ('res-gpu', 'GPU Service Units', 'GPU_HOURS', 100000, '00000000-0000-0000-0000-000000000001');
+ ('res-gpu', 'GPU Service Units', 'GPU_HOURS', 100000, '00000000-0000-0000-0000-000000000001')
+ON CONFLICT DO NOTHING;
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-344b252c9dd481944db0', 'sample-org', 'Elena', 'Vasquez', '', 'elena.vasquez@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-c8e6b563ac8f348d6069', 'sample-org', 'Marcus', 'Chen', '', 'marcus.chen@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-2fd62ff24cea6be8de4c', 'sample-org', 'Priya', 'Raghavan', '', 'priya.raghavan@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-bc08d052551918510023', 'sample-org', 'Tomás', 'Silva', '', 'tomas.silva@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-b800fa9f20c414f38e44', 'sample-org', 'Aisha', 'Okafor', '', 'aisha.okafor@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-3cd6d104b01ef129b379', 'sample-org', 'David', 'Kim', '', 'david.kim@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-90f9c3a872e74ceb9b54', 'sample-org', 'Sarah', 'Lindqvist', '', 'sarah.lindqvist@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-38b35e0e4b22d0d6460a', 'sample-org', 'Rajesh', 'Patel', '', 'rajesh.patel@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-26420ea0545ca72a32b3', 'sample-org', 'Mei', 'Zhang', '', 'mei.zhang@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-3b164a9395896373810e', 'sample-org', 'James', 'O''Brien', '', 'james.obrien@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-626aede3f65aeb969c74', 'sample-org', 'Fatima', 'Hassan', '', 'fatima.hassan@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-4907af7ab30bd84e6deb', 'sample-org', 'Lukas', 'Weber', '', 'lukas.weber@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-ae4c14e3cd98f58c2363', 'sample-org', 'Ana', 'Ferreira', '', 'ana.ferreira@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-343175c9b7007d1ff0f8', 'sample-org', 'Hiro', 'Tanaka', '', 'hiro.tanaka@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-f908bd32e57fd5c2896d', 'sample-org', 'Nadia', 'Petrova', '', 'nadia.petrova@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-f6343c23c6a7028f8ed7', 'sample-org', 'Carlos', 'Mendoza', '', 'carlos.mendoza@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-fa5b4cdf77ce78bd27e0', 'sample-org', 'Ingrid', 'Johansson', '', 'ingrid.johansson@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-598aba3fd25a5b74a2cf', 'sample-org', 'Kwame', 'Asante', '', 'kwame.asante@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-35b28b0dfed3e7dfcd36', 'sample-org', 'Leila', 'Nasser', '', 'leila.nasser@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-02b1cd6cafbd27dd7201', 'sample-org', 'Viktor', 'Novak', '', 'viktor.novak@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-b8e82843b10d1a15b995', 'sample-org', 'Grace', 'Adeyemi', '', 'grace.adeyemi@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-318427dda738e0504620', 'sample-org', 'Sven', 'Eriksson', '', 'sven.eriksson@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-52fae2d4d9ff23760057', 'sample-org', 'Rosa', 'Delgado', '', 'rosa.delgado@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
- ('smp-8d36f8ec62cc1ab62fd5', 'sample-org', 'Arjun', 'Menon', '', 'arjun.menon@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-344b252c9dd481944db0', 'sample-org', 'Elena', 'Vasquez', '', 'elena.vasquez@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-c8e6b563ac8f348d6069', 'sample-org', 'Marcus', 'Chen', '', 'marcus.chen@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-2fd62ff24cea6be8de4c', 'sample-org', 'Priya', 'Raghavan', '', 'priya.raghavan@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-bc08d052551918510023', 'sample-org', 'Tomás', 'Silva', '', 'tomas.silva@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-b800fa9f20c414f38e44', 'sample-org', 'Aisha', 'Okafor', '', 'aisha.okafor@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-3cd6d104b01ef129b379', 'sample-org', 'David', 'Kim', '', 'david.kim@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-90f9c3a872e74ceb9b54', 'sample-org', 'Sarah', 'Lindqvist', '', 'sarah.lindqvist@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-38b35e0e4b22d0d6460a', 'sample-org', 'Rajesh', 'Patel', '', 'rajesh.patel@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-26420ea0545ca72a32b3', 'sample-org', 'Mei', 'Zhang', '', 'mei.zhang@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-3b164a9395896373810e', 'sample-org', 'James', 'O''Brien', '', 'james.obrien@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-626aede3f65aeb969c74', 'sample-org', 'Fatima', 'Hassan', '', 'fatima.hassan@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-4907af7ab30bd84e6deb', 'sample-org', 'Lukas', 'Weber', '', 'lukas.weber@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-ae4c14e3cd98f58c2363', 'sample-org', 'Ana', 'Ferreira', '', 'ana.ferreira@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-343175c9b7007d1ff0f8', 'sample-org', 'Hiro', 'Tanaka', '', 'hiro.tanaka@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-f908bd32e57fd5c2896d', 'sample-org', 'Nadia', 'Petrova', '', 'nadia.petrova@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-f6343c23c6a7028f8ed7', 'sample-org', 'Carlos', 'Mendoza', '', 'carlos.mendoza@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-fa5b4cdf77ce78bd27e0', 'sample-org', 'Ingrid', 'Johansson', '', 'ingrid.johansson@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-598aba3fd25a5b74a2cf', 'sample-org', 'Kwame', 'Asante', '', 'kwame.asante@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-35b28b0dfed3e7dfcd36', 'sample-org', 'Leila', 'Nasser', '', 'leila.nasser@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-02b1cd6cafbd27dd7201', 'sample-org', 'Viktor', 'Novak', '', 'viktor.novak@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-b8e82843b10d1a15b995', 'sample-org', 'Grace', 'Adeyemi', '', 'grace.adeyemi@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-318427dda738e0504620', 'sample-org', 'Sven', 'Eriksson', '', 'sven.eriksson@sample.example.edu', 'PENDING', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-52fae2d4d9ff23760057', 'sample-org', 'Rosa', 'Delgado', '', 'rosa.delgado@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+ ('smp-8d36f8ec62cc1ab62fd5', 'sample-org', 'Arjun', 'Menon', '', 'arjun.menon@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
-- CHM240012: Ab Initio Screening of Transition-Metal Catalysts
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-4c8ab475ba01ce29fb67', 'CHM240012', 'Ab Initio Screening of Transition-Metal Catalysts', 'access', 'smp-344b252c9dd481944db0', 'INACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-e874cc69f25dfd4b8d9b', 'smp-4c8ab475ba01ce29fb67', 'CHM240012 startup allocation', 'INACTIVE', '00000000-0000-0000-0000-000000000001', 500000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-4c8ab475ba01ce29fb67', 'CHM240012', 'Ab Initio Screening of Transition-Metal Catalysts', 'access', 'smp-344b252c9dd481944db0', 'INACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-e874cc69f25dfd4b8d9b', 'smp-4c8ab475ba01ce29fb67', 'CHM240012 startup allocation', 'INACTIVE', '00000000-0000-0000-0000-000000000001', 500000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-4c8ab475ba01ce29fb67', 'smp-344b252c9dd481944db0', 'PI'),
('smp-4c8ab475ba01ce29fb67', 'smp-3cd6d104b01ef129b379', 'CO_PI'),
- ('smp-4c8ab475ba01ce29fb67', 'smp-4907af7ab30bd84e6deb', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-4c8ab475ba01ce29fb67', 'smp-4907af7ab30bd84e6deb', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-03f67ac7ec854e58822e', 'smp-e874cc69f25dfd4b8d9b', 'smp-344b252c9dd481944db0', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
('smp-00ecfa5a2f215d673736', 'smp-e874cc69f25dfd4b8d9b', 'smp-3cd6d104b01ef129b379', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-4fe04464197b2f2fa95c', 'smp-e874cc69f25dfd4b8d9b', 'smp-2fd62ff24cea6be8de4c', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-5e607b81210b9bed2acd', 'smp-e874cc69f25dfd4b8d9b', 'smp-38b35e0e4b22d0d6460a', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-71cf0b7ab64664364795', 'smp-e874cc69f25dfd4b8d9b', 'smp-343175c9b7007d1ff0f8', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-71cf0b7ab64664364795', 'smp-e874cc69f25dfd4b8d9b', 'smp-343175c9b7007d1ff0f8', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
('smp-cb33ed122c36262d7ce3', 'smp-e874cc69f25dfd4b8d9b', 'res-cpu', 5000, 3600),
- ('smp-7baff5fb5af1b937d1c2', 'smp-e874cc69f25dfd4b8d9b', 'res-gpu', 250, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-7baff5fb5af1b937d1c2', 'smp-e874cc69f25dfd4b8d9b', 'res-gpu', 250, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-530ad0d71ddc54f4b616', 'smp-e874cc69f25dfd4b8d9b', 25122, 25122, '2026-02-15 12:00:00', 'smp-344b252c9dd481944db0', 'job-chm240012-02', 'res-cpu'),
('smp-105c2ba6727a17d131e0', 'smp-e874cc69f25dfd4b8d9b', 25244, 25244, '2026-04-15 12:00:00', 'smp-344b252c9dd481944db0', 'job-chm240012-04', 'res-cpu'),
('smp-d66812cbdfc4d5f9754a', 'smp-e874cc69f25dfd4b8d9b', 25366, 25366, '2026-06-15 12:00:00', 'smp-344b252c9dd481944db0', 'job-chm240012-06', 'res-cpu'),
@@ -93,30 +125,38 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-6826a5eafd15dcd7cbd1', 'smp-e874cc69f25dfd4b8d9b', 25503, 25503, '2026-06-15 12:00:00', 'smp-2fd62ff24cea6be8de4c', 'job-chm240012-16', 'res-cpu'),
('smp-6a4ce2db6e7755a2f0f0', 'smp-e874cc69f25dfd4b8d9b', 25396, 25396, '2026-02-15 12:00:00', 'smp-38b35e0e4b22d0d6460a', 'job-chm240012-22', 'res-cpu'),
('smp-de8cfe7a9daa94af63dd', 'smp-e874cc69f25dfd4b8d9b', 25518, 25518, '2026-04-15 12:00:00', 'smp-38b35e0e4b22d0d6460a', 'job-chm240012-24', 'res-cpu'),
- ('smp-bab3e2728afe00548e12', 'smp-e874cc69f25dfd4b8d9b', 25640, 25640, '2026-06-15 12:00:00', 'smp-38b35e0e4b22d0d6460a', 'job-chm240012-26', 'res-cpu');
-INSERT IGNORE INTO compute_allocation_change_requests (id, compute_allocation_id, requested_su_amount, requested_status, reason, change_status, requester_id, approver_id, timestamp) VALUES
- ('smp-bee39eb5dcf4d798dadd', 'smp-e874cc69f25dfd4b8d9b', 250000, 'ACTIVE', 'Supplement for CHM240012 production campaign', 'PENDING', 'smp-344b252c9dd481944db0', '', '2026-06-20 09:00:00');
-INSERT IGNORE INTO compute_allocation_change_request_events (id, compute_allocation_change_request_id, event_type, description, timestamp) VALUES
- ('smp-bee39eb5dcf4d798dadd-e1', 'smp-bee39eb5dcf4d798dadd', 'CREATED', 'Change request submitted by PI.', '2026-06-20 09:00:00');
+ ('smp-bab3e2728afe00548e12', 'smp-e874cc69f25dfd4b8d9b', 25640, 25640, '2026-06-15 12:00:00', 'smp-38b35e0e4b22d0d6460a', 'job-chm240012-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_change_requests (id, compute_allocation_id, requested_su_amount, requested_status, reason, change_status, requester_id, approver_id, timestamp) VALUES
+ ('smp-bee39eb5dcf4d798dadd', 'smp-e874cc69f25dfd4b8d9b', 250000, 'ACTIVE', 'Supplement for CHM240012 production campaign', 'PENDING', 'smp-344b252c9dd481944db0', '', '2026-06-20 09:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_change_request_events (id, compute_allocation_change_request_id, event_type, description, timestamp) VALUES
+ ('smp-bee39eb5dcf4d798dadd-e1', 'smp-bee39eb5dcf4d798dadd', 'CREATED', 'Change request submitted by PI.', '2026-06-20 09:00:00')
+ON CONFLICT DO NOTHING;
-- PHY240034: Lattice QCD at the Physical Point
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-eedc3ed54172fa1539ee', 'PHY240034', 'Lattice QCD at the Physical Point', 'access', 'smp-c8e6b563ac8f348d6069', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-d29064eb686184d12cbe', 'smp-eedc3ed54172fa1539ee', 'PHY240034 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 250000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-eedc3ed54172fa1539ee', 'PHY240034', 'Lattice QCD at the Physical Point', 'access', 'smp-c8e6b563ac8f348d6069', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-d29064eb686184d12cbe', 'smp-eedc3ed54172fa1539ee', 'PHY240034 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 250000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-eedc3ed54172fa1539ee', 'smp-c8e6b563ac8f348d6069', 'PI'),
('smp-eedc3ed54172fa1539ee', 'smp-90f9c3a872e74ceb9b54', 'CO_PI'),
- ('smp-eedc3ed54172fa1539ee', 'smp-ae4c14e3cd98f58c2363', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-eedc3ed54172fa1539ee', 'smp-ae4c14e3cd98f58c2363', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-dff1bea8f3c9fc390dc2', 'smp-d29064eb686184d12cbe', 'smp-c8e6b563ac8f348d6069', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-7e3a683d1b228d3fd397', 'smp-d29064eb686184d12cbe', 'smp-90f9c3a872e74ceb9b54', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-781a6da2739d158a9381', 'smp-d29064eb686184d12cbe', 'smp-bc08d052551918510023', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-11ef2821527195380666', 'smp-d29064eb686184d12cbe', 'smp-26420ea0545ca72a32b3', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-8f07e38746a089ac45ce', 'smp-d29064eb686184d12cbe', 'smp-f908bd32e57fd5c2896d', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
- ('smp-81ce9b9915d4330c9618', 'smp-d29064eb686184d12cbe', 'res-cpu', 2500, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-8f07e38746a089ac45ce', 'smp-d29064eb686184d12cbe', 'smp-f908bd32e57fd5c2896d', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-81ce9b9915d4330c9618', 'smp-d29064eb686184d12cbe', 'res-cpu', 2500, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-93a9297f42034c4f6c54', 'smp-d29064eb686184d12cbe', 12622, 12622, '2026-02-15 12:00:00', 'smp-c8e6b563ac8f348d6069', 'job-phy240034-02', 'res-cpu'),
('smp-182f0db7f97cb88e60ca', 'smp-d29064eb686184d12cbe', 12744, 12744, '2026-04-15 12:00:00', 'smp-c8e6b563ac8f348d6069', 'job-phy240034-04', 'res-cpu'),
('smp-de38d27ef1afee55bc46', 'smp-d29064eb686184d12cbe', 12866, 12866, '2026-06-15 12:00:00', 'smp-c8e6b563ac8f348d6069', 'job-phy240034-06', 'res-cpu'),
@@ -125,26 +165,32 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-eb632ef53e053b61be18', 'smp-d29064eb686184d12cbe', 13003, 13003, '2026-06-15 12:00:00', 'smp-bc08d052551918510023', 'job-phy240034-16', 'res-cpu'),
('smp-fe7ed21602138bb5d6bd', 'smp-d29064eb686184d12cbe', 12896, 12896, '2026-02-15 12:00:00', 'smp-26420ea0545ca72a32b3', 'job-phy240034-22', 'res-cpu'),
('smp-fcb629755d1e9143f58f', 'smp-d29064eb686184d12cbe', 13018, 13018, '2026-04-15 12:00:00', 'smp-26420ea0545ca72a32b3', 'job-phy240034-24', 'res-cpu'),
- ('smp-a528a8ce9397f2f2c14e', 'smp-d29064eb686184d12cbe', 13140, 13140, '2026-06-15 12:00:00', 'smp-26420ea0545ca72a32b3', 'job-phy240034-26', 'res-cpu');
+ ('smp-a528a8ce9397f2f2c14e', 'smp-d29064eb686184d12cbe', 13140, 13140, '2026-06-15 12:00:00', 'smp-26420ea0545ca72a32b3', 'job-phy240034-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- BIO240041: Cryo-EM Reconstruction of Membrane Transporters
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-9a75381c02101891703d', 'BIO240041', 'Cryo-EM Reconstruction of Membrane Transporters', 'access', 'smp-2fd62ff24cea6be8de4c', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-4d0e44e3216d12f4d479', 'smp-9a75381c02101891703d', 'BIO240041 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 1200000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-9a75381c02101891703d', 'BIO240041', 'Cryo-EM Reconstruction of Membrane Transporters', 'access', 'smp-2fd62ff24cea6be8de4c', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-4d0e44e3216d12f4d479', 'smp-9a75381c02101891703d', 'BIO240041 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 1200000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-9a75381c02101891703d', 'smp-2fd62ff24cea6be8de4c', 'PI'),
('smp-9a75381c02101891703d', 'smp-38b35e0e4b22d0d6460a', 'CO_PI'),
- ('smp-9a75381c02101891703d', 'smp-343175c9b7007d1ff0f8', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-9a75381c02101891703d', 'smp-343175c9b7007d1ff0f8', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-a3ab9b4d9c40788c1903', 'smp-4d0e44e3216d12f4d479', 'smp-2fd62ff24cea6be8de4c', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-3267f51d3012259d259a', 'smp-4d0e44e3216d12f4d479', 'smp-38b35e0e4b22d0d6460a', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-629ec6cc62ffb28c951a', 'smp-4d0e44e3216d12f4d479', 'smp-b800fa9f20c414f38e44', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-df43769412be97bff936', 'smp-4d0e44e3216d12f4d479', 'smp-3b164a9395896373810e', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
- ('smp-ceeef3865b4d552302af', 'smp-4d0e44e3216d12f4d479', 'smp-f6343c23c6a7028f8ed7', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
- ('smp-3adf9e4196cfe653ed10', 'smp-4d0e44e3216d12f4d479', 'res-cpu', 12000, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-ceeef3865b4d552302af', 'smp-4d0e44e3216d12f4d479', 'smp-f6343c23c6a7028f8ed7', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-3adf9e4196cfe653ed10', 'smp-4d0e44e3216d12f4d479', 'res-cpu', 12000, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-f9b40c25d80a709c32e4', 'smp-4d0e44e3216d12f4d479', 60122, 60122, '2026-02-15 12:00:00', 'smp-2fd62ff24cea6be8de4c', 'job-bio240041-02', 'res-cpu'),
('smp-ee6c9eb02449247cee7e', 'smp-4d0e44e3216d12f4d479', 60244, 60244, '2026-04-15 12:00:00', 'smp-2fd62ff24cea6be8de4c', 'job-bio240041-04', 'res-cpu'),
('smp-a8db1b3ea4e59139a759', 'smp-4d0e44e3216d12f4d479', 60366, 60366, '2026-06-15 12:00:00', 'smp-2fd62ff24cea6be8de4c', 'job-bio240041-06', 'res-cpu'),
@@ -153,27 +199,33 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-6dc600c77b58f77802c9', 'smp-4d0e44e3216d12f4d479', 60503, 60503, '2026-06-15 12:00:00', 'smp-b800fa9f20c414f38e44', 'job-bio240041-16', 'res-cpu'),
('smp-f932d2e828db398abb2c', 'smp-4d0e44e3216d12f4d479', 60396, 60396, '2026-02-15 12:00:00', 'smp-3b164a9395896373810e', 'job-bio240041-22', 'res-cpu'),
('smp-dd39cc232b646d084e0b', 'smp-4d0e44e3216d12f4d479', 60518, 60518, '2026-04-15 12:00:00', 'smp-3b164a9395896373810e', 'job-bio240041-24', 'res-cpu'),
- ('smp-c822b6cfa2e56941eaaf', 'smp-4d0e44e3216d12f4d479', 60640, 60640, '2026-06-15 12:00:00', 'smp-3b164a9395896373810e', 'job-bio240041-26', 'res-cpu');
+ ('smp-c822b6cfa2e56941eaaf', 'smp-4d0e44e3216d12f4d479', 60640, 60640, '2026-06-15 12:00:00', 'smp-3b164a9395896373810e', 'job-bio240041-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- ATM240007: Convection-Permitting Regional Climate Downscaling
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-739a563efab75e5ab41a', 'ATM240007', 'Convection-Permitting Regional Climate Downscaling', 'access', 'smp-bc08d052551918510023', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-2551d58902d6316834f7', 'smp-739a563efab75e5ab41a', 'ATM240007 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 100000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-739a563efab75e5ab41a', 'ATM240007', 'Convection-Permitting Regional Climate Downscaling', 'access', 'smp-bc08d052551918510023', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-2551d58902d6316834f7', 'smp-739a563efab75e5ab41a', 'ATM240007 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 100000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-739a563efab75e5ab41a', 'smp-bc08d052551918510023', 'PI'),
('smp-739a563efab75e5ab41a', 'smp-26420ea0545ca72a32b3', 'CO_PI'),
- ('smp-739a563efab75e5ab41a', 'smp-f908bd32e57fd5c2896d', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-739a563efab75e5ab41a', 'smp-f908bd32e57fd5c2896d', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-f646fe054930300ae250', 'smp-2551d58902d6316834f7', 'smp-bc08d052551918510023', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-83a967a84b8f97fcc7f8', 'smp-2551d58902d6316834f7', 'smp-26420ea0545ca72a32b3', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-f6b5af8ead74db587789', 'smp-2551d58902d6316834f7', 'smp-3cd6d104b01ef129b379', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
('smp-8605e7b86b9d576f483b', 'smp-2551d58902d6316834f7', 'smp-626aede3f65aeb969c74', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-fd74a21ed701b3fde48c', 'smp-2551d58902d6316834f7', 'smp-fa5b4cdf77ce78bd27e0', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-fd74a21ed701b3fde48c', 'smp-2551d58902d6316834f7', 'smp-fa5b4cdf77ce78bd27e0', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
('smp-cd993b04e4f2c02b88f8', 'smp-2551d58902d6316834f7', 'res-cpu', 1000, 3600),
- ('smp-32db965752251c364028', 'smp-2551d58902d6316834f7', 'res-gpu', 50, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-32db965752251c364028', 'smp-2551d58902d6316834f7', 'res-gpu', 50, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-5c988efc4a580a59b89d', 'smp-2551d58902d6316834f7', 5122, 5122, '2026-02-15 12:00:00', 'smp-bc08d052551918510023', 'job-atm240007-02', 'res-cpu'),
('smp-acb54b7dbe6529188a0c', 'smp-2551d58902d6316834f7', 5244, 5244, '2026-04-15 12:00:00', 'smp-bc08d052551918510023', 'job-atm240007-04', 'res-cpu'),
('smp-84b534468be86ac704e3', 'smp-2551d58902d6316834f7', 5366, 5366, '2026-06-15 12:00:00', 'smp-bc08d052551918510023', 'job-atm240007-06', 'res-cpu'),
@@ -182,26 +234,32 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-228ad1f5a429f1b97327', 'smp-2551d58902d6316834f7', 5503, 5503, '2026-06-15 12:00:00', 'smp-3cd6d104b01ef129b379', 'job-atm240007-16', 'res-cpu'),
('smp-e1a05cb0fa2fed877d41', 'smp-2551d58902d6316834f7', 5396, 5396, '2026-02-15 12:00:00', 'smp-626aede3f65aeb969c74', 'job-atm240007-22', 'res-cpu'),
('smp-b8bd552c0e4ccee94b45', 'smp-2551d58902d6316834f7', 5518, 5518, '2026-04-15 12:00:00', 'smp-626aede3f65aeb969c74', 'job-atm240007-24', 'res-cpu'),
- ('smp-a656878e65a300e21644', 'smp-2551d58902d6316834f7', 5640, 5640, '2026-06-15 12:00:00', 'smp-626aede3f65aeb969c74', 'job-atm240007-26', 'res-cpu');
+ ('smp-a656878e65a300e21644', 'smp-2551d58902d6316834f7', 5640, 5640, '2026-06-15 12:00:00', 'smp-626aede3f65aeb969c74', 'job-atm240007-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- MAT240019: Phase-Field Modeling of Additive Manufacturing
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-5591ed41e4391631471d', 'MAT240019', 'Phase-Field Modeling of Additive Manufacturing', 'access', 'smp-b800fa9f20c414f38e44', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-e1d2a3f5f134af253407', 'smp-5591ed41e4391631471d', 'MAT240019 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 750000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-5591ed41e4391631471d', 'MAT240019', 'Phase-Field Modeling of Additive Manufacturing', 'access', 'smp-b800fa9f20c414f38e44', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-e1d2a3f5f134af253407', 'smp-5591ed41e4391631471d', 'MAT240019 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 750000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-5591ed41e4391631471d', 'smp-b800fa9f20c414f38e44', 'PI'),
('smp-5591ed41e4391631471d', 'smp-3b164a9395896373810e', 'CO_PI'),
- ('smp-5591ed41e4391631471d', 'smp-f6343c23c6a7028f8ed7', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-5591ed41e4391631471d', 'smp-f6343c23c6a7028f8ed7', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-e3e592f0cf08e6810c4a', 'smp-e1d2a3f5f134af253407', 'smp-b800fa9f20c414f38e44', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-c1175f5f185e611386bc', 'smp-e1d2a3f5f134af253407', 'smp-3b164a9395896373810e', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
('smp-6fbefdb58caf23d4ec9a', 'smp-e1d2a3f5f134af253407', 'smp-90f9c3a872e74ceb9b54', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-846394a530902c49b71f', 'smp-e1d2a3f5f134af253407', 'smp-4907af7ab30bd84e6deb', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-3d456ef04b41df4b6b21', 'smp-e1d2a3f5f134af253407', 'smp-598aba3fd25a5b74a2cf', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
- ('smp-78e31b4a2a87e668ede9', 'smp-e1d2a3f5f134af253407', 'res-cpu', 7500, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-3d456ef04b41df4b6b21', 'smp-e1d2a3f5f134af253407', 'smp-598aba3fd25a5b74a2cf', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-78e31b4a2a87e668ede9', 'smp-e1d2a3f5f134af253407', 'res-cpu', 7500, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-94012f10b2837f8e70ac', 'smp-e1d2a3f5f134af253407', 37622, 37622, '2026-02-15 12:00:00', 'smp-b800fa9f20c414f38e44', 'job-mat240019-02', 'res-cpu'),
('smp-4bf63c33ab7ad750bbcb', 'smp-e1d2a3f5f134af253407', 37744, 37744, '2026-04-15 12:00:00', 'smp-b800fa9f20c414f38e44', 'job-mat240019-04', 'res-cpu'),
('smp-1f45c13ef3fb9504c686', 'smp-e1d2a3f5f134af253407', 37866, 37866, '2026-06-15 12:00:00', 'smp-b800fa9f20c414f38e44', 'job-mat240019-06', 'res-cpu'),
@@ -210,30 +268,38 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-4c4f22d12ad43f31a1eb', 'smp-e1d2a3f5f134af253407', 38003, 38003, '2026-06-15 12:00:00', 'smp-90f9c3a872e74ceb9b54', 'job-mat240019-16', 'res-cpu'),
('smp-c0202dbaa96535442ec2', 'smp-e1d2a3f5f134af253407', 37896, 37896, '2026-02-15 12:00:00', 'smp-4907af7ab30bd84e6deb', 'job-mat240019-22', 'res-cpu'),
('smp-7ff22c79168831d62ee2', 'smp-e1d2a3f5f134af253407', 38018, 38018, '2026-04-15 12:00:00', 'smp-4907af7ab30bd84e6deb', 'job-mat240019-24', 'res-cpu'),
- ('smp-4892ec0a6d21e1cdd04c', 'smp-e1d2a3f5f134af253407', 38140, 38140, '2026-06-15 12:00:00', 'smp-4907af7ab30bd84e6deb', 'job-mat240019-26', 'res-cpu');
-INSERT IGNORE INTO compute_allocation_change_requests (id, compute_allocation_id, requested_su_amount, requested_status, reason, change_status, requester_id, approver_id, timestamp) VALUES
- ('smp-254f9b0991e8ba676b9e', 'smp-e1d2a3f5f134af253407', 375000, 'ACTIVE', 'Supplement for MAT240019 production campaign', 'APPROVED', 'smp-b800fa9f20c414f38e44', '', '2026-06-24 09:00:00');
-INSERT IGNORE INTO compute_allocation_change_request_events (id, compute_allocation_change_request_id, event_type, description, timestamp) VALUES
- ('smp-254f9b0991e8ba676b9e-e1', 'smp-254f9b0991e8ba676b9e', 'CREATED', 'Change request submitted by PI.', '2026-06-24 09:00:00');
+ ('smp-4892ec0a6d21e1cdd04c', 'smp-e1d2a3f5f134af253407', 38140, 38140, '2026-06-15 12:00:00', 'smp-4907af7ab30bd84e6deb', 'job-mat240019-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_change_requests (id, compute_allocation_id, requested_su_amount, requested_status, reason, change_status, requester_id, approver_id, timestamp) VALUES
+ ('smp-254f9b0991e8ba676b9e', 'smp-e1d2a3f5f134af253407', 375000, 'ACTIVE', 'Supplement for MAT240019 production campaign', 'APPROVED', 'smp-b800fa9f20c414f38e44', '', '2026-06-24 09:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_change_request_events (id, compute_allocation_change_request_id, event_type, description, timestamp) VALUES
+ ('smp-254f9b0991e8ba676b9e-e1', 'smp-254f9b0991e8ba676b9e', 'CREATED', 'Change request submitted by PI.', '2026-06-24 09:00:00')
+ON CONFLICT DO NOTHING;
-- AST240023: Galaxy Formation in Cosmological Zoom Simulations
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-2acc6fcc75db53616416', 'AST240023', 'Galaxy Formation in Cosmological Zoom Simulations', 'access', 'smp-3cd6d104b01ef129b379', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-603a9403849f5bf1779d', 'smp-2acc6fcc75db53616416', 'AST240023 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 300000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-2acc6fcc75db53616416', 'AST240023', 'Galaxy Formation in Cosmological Zoom Simulations', 'access', 'smp-3cd6d104b01ef129b379', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-603a9403849f5bf1779d', 'smp-2acc6fcc75db53616416', 'AST240023 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 300000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-2acc6fcc75db53616416', 'smp-3cd6d104b01ef129b379', 'PI'),
('smp-2acc6fcc75db53616416', 'smp-626aede3f65aeb969c74', 'CO_PI'),
- ('smp-2acc6fcc75db53616416', 'smp-fa5b4cdf77ce78bd27e0', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-2acc6fcc75db53616416', 'smp-fa5b4cdf77ce78bd27e0', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-2a7e4efefba9ce923ebe', 'smp-603a9403849f5bf1779d', 'smp-3cd6d104b01ef129b379', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
('smp-2caa26d94f937425d1a4', 'smp-603a9403849f5bf1779d', 'smp-626aede3f65aeb969c74', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-c41e4253e1dc415f9d0f', 'smp-603a9403849f5bf1779d', 'smp-38b35e0e4b22d0d6460a', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-4b581a2c13cf595a4883', 'smp-603a9403849f5bf1779d', 'smp-ae4c14e3cd98f58c2363', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-f065f61b096e0c240128', 'smp-603a9403849f5bf1779d', 'smp-35b28b0dfed3e7dfcd36', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
- ('smp-2a8b2229da7f623bb18a', 'smp-603a9403849f5bf1779d', 'res-cpu', 3000, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-f065f61b096e0c240128', 'smp-603a9403849f5bf1779d', 'smp-35b28b0dfed3e7dfcd36', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-2a8b2229da7f623bb18a', 'smp-603a9403849f5bf1779d', 'res-cpu', 3000, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-6119d8a1f8d04129f3f8', 'smp-603a9403849f5bf1779d', 15122, 15122, '2026-02-15 12:00:00', 'smp-3cd6d104b01ef129b379', 'job-ast240023-02', 'res-cpu'),
('smp-647181201e12d96b2b62', 'smp-603a9403849f5bf1779d', 15244, 15244, '2026-04-15 12:00:00', 'smp-3cd6d104b01ef129b379', 'job-ast240023-04', 'res-cpu'),
('smp-28df35315256eb87cb4f', 'smp-603a9403849f5bf1779d', 15366, 15366, '2026-06-15 12:00:00', 'smp-3cd6d104b01ef129b379', 'job-ast240023-06', 'res-cpu'),
@@ -242,27 +308,33 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-1cf9d1e335c97e2b8952', 'smp-603a9403849f5bf1779d', 15503, 15503, '2026-06-15 12:00:00', 'smp-38b35e0e4b22d0d6460a', 'job-ast240023-16', 'res-cpu'),
('smp-3466fea28ce65b64f3f3', 'smp-603a9403849f5bf1779d', 15396, 15396, '2026-02-15 12:00:00', 'smp-ae4c14e3cd98f58c2363', 'job-ast240023-22', 'res-cpu'),
('smp-9bfa80727d3c8049409b', 'smp-603a9403849f5bf1779d', 15518, 15518, '2026-04-15 12:00:00', 'smp-ae4c14e3cd98f58c2363', 'job-ast240023-24', 'res-cpu'),
- ('smp-70407e7274fdb84e6670', 'smp-603a9403849f5bf1779d', 15640, 15640, '2026-06-15 12:00:00', 'smp-ae4c14e3cd98f58c2363', 'job-ast240023-26', 'res-cpu');
+ ('smp-70407e7274fdb84e6670', 'smp-603a9403849f5bf1779d', 15640, 15640, '2026-06-15 12:00:00', 'smp-ae4c14e3cd98f58c2363', 'job-ast240023-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- CHE240055: Coarse-Grained Dynamics of Polymer Electrolytes
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-b7545fb17c505974fac7', 'CHE240055', 'Coarse-Grained Dynamics of Polymer Electrolytes', 'access', 'smp-90f9c3a872e74ceb9b54', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-50a930bb1fb569beb058', 'smp-b7545fb17c505974fac7', 'CHE240055 startup allocation', 'INACTIVE', '00000000-0000-0000-0000-000000000001', 500000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-b7545fb17c505974fac7', 'CHE240055', 'Coarse-Grained Dynamics of Polymer Electrolytes', 'access', 'smp-90f9c3a872e74ceb9b54', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-50a930bb1fb569beb058', 'smp-b7545fb17c505974fac7', 'CHE240055 startup allocation', 'INACTIVE', '00000000-0000-0000-0000-000000000001', 500000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-b7545fb17c505974fac7', 'smp-90f9c3a872e74ceb9b54', 'PI'),
('smp-b7545fb17c505974fac7', 'smp-4907af7ab30bd84e6deb', 'CO_PI'),
- ('smp-b7545fb17c505974fac7', 'smp-598aba3fd25a5b74a2cf', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-b7545fb17c505974fac7', 'smp-598aba3fd25a5b74a2cf', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-df1b9ec3ffc9f799f8c7', 'smp-50a930bb1fb569beb058', 'smp-90f9c3a872e74ceb9b54', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-d9b23eb43e735256ff09', 'smp-50a930bb1fb569beb058', 'smp-4907af7ab30bd84e6deb', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-0f69ddeda7cf2638c12c', 'smp-50a930bb1fb569beb058', 'smp-26420ea0545ca72a32b3', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-4e05771035c6043e8ce7', 'smp-50a930bb1fb569beb058', 'smp-343175c9b7007d1ff0f8', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-015a0efad9c581a798c3', 'smp-50a930bb1fb569beb058', 'smp-02b1cd6cafbd27dd7201', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-015a0efad9c581a798c3', 'smp-50a930bb1fb569beb058', 'smp-02b1cd6cafbd27dd7201', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
('smp-585418a12468f375fb17', 'smp-50a930bb1fb569beb058', 'res-cpu', 5000, 3600),
- ('smp-22ebd1572fb944db1fef', 'smp-50a930bb1fb569beb058', 'res-gpu', 250, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-22ebd1572fb944db1fef', 'smp-50a930bb1fb569beb058', 'res-gpu', 250, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-7303d44888b9449db6a1', 'smp-50a930bb1fb569beb058', 25122, 25122, '2026-02-15 12:00:00', 'smp-90f9c3a872e74ceb9b54', 'job-che240055-02', 'res-cpu'),
('smp-6833b38aa98debeb791c', 'smp-50a930bb1fb569beb058', 25244, 25244, '2026-04-15 12:00:00', 'smp-90f9c3a872e74ceb9b54', 'job-che240055-04', 'res-cpu'),
('smp-b53af282aaef9a521e26', 'smp-50a930bb1fb569beb058', 25366, 25366, '2026-06-15 12:00:00', 'smp-90f9c3a872e74ceb9b54', 'job-che240055-06', 'res-cpu'),
@@ -271,26 +343,32 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-65746ddfa778564f94cf', 'smp-50a930bb1fb569beb058', 25503, 25503, '2026-06-15 12:00:00', 'smp-26420ea0545ca72a32b3', 'job-che240055-16', 'res-cpu'),
('smp-3cea6aa6fef3a2a2c02c', 'smp-50a930bb1fb569beb058', 25396, 25396, '2026-02-15 12:00:00', 'smp-343175c9b7007d1ff0f8', 'job-che240055-22', 'res-cpu'),
('smp-beb62c91dc7e783233e6', 'smp-50a930bb1fb569beb058', 25518, 25518, '2026-04-15 12:00:00', 'smp-343175c9b7007d1ff0f8', 'job-che240055-24', 'res-cpu'),
- ('smp-6bdbddae9f82d1257645', 'smp-50a930bb1fb569beb058', 25640, 25640, '2026-06-15 12:00:00', 'smp-343175c9b7007d1ff0f8', 'job-che240055-26', 'res-cpu');
+ ('smp-6bdbddae9f82d1257645', 'smp-50a930bb1fb569beb058', 25640, 25640, '2026-06-15 12:00:00', 'smp-343175c9b7007d1ff0f8', 'job-che240055-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- GEO240011: Full-Waveform Inversion for Cascadia Subduction
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-3df742c724053f0bbfcc', 'GEO240011', 'Full-Waveform Inversion for Cascadia Subduction', 'access', 'smp-38b35e0e4b22d0d6460a', 'INACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-903e6a1acd2c67f92e00', 'smp-3df742c724053f0bbfcc', 'GEO240011 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 250000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-3df742c724053f0bbfcc', 'GEO240011', 'Full-Waveform Inversion for Cascadia Subduction', 'access', 'smp-38b35e0e4b22d0d6460a', 'INACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-903e6a1acd2c67f92e00', 'smp-3df742c724053f0bbfcc', 'GEO240011 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 250000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-3df742c724053f0bbfcc', 'smp-38b35e0e4b22d0d6460a', 'PI'),
('smp-3df742c724053f0bbfcc', 'smp-ae4c14e3cd98f58c2363', 'CO_PI'),
- ('smp-3df742c724053f0bbfcc', 'smp-35b28b0dfed3e7dfcd36', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-3df742c724053f0bbfcc', 'smp-35b28b0dfed3e7dfcd36', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-6824f6e16aaacea2c844', 'smp-903e6a1acd2c67f92e00', 'smp-38b35e0e4b22d0d6460a', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-7bab7f463ea43025f80e', 'smp-903e6a1acd2c67f92e00', 'smp-ae4c14e3cd98f58c2363', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-21fa0da14c6bed4666d1', 'smp-903e6a1acd2c67f92e00', 'smp-3b164a9395896373810e', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-c3110fe149f18a80ded9', 'smp-903e6a1acd2c67f92e00', 'smp-f908bd32e57fd5c2896d', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
- ('smp-d4fee66ef35b31194264', 'smp-903e6a1acd2c67f92e00', 'smp-b8e82843b10d1a15b995', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
- ('smp-b2d422b17c67c38949ba', 'smp-903e6a1acd2c67f92e00', 'res-cpu', 2500, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-d4fee66ef35b31194264', 'smp-903e6a1acd2c67f92e00', 'smp-b8e82843b10d1a15b995', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-b2d422b17c67c38949ba', 'smp-903e6a1acd2c67f92e00', 'res-cpu', 2500, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-45ad6b15a89566275d44', 'smp-903e6a1acd2c67f92e00', 12622, 12622, '2026-02-15 12:00:00', 'smp-38b35e0e4b22d0d6460a', 'job-geo240011-02', 'res-cpu'),
('smp-a0098b972e16b7584386', 'smp-903e6a1acd2c67f92e00', 12744, 12744, '2026-04-15 12:00:00', 'smp-38b35e0e4b22d0d6460a', 'job-geo240011-04', 'res-cpu'),
('smp-4b6185e17858dac52571', 'smp-903e6a1acd2c67f92e00', 12866, 12866, '2026-06-15 12:00:00', 'smp-38b35e0e4b22d0d6460a', 'job-geo240011-06', 'res-cpu'),
@@ -299,26 +377,32 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-b84ddb9faa6307a5a95e', 'smp-903e6a1acd2c67f92e00', 13003, 13003, '2026-06-15 12:00:00', 'smp-3b164a9395896373810e', 'job-geo240011-16', 'res-cpu'),
('smp-0e4ae5caa92f5e0d82a8', 'smp-903e6a1acd2c67f92e00', 12896, 12896, '2026-02-15 12:00:00', 'smp-f908bd32e57fd5c2896d', 'job-geo240011-22', 'res-cpu'),
('smp-e04a55b89c5261a53754', 'smp-903e6a1acd2c67f92e00', 13018, 13018, '2026-04-15 12:00:00', 'smp-f908bd32e57fd5c2896d', 'job-geo240011-24', 'res-cpu'),
- ('smp-d9a25ca35552f21d7be9', 'smp-903e6a1acd2c67f92e00', 13140, 13140, '2026-06-15 12:00:00', 'smp-f908bd32e57fd5c2896d', 'job-geo240011-26', 'res-cpu');
+ ('smp-d9a25ca35552f21d7be9', 'smp-903e6a1acd2c67f92e00', 13140, 13140, '2026-06-15 12:00:00', 'smp-f908bd32e57fd5c2896d', 'job-geo240011-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- CIS240068: Distributed Training of Sparse Mixture Models
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-46566060f3d3d2eb9420', 'CIS240068', 'Distributed Training of Sparse Mixture Models', 'access', 'smp-26420ea0545ca72a32b3', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-f6d592517cf3b690a68b', 'smp-46566060f3d3d2eb9420', 'CIS240068 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 1200000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-46566060f3d3d2eb9420', 'CIS240068', 'Distributed Training of Sparse Mixture Models', 'access', 'smp-26420ea0545ca72a32b3', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-f6d592517cf3b690a68b', 'smp-46566060f3d3d2eb9420', 'CIS240068 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 1200000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-46566060f3d3d2eb9420', 'smp-26420ea0545ca72a32b3', 'PI'),
('smp-46566060f3d3d2eb9420', 'smp-343175c9b7007d1ff0f8', 'CO_PI'),
- ('smp-46566060f3d3d2eb9420', 'smp-02b1cd6cafbd27dd7201', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-46566060f3d3d2eb9420', 'smp-02b1cd6cafbd27dd7201', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-64d81cddbe82cffe8c4c', 'smp-f6d592517cf3b690a68b', 'smp-26420ea0545ca72a32b3', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-7bccab59588549eca7fe', 'smp-f6d592517cf3b690a68b', 'smp-343175c9b7007d1ff0f8', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-b692ed3def842f0502b3', 'smp-f6d592517cf3b690a68b', 'smp-626aede3f65aeb969c74', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
('smp-2e22c2973dd09d97a3d2', 'smp-f6d592517cf3b690a68b', 'smp-f6343c23c6a7028f8ed7', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-7c7f4f97cb391e2c7bbc', 'smp-f6d592517cf3b690a68b', 'smp-318427dda738e0504620', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
- ('smp-2ee3008445252911c454', 'smp-f6d592517cf3b690a68b', 'res-cpu', 12000, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-7c7f4f97cb391e2c7bbc', 'smp-f6d592517cf3b690a68b', 'smp-318427dda738e0504620', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-2ee3008445252911c454', 'smp-f6d592517cf3b690a68b', 'res-cpu', 12000, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-ed290c19f19b9711e6de', 'smp-f6d592517cf3b690a68b', 60122, 60122, '2026-02-15 12:00:00', 'smp-26420ea0545ca72a32b3', 'job-cis240068-02', 'res-cpu'),
('smp-b92b66eeb33cb669dc2d', 'smp-f6d592517cf3b690a68b', 60244, 60244, '2026-04-15 12:00:00', 'smp-26420ea0545ca72a32b3', 'job-cis240068-04', 'res-cpu'),
('smp-883764679b1ad666aaa2', 'smp-f6d592517cf3b690a68b', 60366, 60366, '2026-06-15 12:00:00', 'smp-26420ea0545ca72a32b3', 'job-cis240068-06', 'res-cpu'),
@@ -327,31 +411,39 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-d4e85aad8a2a10fbe185', 'smp-f6d592517cf3b690a68b', 60503, 60503, '2026-06-15 12:00:00', 'smp-626aede3f65aeb969c74', 'job-cis240068-16', 'res-cpu'),
('smp-30ed7b95adcdbcc8432c', 'smp-f6d592517cf3b690a68b', 60396, 60396, '2026-02-15 12:00:00', 'smp-f6343c23c6a7028f8ed7', 'job-cis240068-22', 'res-cpu'),
('smp-3bf95a6ff91f95a7bf45', 'smp-f6d592517cf3b690a68b', 60518, 60518, '2026-04-15 12:00:00', 'smp-f6343c23c6a7028f8ed7', 'job-cis240068-24', 'res-cpu'),
- ('smp-c47aec92d29b9aa71a2c', 'smp-f6d592517cf3b690a68b', 60640, 60640, '2026-06-15 12:00:00', 'smp-f6343c23c6a7028f8ed7', 'job-cis240068-26', 'res-cpu');
-INSERT IGNORE INTO compute_allocation_change_requests (id, compute_allocation_id, requested_su_amount, requested_status, reason, change_status, requester_id, approver_id, timestamp) VALUES
- ('smp-a362284a709cb8db8365', 'smp-f6d592517cf3b690a68b', 600000, 'ACTIVE', 'Supplement for CIS240068 production campaign', 'REJECTED', 'smp-26420ea0545ca72a32b3', '', '2026-06-20 09:00:00');
-INSERT IGNORE INTO compute_allocation_change_request_events (id, compute_allocation_change_request_id, event_type, description, timestamp) VALUES
- ('smp-a362284a709cb8db8365-e1', 'smp-a362284a709cb8db8365', 'CREATED', 'Change request submitted by PI.', '2026-06-20 09:00:00');
+ ('smp-c47aec92d29b9aa71a2c', 'smp-f6d592517cf3b690a68b', 60640, 60640, '2026-06-15 12:00:00', 'smp-f6343c23c6a7028f8ed7', 'job-cis240068-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_change_requests (id, compute_allocation_id, requested_su_amount, requested_status, reason, change_status, requester_id, approver_id, timestamp) VALUES
+ ('smp-a362284a709cb8db8365', 'smp-f6d592517cf3b690a68b', 600000, 'ACTIVE', 'Supplement for CIS240068 production campaign', 'REJECTED', 'smp-26420ea0545ca72a32b3', '', '2026-06-20 09:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_change_request_events (id, compute_allocation_change_request_id, event_type, description, timestamp) VALUES
+ ('smp-a362284a709cb8db8365-e1', 'smp-a362284a709cb8db8365', 'CREATED', 'Change request submitted by PI.', '2026-06-20 09:00:00')
+ON CONFLICT DO NOTHING;
-- MCB240032: Molecular Dynamics of Intrinsically Disordered Proteins
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-2ef2fd65182345f961cf', 'MCB240032', 'Molecular Dynamics of Intrinsically Disordered Proteins', 'access', 'smp-3b164a9395896373810e', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-c9853aa7a5047707793f', 'smp-2ef2fd65182345f961cf', 'MCB240032 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 100000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-2ef2fd65182345f961cf', 'MCB240032', 'Molecular Dynamics of Intrinsically Disordered Proteins', 'access', 'smp-3b164a9395896373810e', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-c9853aa7a5047707793f', 'smp-2ef2fd65182345f961cf', 'MCB240032 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 100000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-2ef2fd65182345f961cf', 'smp-3b164a9395896373810e', 'PI'),
('smp-2ef2fd65182345f961cf', 'smp-f908bd32e57fd5c2896d', 'CO_PI'),
- ('smp-2ef2fd65182345f961cf', 'smp-b8e82843b10d1a15b995', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-2ef2fd65182345f961cf', 'smp-b8e82843b10d1a15b995', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-9476c4f1e5bf7533ef75', 'smp-c9853aa7a5047707793f', 'smp-3b164a9395896373810e', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-6f19adb01050b92b28ad', 'smp-c9853aa7a5047707793f', 'smp-f908bd32e57fd5c2896d', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
('smp-f6693a2b1c7120ffe29c', 'smp-c9853aa7a5047707793f', 'smp-4907af7ab30bd84e6deb', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-caa25cae214c2173de67', 'smp-c9853aa7a5047707793f', 'smp-fa5b4cdf77ce78bd27e0', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-07bffefcd0482952db55', 'smp-c9853aa7a5047707793f', 'smp-52fae2d4d9ff23760057', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-07bffefcd0482952db55', 'smp-c9853aa7a5047707793f', 'smp-52fae2d4d9ff23760057', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
('smp-04145f1728c174284d80', 'smp-c9853aa7a5047707793f', 'res-cpu', 1000, 3600),
- ('smp-86cca597b1b4dfe7f75f', 'smp-c9853aa7a5047707793f', 'res-gpu', 50, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-86cca597b1b4dfe7f75f', 'smp-c9853aa7a5047707793f', 'res-gpu', 50, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-0545c6e6175b54feda98', 'smp-c9853aa7a5047707793f', 5122, 5122, '2026-02-15 12:00:00', 'smp-3b164a9395896373810e', 'job-mcb240032-02', 'res-cpu'),
('smp-da639b4db3786bbb4839', 'smp-c9853aa7a5047707793f', 5244, 5244, '2026-04-15 12:00:00', 'smp-3b164a9395896373810e', 'job-mcb240032-04', 'res-cpu'),
('smp-2ff12ffb0c267e76a30a', 'smp-c9853aa7a5047707793f', 5366, 5366, '2026-06-15 12:00:00', 'smp-3b164a9395896373810e', 'job-mcb240032-06', 'res-cpu'),
@@ -360,26 +452,32 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-a494340a98c6f8a8d1f6', 'smp-c9853aa7a5047707793f', 5503, 5503, '2026-06-15 12:00:00', 'smp-4907af7ab30bd84e6deb', 'job-mcb240032-16', 'res-cpu'),
('smp-9660252e76ac549b0a47', 'smp-c9853aa7a5047707793f', 5396, 5396, '2026-02-15 12:00:00', 'smp-fa5b4cdf77ce78bd27e0', 'job-mcb240032-22', 'res-cpu'),
('smp-878859e4c47f6b6cc517', 'smp-c9853aa7a5047707793f', 5518, 5518, '2026-04-15 12:00:00', 'smp-fa5b4cdf77ce78bd27e0', 'job-mcb240032-24', 'res-cpu'),
- ('smp-552d8c8f929c5fd05719', 'smp-c9853aa7a5047707793f', 5640, 5640, '2026-06-15 12:00:00', 'smp-fa5b4cdf77ce78bd27e0', 'job-mcb240032-26', 'res-cpu');
+ ('smp-552d8c8f929c5fd05719', 'smp-c9853aa7a5047707793f', 5640, 5640, '2026-06-15 12:00:00', 'smp-fa5b4cdf77ce78bd27e0', 'job-mcb240032-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- ENG240046: Wall-Resolved LES of Turbine Film Cooling
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-0948a283a07e0b73d2f7', 'ENG240046', 'Wall-Resolved LES of Turbine Film Cooling', 'access', 'smp-626aede3f65aeb969c74', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-c4071741a17cbea31f46', 'smp-0948a283a07e0b73d2f7', 'ENG240046 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 750000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-0948a283a07e0b73d2f7', 'ENG240046', 'Wall-Resolved LES of Turbine Film Cooling', 'access', 'smp-626aede3f65aeb969c74', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-c4071741a17cbea31f46', 'smp-0948a283a07e0b73d2f7', 'ENG240046 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 750000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-0948a283a07e0b73d2f7', 'smp-626aede3f65aeb969c74', 'PI'),
('smp-0948a283a07e0b73d2f7', 'smp-f6343c23c6a7028f8ed7', 'CO_PI'),
- ('smp-0948a283a07e0b73d2f7', 'smp-318427dda738e0504620', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-0948a283a07e0b73d2f7', 'smp-318427dda738e0504620', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-ceeab8b8d03eb169950a', 'smp-c4071741a17cbea31f46', 'smp-626aede3f65aeb969c74', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
('smp-d3f4c3b41e0e54c34bed', 'smp-c4071741a17cbea31f46', 'smp-f6343c23c6a7028f8ed7', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-9d0325840ec68ace6a7d', 'smp-c4071741a17cbea31f46', 'smp-ae4c14e3cd98f58c2363', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-95c353026d3979df0010', 'smp-c4071741a17cbea31f46', 'smp-598aba3fd25a5b74a2cf', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-3f7b090718cb5d3bffda', 'smp-c4071741a17cbea31f46', 'smp-8d36f8ec62cc1ab62fd5', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
- ('smp-290e16e3bc2f7ef0e0ff', 'smp-c4071741a17cbea31f46', 'res-cpu', 7500, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-3f7b090718cb5d3bffda', 'smp-c4071741a17cbea31f46', 'smp-8d36f8ec62cc1ab62fd5', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-290e16e3bc2f7ef0e0ff', 'smp-c4071741a17cbea31f46', 'res-cpu', 7500, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-1b8f689a0cffe1d52ca8', 'smp-c4071741a17cbea31f46', 37622, 37622, '2026-02-15 12:00:00', 'smp-626aede3f65aeb969c74', 'job-eng240046-02', 'res-cpu'),
('smp-9ed2a39afce2ac00f3e5', 'smp-c4071741a17cbea31f46', 37744, 37744, '2026-04-15 12:00:00', 'smp-626aede3f65aeb969c74', 'job-eng240046-04', 'res-cpu'),
('smp-cfecf6fd8b56831fabed', 'smp-c4071741a17cbea31f46', 37866, 37866, '2026-06-15 12:00:00', 'smp-626aede3f65aeb969c74', 'job-eng240046-06', 'res-cpu'),
@@ -388,26 +486,32 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-e9c9bfbc8282a2058866', 'smp-c4071741a17cbea31f46', 38003, 38003, '2026-06-15 12:00:00', 'smp-ae4c14e3cd98f58c2363', 'job-eng240046-16', 'res-cpu'),
('smp-4177f7b4cfc4945eafe2', 'smp-c4071741a17cbea31f46', 37896, 37896, '2026-02-15 12:00:00', 'smp-598aba3fd25a5b74a2cf', 'job-eng240046-22', 'res-cpu'),
('smp-aa9d56523363c1d988ea', 'smp-c4071741a17cbea31f46', 38018, 38018, '2026-04-15 12:00:00', 'smp-598aba3fd25a5b74a2cf', 'job-eng240046-24', 'res-cpu'),
- ('smp-91b516fb1a3cd25627bb', 'smp-c4071741a17cbea31f46', 38140, 38140, '2026-06-15 12:00:00', 'smp-598aba3fd25a5b74a2cf', 'job-eng240046-26', 'res-cpu');
+ ('smp-91b516fb1a3cd25627bb', 'smp-c4071741a17cbea31f46', 38140, 38140, '2026-06-15 12:00:00', 'smp-598aba3fd25a5b74a2cf', 'job-eng240046-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- OCE240015: Mesoscale Eddy Tracking in Global Ocean Reanalysis
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-c642dcb393db75aa5526', 'OCE240015', 'Mesoscale Eddy Tracking in Global Ocean Reanalysis', 'access', 'smp-4907af7ab30bd84e6deb', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-de5487db358f6fb7d6fe', 'smp-c642dcb393db75aa5526', 'OCE240015 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 300000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-c642dcb393db75aa5526', 'OCE240015', 'Mesoscale Eddy Tracking in Global Ocean Reanalysis', 'access', 'smp-4907af7ab30bd84e6deb', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-de5487db358f6fb7d6fe', 'smp-c642dcb393db75aa5526', 'OCE240015 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 300000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-c642dcb393db75aa5526', 'smp-4907af7ab30bd84e6deb', 'PI'),
('smp-c642dcb393db75aa5526', 'smp-fa5b4cdf77ce78bd27e0', 'CO_PI'),
- ('smp-c642dcb393db75aa5526', 'smp-52fae2d4d9ff23760057', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-c642dcb393db75aa5526', 'smp-52fae2d4d9ff23760057', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-adc667d00a365ef25633', 'smp-de5487db358f6fb7d6fe', 'smp-4907af7ab30bd84e6deb', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-48470b978fb2146882a6', 'smp-de5487db358f6fb7d6fe', 'smp-fa5b4cdf77ce78bd27e0', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-036435c793e419eb68e5', 'smp-de5487db358f6fb7d6fe', 'smp-343175c9b7007d1ff0f8', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-cab86841b6858594eb99', 'smp-de5487db358f6fb7d6fe', 'smp-35b28b0dfed3e7dfcd36', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-6b247b1e68a043d0386b', 'smp-de5487db358f6fb7d6fe', 'smp-344b252c9dd481944db0', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
- ('smp-352960efaeab23d059b5', 'smp-de5487db358f6fb7d6fe', 'res-cpu', 3000, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-6b247b1e68a043d0386b', 'smp-de5487db358f6fb7d6fe', 'smp-344b252c9dd481944db0', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-352960efaeab23d059b5', 'smp-de5487db358f6fb7d6fe', 'res-cpu', 3000, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-5f737417545f4ef958b9', 'smp-de5487db358f6fb7d6fe', 15122, 15122, '2026-02-15 12:00:00', 'smp-4907af7ab30bd84e6deb', 'job-oce240015-02', 'res-cpu'),
('smp-47166fc6b0dd678f1bd5', 'smp-de5487db358f6fb7d6fe', 15244, 15244, '2026-04-15 12:00:00', 'smp-4907af7ab30bd84e6deb', 'job-oce240015-04', 'res-cpu'),
('smp-23f3f687943779bef143', 'smp-de5487db358f6fb7d6fe', 15366, 15366, '2026-06-15 12:00:00', 'smp-4907af7ab30bd84e6deb', 'job-oce240015-06', 'res-cpu'),
@@ -416,27 +520,33 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-6baa75f9201df61b025f', 'smp-de5487db358f6fb7d6fe', 15503, 15503, '2026-06-15 12:00:00', 'smp-343175c9b7007d1ff0f8', 'job-oce240015-16', 'res-cpu'),
('smp-ba1a17e1891bf497e4a3', 'smp-de5487db358f6fb7d6fe', 15396, 15396, '2026-02-15 12:00:00', 'smp-35b28b0dfed3e7dfcd36', 'job-oce240015-22', 'res-cpu'),
('smp-fb89291e07eabaf74b00', 'smp-de5487db358f6fb7d6fe', 15518, 15518, '2026-04-15 12:00:00', 'smp-35b28b0dfed3e7dfcd36', 'job-oce240015-24', 'res-cpu'),
- ('smp-a107d2c5d41c9a8015c0', 'smp-de5487db358f6fb7d6fe', 15640, 15640, '2026-06-15 12:00:00', 'smp-35b28b0dfed3e7dfcd36', 'job-oce240015-26', 'res-cpu');
+ ('smp-a107d2c5d41c9a8015c0', 'smp-de5487db358f6fb7d6fe', 15640, 15640, '2026-06-15 12:00:00', 'smp-35b28b0dfed3e7dfcd36', 'job-oce240015-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- DMS240027: High-Order Solvers for Kinetic Plasma Equations
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-22445a61084f5764042b', 'DMS240027', 'High-Order Solvers for Kinetic Plasma Equations', 'access', 'smp-ae4c14e3cd98f58c2363', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-ce03e036de1da6863393', 'smp-22445a61084f5764042b', 'DMS240027 startup allocation', 'INACTIVE', '00000000-0000-0000-0000-000000000001', 500000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-22445a61084f5764042b', 'DMS240027', 'High-Order Solvers for Kinetic Plasma Equations', 'access', 'smp-ae4c14e3cd98f58c2363', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-ce03e036de1da6863393', 'smp-22445a61084f5764042b', 'DMS240027 startup allocation', 'INACTIVE', '00000000-0000-0000-0000-000000000001', 500000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-22445a61084f5764042b', 'smp-ae4c14e3cd98f58c2363', 'PI'),
('smp-22445a61084f5764042b', 'smp-598aba3fd25a5b74a2cf', 'CO_PI'),
- ('smp-22445a61084f5764042b', 'smp-8d36f8ec62cc1ab62fd5', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-22445a61084f5764042b', 'smp-8d36f8ec62cc1ab62fd5', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-5f8a62442cc1be68f10d', 'smp-ce03e036de1da6863393', 'smp-ae4c14e3cd98f58c2363', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-7d6d016ed837e198c526', 'smp-ce03e036de1da6863393', 'smp-598aba3fd25a5b74a2cf', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-43c41d94073b21d4db5f', 'smp-ce03e036de1da6863393', 'smp-f908bd32e57fd5c2896d', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-f5fa7ced0d73482111ba', 'smp-ce03e036de1da6863393', 'smp-02b1cd6cafbd27dd7201', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
- ('smp-2b3ebce9395cb23a040e', 'smp-ce03e036de1da6863393', 'smp-c8e6b563ac8f348d6069', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-2b3ebce9395cb23a040e', 'smp-ce03e036de1da6863393', 'smp-c8e6b563ac8f348d6069', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
('smp-01b687d9274b62c9f6b5', 'smp-ce03e036de1da6863393', 'res-cpu', 5000, 3600),
- ('smp-f9c7a3aa26187e49d26e', 'smp-ce03e036de1da6863393', 'res-gpu', 250, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-f9c7a3aa26187e49d26e', 'smp-ce03e036de1da6863393', 'res-gpu', 250, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-dba38111b0c14e71fd7f', 'smp-ce03e036de1da6863393', 25122, 25122, '2026-02-15 12:00:00', 'smp-ae4c14e3cd98f58c2363', 'job-dms240027-02', 'res-cpu'),
('smp-2a90ead06577735119e1', 'smp-ce03e036de1da6863393', 25244, 25244, '2026-04-15 12:00:00', 'smp-ae4c14e3cd98f58c2363', 'job-dms240027-04', 'res-cpu'),
('smp-7072c544a0eec959aef0', 'smp-ce03e036de1da6863393', 25366, 25366, '2026-06-15 12:00:00', 'smp-ae4c14e3cd98f58c2363', 'job-dms240027-06', 'res-cpu'),
@@ -445,30 +555,38 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-c4278139fad25c9719e3', 'smp-ce03e036de1da6863393', 25503, 25503, '2026-06-15 12:00:00', 'smp-f908bd32e57fd5c2896d', 'job-dms240027-16', 'res-cpu'),
('smp-20b22d60cdb397c05c42', 'smp-ce03e036de1da6863393', 25396, 25396, '2026-02-15 12:00:00', 'smp-02b1cd6cafbd27dd7201', 'job-dms240027-22', 'res-cpu'),
('smp-8147ad5d8acf9a477192', 'smp-ce03e036de1da6863393', 25518, 25518, '2026-04-15 12:00:00', 'smp-02b1cd6cafbd27dd7201', 'job-dms240027-24', 'res-cpu'),
- ('smp-c462c852980fe4eaaef5', 'smp-ce03e036de1da6863393', 25640, 25640, '2026-06-15 12:00:00', 'smp-02b1cd6cafbd27dd7201', 'job-dms240027-26', 'res-cpu');
-INSERT IGNORE INTO compute_allocation_change_requests (id, compute_allocation_id, requested_su_amount, requested_status, reason, change_status, requester_id, approver_id, timestamp) VALUES
- ('smp-cba9d22751366aa10b94', 'smp-ce03e036de1da6863393', 250000, 'ACTIVE', 'Supplement for DMS240027 production campaign', 'PENDING', 'smp-ae4c14e3cd98f58c2363', '', '2026-06-24 09:00:00');
-INSERT IGNORE INTO compute_allocation_change_request_events (id, compute_allocation_change_request_id, event_type, description, timestamp) VALUES
- ('smp-cba9d22751366aa10b94-e1', 'smp-cba9d22751366aa10b94', 'CREATED', 'Change request submitted by PI.', '2026-06-24 09:00:00');
+ ('smp-c462c852980fe4eaaef5', 'smp-ce03e036de1da6863393', 25640, 25640, '2026-06-15 12:00:00', 'smp-02b1cd6cafbd27dd7201', 'job-dms240027-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_change_requests (id, compute_allocation_id, requested_su_amount, requested_status, reason, change_status, requester_id, approver_id, timestamp) VALUES
+ ('smp-cba9d22751366aa10b94', 'smp-ce03e036de1da6863393', 250000, 'ACTIVE', 'Supplement for DMS240027 production campaign', 'PENDING', 'smp-ae4c14e3cd98f58c2363', '', '2026-06-24 09:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_change_request_events (id, compute_allocation_change_request_id, event_type, description, timestamp) VALUES
+ ('smp-cba9d22751366aa10b94-e1', 'smp-cba9d22751366aa10b94', 'CREATED', 'Change request submitted by PI.', '2026-06-24 09:00:00')
+ON CONFLICT DO NOTHING;
-- EAR240038: Mantle Convection with Realistic Rheology
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-6f71b0a457dc2d9383c7', 'EAR240038', 'Mantle Convection with Realistic Rheology', 'access', 'smp-343175c9b7007d1ff0f8', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-cac362e9bc8a5b73dc56', 'smp-6f71b0a457dc2d9383c7', 'EAR240038 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 250000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-6f71b0a457dc2d9383c7', 'EAR240038', 'Mantle Convection with Realistic Rheology', 'access', 'smp-343175c9b7007d1ff0f8', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-cac362e9bc8a5b73dc56', 'smp-6f71b0a457dc2d9383c7', 'EAR240038 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 250000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-6f71b0a457dc2d9383c7', 'smp-343175c9b7007d1ff0f8', 'PI'),
('smp-6f71b0a457dc2d9383c7', 'smp-35b28b0dfed3e7dfcd36', 'CO_PI'),
- ('smp-6f71b0a457dc2d9383c7', 'smp-344b252c9dd481944db0', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-6f71b0a457dc2d9383c7', 'smp-344b252c9dd481944db0', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-7433b8a0532af1994f82', 'smp-cac362e9bc8a5b73dc56', 'smp-343175c9b7007d1ff0f8', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-8ab28e66c8069430b34a', 'smp-cac362e9bc8a5b73dc56', 'smp-35b28b0dfed3e7dfcd36', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-7ed90896509e0411de77', 'smp-cac362e9bc8a5b73dc56', 'smp-f6343c23c6a7028f8ed7', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
('smp-ee268f40f73c25a878df', 'smp-cac362e9bc8a5b73dc56', 'smp-b8e82843b10d1a15b995', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-5e3817eadd4645ed02d4', 'smp-cac362e9bc8a5b73dc56', 'smp-2fd62ff24cea6be8de4c', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
- ('smp-81f50c2c9a64f4e65068', 'smp-cac362e9bc8a5b73dc56', 'res-cpu', 2500, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-5e3817eadd4645ed02d4', 'smp-cac362e9bc8a5b73dc56', 'smp-2fd62ff24cea6be8de4c', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-81f50c2c9a64f4e65068', 'smp-cac362e9bc8a5b73dc56', 'res-cpu', 2500, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-c88c10bdb0c05befa0f2', 'smp-cac362e9bc8a5b73dc56', 12622, 12622, '2026-02-15 12:00:00', 'smp-343175c9b7007d1ff0f8', 'job-ear240038-02', 'res-cpu'),
('smp-324ff31c36ac2b276a59', 'smp-cac362e9bc8a5b73dc56', 12744, 12744, '2026-04-15 12:00:00', 'smp-343175c9b7007d1ff0f8', 'job-ear240038-04', 'res-cpu'),
('smp-dc60c880beda5edcc869', 'smp-cac362e9bc8a5b73dc56', 12866, 12866, '2026-06-15 12:00:00', 'smp-343175c9b7007d1ff0f8', 'job-ear240038-06', 'res-cpu'),
@@ -477,26 +595,32 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-e4ea2a77728d3faa087a', 'smp-cac362e9bc8a5b73dc56', 13003, 13003, '2026-06-15 12:00:00', 'smp-f6343c23c6a7028f8ed7', 'job-ear240038-16', 'res-cpu'),
('smp-a1cd91aae6e29ba33065', 'smp-cac362e9bc8a5b73dc56', 12896, 12896, '2026-02-15 12:00:00', 'smp-b8e82843b10d1a15b995', 'job-ear240038-22', 'res-cpu'),
('smp-35a3fb4f232de26e49a6', 'smp-cac362e9bc8a5b73dc56', 13018, 13018, '2026-04-15 12:00:00', 'smp-b8e82843b10d1a15b995', 'job-ear240038-24', 'res-cpu'),
- ('smp-ad766f924c56521625d2', 'smp-cac362e9bc8a5b73dc56', 13140, 13140, '2026-06-15 12:00:00', 'smp-b8e82843b10d1a15b995', 'job-ear240038-26', 'res-cpu');
+ ('smp-ad766f924c56521625d2', 'smp-cac362e9bc8a5b73dc56', 13140, 13140, '2026-06-15 12:00:00', 'smp-b8e82843b10d1a15b995', 'job-ear240038-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- NEU240052: Whole-Brain Spiking Network Simulation
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-f5e84bbacec87c3deb6d', 'NEU240052', 'Whole-Brain Spiking Network Simulation', 'access', 'smp-f908bd32e57fd5c2896d', 'INACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-b1145214bc21940084fa', 'smp-f5e84bbacec87c3deb6d', 'NEU240052 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 1200000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-f5e84bbacec87c3deb6d', 'NEU240052', 'Whole-Brain Spiking Network Simulation', 'access', 'smp-f908bd32e57fd5c2896d', 'INACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-b1145214bc21940084fa', 'smp-f5e84bbacec87c3deb6d', 'NEU240052 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 1200000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-f5e84bbacec87c3deb6d', 'smp-f908bd32e57fd5c2896d', 'PI'),
('smp-f5e84bbacec87c3deb6d', 'smp-02b1cd6cafbd27dd7201', 'CO_PI'),
- ('smp-f5e84bbacec87c3deb6d', 'smp-c8e6b563ac8f348d6069', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-f5e84bbacec87c3deb6d', 'smp-c8e6b563ac8f348d6069', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-ef7b3cf471a18f724bfe', 'smp-b1145214bc21940084fa', 'smp-f908bd32e57fd5c2896d', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-dd291d9acb1a3cf3a34d', 'smp-b1145214bc21940084fa', 'smp-02b1cd6cafbd27dd7201', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
('smp-8172646ef19cf2aba13d', 'smp-b1145214bc21940084fa', 'smp-fa5b4cdf77ce78bd27e0', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-62e2175da6c8cd7bc527', 'smp-b1145214bc21940084fa', 'smp-318427dda738e0504620', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-5ee1e3afbdb762fc82b9', 'smp-b1145214bc21940084fa', 'smp-bc08d052551918510023', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
- ('smp-272f3c504c041203dbb6', 'smp-b1145214bc21940084fa', 'res-cpu', 12000, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-5ee1e3afbdb762fc82b9', 'smp-b1145214bc21940084fa', 'smp-bc08d052551918510023', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-272f3c504c041203dbb6', 'smp-b1145214bc21940084fa', 'res-cpu', 12000, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-bbce515125ea9a07b292', 'smp-b1145214bc21940084fa', 60122, 60122, '2026-02-15 12:00:00', 'smp-f908bd32e57fd5c2896d', 'job-neu240052-02', 'res-cpu'),
('smp-22f88a7c4c6b3d6fe05d', 'smp-b1145214bc21940084fa', 60244, 60244, '2026-04-15 12:00:00', 'smp-f908bd32e57fd5c2896d', 'job-neu240052-04', 'res-cpu'),
('smp-01fffd2e411603a60bbd', 'smp-b1145214bc21940084fa', 60366, 60366, '2026-06-15 12:00:00', 'smp-f908bd32e57fd5c2896d', 'job-neu240052-06', 'res-cpu'),
@@ -505,27 +629,33 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-99f3926b6c00e9c5d335', 'smp-b1145214bc21940084fa', 60503, 60503, '2026-06-15 12:00:00', 'smp-fa5b4cdf77ce78bd27e0', 'job-neu240052-16', 'res-cpu'),
('smp-8b8ef509006b1f3c9baa', 'smp-b1145214bc21940084fa', 60396, 60396, '2026-02-15 12:00:00', 'smp-318427dda738e0504620', 'job-neu240052-22', 'res-cpu'),
('smp-ec7a532ca3a2d62e3556', 'smp-b1145214bc21940084fa', 60518, 60518, '2026-04-15 12:00:00', 'smp-318427dda738e0504620', 'job-neu240052-24', 'res-cpu'),
- ('smp-7bb7b64c3a488df7b353', 'smp-b1145214bc21940084fa', 60640, 60640, '2026-06-15 12:00:00', 'smp-318427dda738e0504620', 'job-neu240052-26', 'res-cpu');
+ ('smp-7bb7b64c3a488df7b353', 'smp-b1145214bc21940084fa', 60640, 60640, '2026-06-15 12:00:00', 'smp-318427dda738e0504620', 'job-neu240052-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- ECO240009: Agent-Based Modeling of Land-Use Change
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-e483b73b4458eae0fa14', 'ECO240009', 'Agent-Based Modeling of Land-Use Change', 'access', 'smp-f6343c23c6a7028f8ed7', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-7512b54990a4efa50cf6', 'smp-e483b73b4458eae0fa14', 'ECO240009 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 100000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-e483b73b4458eae0fa14', 'ECO240009', 'Agent-Based Modeling of Land-Use Change', 'access', 'smp-f6343c23c6a7028f8ed7', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-7512b54990a4efa50cf6', 'smp-e483b73b4458eae0fa14', 'ECO240009 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 100000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-e483b73b4458eae0fa14', 'smp-f6343c23c6a7028f8ed7', 'PI'),
('smp-e483b73b4458eae0fa14', 'smp-b8e82843b10d1a15b995', 'CO_PI'),
- ('smp-e483b73b4458eae0fa14', 'smp-2fd62ff24cea6be8de4c', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-e483b73b4458eae0fa14', 'smp-2fd62ff24cea6be8de4c', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-9d0742abbe4931843e57', 'smp-7512b54990a4efa50cf6', 'smp-f6343c23c6a7028f8ed7', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
('smp-849058dc7fcecbf7b6b5', 'smp-7512b54990a4efa50cf6', 'smp-b8e82843b10d1a15b995', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-a934e4f3f489392b9a05', 'smp-7512b54990a4efa50cf6', 'smp-598aba3fd25a5b74a2cf', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-432ed414e8e53550f302', 'smp-7512b54990a4efa50cf6', 'smp-52fae2d4d9ff23760057', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-5cbd30c58d82e19d150a', 'smp-7512b54990a4efa50cf6', 'smp-b800fa9f20c414f38e44', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-5cbd30c58d82e19d150a', 'smp-7512b54990a4efa50cf6', 'smp-b800fa9f20c414f38e44', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
('smp-5a5aec09c83d141ffac9', 'smp-7512b54990a4efa50cf6', 'res-cpu', 1000, 3600),
- ('smp-a96c3041058a5feb48be', 'smp-7512b54990a4efa50cf6', 'res-gpu', 50, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-a96c3041058a5feb48be', 'smp-7512b54990a4efa50cf6', 'res-gpu', 50, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-03881086768fe03766f4', 'smp-7512b54990a4efa50cf6', 5122, 5122, '2026-02-15 12:00:00', 'smp-f6343c23c6a7028f8ed7', 'job-eco240009-02', 'res-cpu'),
('smp-6a8c06b1577be4f478a6', 'smp-7512b54990a4efa50cf6', 5244, 5244, '2026-04-15 12:00:00', 'smp-f6343c23c6a7028f8ed7', 'job-eco240009-04', 'res-cpu'),
('smp-a47aeaa1650e33338ea2', 'smp-7512b54990a4efa50cf6', 5366, 5366, '2026-06-15 12:00:00', 'smp-f6343c23c6a7028f8ed7', 'job-eco240009-06', 'res-cpu'),
@@ -534,26 +664,32 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-2fd5abb579af4df3c888', 'smp-7512b54990a4efa50cf6', 5503, 5503, '2026-06-15 12:00:00', 'smp-598aba3fd25a5b74a2cf', 'job-eco240009-16', 'res-cpu'),
('smp-cb736460f484f94be590', 'smp-7512b54990a4efa50cf6', 5396, 5396, '2026-02-15 12:00:00', 'smp-52fae2d4d9ff23760057', 'job-eco240009-22', 'res-cpu'),
('smp-1fcffefc2bf002f5599c', 'smp-7512b54990a4efa50cf6', 5518, 5518, '2026-04-15 12:00:00', 'smp-52fae2d4d9ff23760057', 'job-eco240009-24', 'res-cpu'),
- ('smp-72ae86ab1c594bf42f50', 'smp-7512b54990a4efa50cf6', 5640, 5640, '2026-06-15 12:00:00', 'smp-52fae2d4d9ff23760057', 'job-eco240009-26', 'res-cpu');
+ ('smp-72ae86ab1c594bf42f50', 'smp-7512b54990a4efa50cf6', 5640, 5640, '2026-06-15 12:00:00', 'smp-52fae2d4d9ff23760057', 'job-eco240009-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- MED240061: Virtual Screening Against Antibiotic Resistance Targets
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-980231459f6b23c93e99', 'MED240061', 'Virtual Screening Against Antibiotic Resistance Targets', 'access', 'smp-fa5b4cdf77ce78bd27e0', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-bdb96ba1e6a5d37513db', 'smp-980231459f6b23c93e99', 'MED240061 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 750000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-980231459f6b23c93e99', 'MED240061', 'Virtual Screening Against Antibiotic Resistance Targets', 'access', 'smp-fa5b4cdf77ce78bd27e0', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-bdb96ba1e6a5d37513db', 'smp-980231459f6b23c93e99', 'MED240061 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 750000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-980231459f6b23c93e99', 'smp-fa5b4cdf77ce78bd27e0', 'PI'),
('smp-980231459f6b23c93e99', 'smp-318427dda738e0504620', 'CO_PI'),
- ('smp-980231459f6b23c93e99', 'smp-bc08d052551918510023', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-980231459f6b23c93e99', 'smp-bc08d052551918510023', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-7ffab5fb09b6212c8741', 'smp-bdb96ba1e6a5d37513db', 'smp-fa5b4cdf77ce78bd27e0', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-99a0dad83547931b15ad', 'smp-bdb96ba1e6a5d37513db', 'smp-318427dda738e0504620', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-fd315fada5b30c7a1b15', 'smp-bdb96ba1e6a5d37513db', 'smp-35b28b0dfed3e7dfcd36', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-b485267a18604bb920b4', 'smp-bdb96ba1e6a5d37513db', 'smp-8d36f8ec62cc1ab62fd5', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-c28380a7fcc0a214c012', 'smp-bdb96ba1e6a5d37513db', 'smp-3cd6d104b01ef129b379', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
- ('smp-7d2a9327eed6552b9597', 'smp-bdb96ba1e6a5d37513db', 'res-cpu', 7500, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-c28380a7fcc0a214c012', 'smp-bdb96ba1e6a5d37513db', 'smp-3cd6d104b01ef129b379', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-7d2a9327eed6552b9597', 'smp-bdb96ba1e6a5d37513db', 'res-cpu', 7500, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-fa6d5c9f7535e88b9fe3', 'smp-bdb96ba1e6a5d37513db', 37622, 37622, '2026-02-15 12:00:00', 'smp-fa5b4cdf77ce78bd27e0', 'job-med240061-02', 'res-cpu'),
('smp-7c000be8f80e3ca352db', 'smp-bdb96ba1e6a5d37513db', 37744, 37744, '2026-04-15 12:00:00', 'smp-fa5b4cdf77ce78bd27e0', 'job-med240061-04', 'res-cpu'),
('smp-fb4aaee12a343fe01586', 'smp-bdb96ba1e6a5d37513db', 37866, 37866, '2026-06-15 12:00:00', 'smp-fa5b4cdf77ce78bd27e0', 'job-med240061-06', 'res-cpu'),
@@ -562,30 +698,38 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-13e67e5444e2ec360cbd', 'smp-bdb96ba1e6a5d37513db', 38003, 38003, '2026-06-15 12:00:00', 'smp-35b28b0dfed3e7dfcd36', 'job-med240061-16', 'res-cpu'),
('smp-c7929bce2db2faeecc55', 'smp-bdb96ba1e6a5d37513db', 37896, 37896, '2026-02-15 12:00:00', 'smp-8d36f8ec62cc1ab62fd5', 'job-med240061-22', 'res-cpu'),
('smp-b03fe4d7ef1395d89bde', 'smp-bdb96ba1e6a5d37513db', 38018, 38018, '2026-04-15 12:00:00', 'smp-8d36f8ec62cc1ab62fd5', 'job-med240061-24', 'res-cpu'),
- ('smp-ecb89e0a260d7720850c', 'smp-bdb96ba1e6a5d37513db', 38140, 38140, '2026-06-15 12:00:00', 'smp-8d36f8ec62cc1ab62fd5', 'job-med240061-26', 'res-cpu');
-INSERT IGNORE INTO compute_allocation_change_requests (id, compute_allocation_id, requested_su_amount, requested_status, reason, change_status, requester_id, approver_id, timestamp) VALUES
- ('smp-6ce228100e9a0587de58', 'smp-bdb96ba1e6a5d37513db', 375000, 'ACTIVE', 'Supplement for MED240061 production campaign', 'PENDING', 'smp-fa5b4cdf77ce78bd27e0', '', '2026-06-20 09:00:00');
-INSERT IGNORE INTO compute_allocation_change_request_events (id, compute_allocation_change_request_id, event_type, description, timestamp) VALUES
- ('smp-6ce228100e9a0587de58-e1', 'smp-6ce228100e9a0587de58', 'CREATED', 'Change request submitted by PI.', '2026-06-20 09:00:00');
+ ('smp-ecb89e0a260d7720850c', 'smp-bdb96ba1e6a5d37513db', 38140, 38140, '2026-06-15 12:00:00', 'smp-8d36f8ec62cc1ab62fd5', 'job-med240061-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_change_requests (id, compute_allocation_id, requested_su_amount, requested_status, reason, change_status, requester_id, approver_id, timestamp) VALUES
+ ('smp-6ce228100e9a0587de58', 'smp-bdb96ba1e6a5d37513db', 375000, 'ACTIVE', 'Supplement for MED240061 production campaign', 'PENDING', 'smp-fa5b4cdf77ce78bd27e0', '', '2026-06-20 09:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_change_request_events (id, compute_allocation_change_request_id, event_type, description, timestamp) VALUES
+ ('smp-6ce228100e9a0587de58-e1', 'smp-6ce228100e9a0587de58', 'CREATED', 'Change request submitted by PI.', '2026-06-20 09:00:00')
+ON CONFLICT DO NOTHING;
-- AER240044: Hypersonic Boundary-Layer Transition Prediction
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-ac4de8764ba7aa98e886', 'AER240044', 'Hypersonic Boundary-Layer Transition Prediction', 'access', 'smp-598aba3fd25a5b74a2cf', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-fd5e6684bf9182850ebd', 'smp-ac4de8764ba7aa98e886', 'AER240044 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 300000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-ac4de8764ba7aa98e886', 'AER240044', 'Hypersonic Boundary-Layer Transition Prediction', 'access', 'smp-598aba3fd25a5b74a2cf', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-fd5e6684bf9182850ebd', 'smp-ac4de8764ba7aa98e886', 'AER240044 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 300000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-ac4de8764ba7aa98e886', 'smp-598aba3fd25a5b74a2cf', 'PI'),
('smp-ac4de8764ba7aa98e886', 'smp-52fae2d4d9ff23760057', 'CO_PI'),
- ('smp-ac4de8764ba7aa98e886', 'smp-b800fa9f20c414f38e44', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-ac4de8764ba7aa98e886', 'smp-b800fa9f20c414f38e44', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-9ac2e29e9bca669bedac', 'smp-fd5e6684bf9182850ebd', 'smp-598aba3fd25a5b74a2cf', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-276a2a13e89431d65dd1', 'smp-fd5e6684bf9182850ebd', 'smp-52fae2d4d9ff23760057', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-9f80e1cb60b7fed5519d', 'smp-fd5e6684bf9182850ebd', 'smp-02b1cd6cafbd27dd7201', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-23ee3cd03d7dd5895364', 'smp-fd5e6684bf9182850ebd', 'smp-344b252c9dd481944db0', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
- ('smp-5f60915d151ee7c64632', 'smp-fd5e6684bf9182850ebd', 'smp-90f9c3a872e74ceb9b54', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
- ('smp-785ef1f0e1c1269098dd', 'smp-fd5e6684bf9182850ebd', 'res-cpu', 3000, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-5f60915d151ee7c64632', 'smp-fd5e6684bf9182850ebd', 'smp-90f9c3a872e74ceb9b54', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-785ef1f0e1c1269098dd', 'smp-fd5e6684bf9182850ebd', 'res-cpu', 3000, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-2840160fb65ec4f4d7ce', 'smp-fd5e6684bf9182850ebd', 15122, 15122, '2026-02-15 12:00:00', 'smp-598aba3fd25a5b74a2cf', 'job-aer240044-02', 'res-cpu'),
('smp-894388502b8d4c479e50', 'smp-fd5e6684bf9182850ebd', 15244, 15244, '2026-04-15 12:00:00', 'smp-598aba3fd25a5b74a2cf', 'job-aer240044-04', 'res-cpu'),
('smp-fec37bbc32f5d233a7f6', 'smp-fd5e6684bf9182850ebd', 15366, 15366, '2026-06-15 12:00:00', 'smp-598aba3fd25a5b74a2cf', 'job-aer240044-06', 'res-cpu'),
@@ -594,27 +738,33 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-bbef762e52a9d5327f23', 'smp-fd5e6684bf9182850ebd', 15503, 15503, '2026-06-15 12:00:00', 'smp-02b1cd6cafbd27dd7201', 'job-aer240044-16', 'res-cpu'),
('smp-0cdc260bcd08b694ecef', 'smp-fd5e6684bf9182850ebd', 15396, 15396, '2026-02-15 12:00:00', 'smp-344b252c9dd481944db0', 'job-aer240044-22', 'res-cpu'),
('smp-594643d1848f1c5317a0', 'smp-fd5e6684bf9182850ebd', 15518, 15518, '2026-04-15 12:00:00', 'smp-344b252c9dd481944db0', 'job-aer240044-24', 'res-cpu'),
- ('smp-1b6c4033a2c532111c26', 'smp-fd5e6684bf9182850ebd', 15640, 15640, '2026-06-15 12:00:00', 'smp-344b252c9dd481944db0', 'job-aer240044-26', 'res-cpu');
+ ('smp-1b6c4033a2c532111c26', 'smp-fd5e6684bf9182850ebd', 15640, 15640, '2026-06-15 12:00:00', 'smp-344b252c9dd481944db0', 'job-aer240044-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- QCN240017: Tensor-Network Simulation of Quantum Circuits
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-0b70f6fc04a8a1f03514', 'QCN240017', 'Tensor-Network Simulation of Quantum Circuits', 'access', 'smp-35b28b0dfed3e7dfcd36', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-81d26521f4dc52bb030b', 'smp-0b70f6fc04a8a1f03514', 'QCN240017 startup allocation', 'INACTIVE', '00000000-0000-0000-0000-000000000001', 500000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-0b70f6fc04a8a1f03514', 'QCN240017', 'Tensor-Network Simulation of Quantum Circuits', 'access', 'smp-35b28b0dfed3e7dfcd36', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-81d26521f4dc52bb030b', 'smp-0b70f6fc04a8a1f03514', 'QCN240017 startup allocation', 'INACTIVE', '00000000-0000-0000-0000-000000000001', 500000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-0b70f6fc04a8a1f03514', 'smp-35b28b0dfed3e7dfcd36', 'PI'),
('smp-0b70f6fc04a8a1f03514', 'smp-8d36f8ec62cc1ab62fd5', 'CO_PI'),
- ('smp-0b70f6fc04a8a1f03514', 'smp-3cd6d104b01ef129b379', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-0b70f6fc04a8a1f03514', 'smp-3cd6d104b01ef129b379', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-10a5eafb6445f979c71d', 'smp-81d26521f4dc52bb030b', 'smp-35b28b0dfed3e7dfcd36', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-2065f6b4a0c81045f9a6', 'smp-81d26521f4dc52bb030b', 'smp-8d36f8ec62cc1ab62fd5', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-77b9d327bcbd501ea39e', 'smp-81d26521f4dc52bb030b', 'smp-b8e82843b10d1a15b995', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
('smp-9957caef1d18c43f12b3', 'smp-81d26521f4dc52bb030b', 'smp-c8e6b563ac8f348d6069', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-e7da29a49c0b1e06670a', 'smp-81d26521f4dc52bb030b', 'smp-38b35e0e4b22d0d6460a', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-e7da29a49c0b1e06670a', 'smp-81d26521f4dc52bb030b', 'smp-38b35e0e4b22d0d6460a', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
('smp-f39914bda842d3029073', 'smp-81d26521f4dc52bb030b', 'res-cpu', 5000, 3600),
- ('smp-48fe1aeed1dee260a1cd', 'smp-81d26521f4dc52bb030b', 'res-gpu', 250, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-48fe1aeed1dee260a1cd', 'smp-81d26521f4dc52bb030b', 'res-gpu', 250, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-8aba1e6527fb6978d840', 'smp-81d26521f4dc52bb030b', 25122, 25122, '2026-02-15 12:00:00', 'smp-35b28b0dfed3e7dfcd36', 'job-qcn240017-02', 'res-cpu'),
('smp-6a627f8a8045fc5d0914', 'smp-81d26521f4dc52bb030b', 25244, 25244, '2026-04-15 12:00:00', 'smp-35b28b0dfed3e7dfcd36', 'job-qcn240017-04', 'res-cpu'),
('smp-e011e4b777f76f1850da', 'smp-81d26521f4dc52bb030b', 25366, 25366, '2026-06-15 12:00:00', 'smp-35b28b0dfed3e7dfcd36', 'job-qcn240017-06', 'res-cpu'),
@@ -623,26 +773,32 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-6f5f0597c0fcc780cf39', 'smp-81d26521f4dc52bb030b', 25503, 25503, '2026-06-15 12:00:00', 'smp-b8e82843b10d1a15b995', 'job-qcn240017-16', 'res-cpu'),
('smp-5699a40460fb16a2423e', 'smp-81d26521f4dc52bb030b', 25396, 25396, '2026-02-15 12:00:00', 'smp-c8e6b563ac8f348d6069', 'job-qcn240017-22', 'res-cpu'),
('smp-9ef9920430d5a62d1381', 'smp-81d26521f4dc52bb030b', 25518, 25518, '2026-04-15 12:00:00', 'smp-c8e6b563ac8f348d6069', 'job-qcn240017-24', 'res-cpu'),
- ('smp-3a66778317d3640f3405', 'smp-81d26521f4dc52bb030b', 25640, 25640, '2026-06-15 12:00:00', 'smp-c8e6b563ac8f348d6069', 'job-qcn240017-26', 'res-cpu');
+ ('smp-3a66778317d3640f3405', 'smp-81d26521f4dc52bb030b', 25640, 25640, '2026-06-15 12:00:00', 'smp-c8e6b563ac8f348d6069', 'job-qcn240017-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- SES240029: Urban Microclimate Simulation for Heat Resilience
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
- ('smp-30a2eabc73a65a860667', 'SES240029', 'Urban Microclimate Simulation for Heat Resilience', 'access', 'smp-02b1cd6cafbd27dd7201', 'ACTIVE');
-INSERT IGNORE INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
- ('smp-0384acb90162ba62c422', 'smp-30a2eabc73a65a860667', 'SES240029 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 250000, '2026-01-01 00:00:00', '2026-12-31 00:00:00');
-INSERT IGNORE INTO project_memberships (project_id, user_id, role) VALUES
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status) VALUES
+ ('smp-30a2eabc73a65a860667', 'SES240029', 'Urban Microclimate Simulation for Heat Resilience', 'access', 'smp-02b1cd6cafbd27dd7201', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocations (id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
+ ('smp-0384acb90162ba62c422', 'smp-30a2eabc73a65a860667', 'SES240029 startup allocation', 'ACTIVE', '00000000-0000-0000-0000-000000000001', 250000, '2026-01-01 00:00:00', '2026-12-31 00:00:00')
+ON CONFLICT DO NOTHING;
+INSERT INTO project_memberships (project_id, user_id, role) VALUES
('smp-30a2eabc73a65a860667', 'smp-02b1cd6cafbd27dd7201', 'PI'),
('smp-30a2eabc73a65a860667', 'smp-344b252c9dd481944db0', 'CO_PI'),
- ('smp-30a2eabc73a65a860667', 'smp-90f9c3a872e74ceb9b54', 'ALLOCATION_MANAGER');
-INSERT IGNORE INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
+ ('smp-30a2eabc73a65a860667', 'smp-90f9c3a872e74ceb9b54', 'ALLOCATION_MANAGER')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_memberships (id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
('smp-08f7c147f068caa62854', 'smp-0384acb90162ba62c422', 'smp-02b1cd6cafbd27dd7201', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-acee8d6c86e4d40da1e0', 'smp-0384acb90162ba62c422', 'smp-344b252c9dd481944db0', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'INACTIVE'),
('smp-ee0408bdf7f2155c00ac', 'smp-0384acb90162ba62c422', 'smp-318427dda738e0504620', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
('smp-a5b622ae224e8db0569e', 'smp-0384acb90162ba62c422', 'smp-2fd62ff24cea6be8de4c', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE'),
- ('smp-40a595e58ab783c22456', 'smp-0384acb90162ba62c422', 'smp-26420ea0545ca72a32b3', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE');
-INSERT IGNORE INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
- ('smp-71ee2172bc07537ec447', 'smp-0384acb90162ba62c422', 'res-cpu', 2500, 3600);
-INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
+ ('smp-40a595e58ab783c22456', 'smp-0384acb90162ba62c422', 'smp-26420ea0545ca72a32b3', '2026-01-01 00:00:00', '2026-12-31 00:00:00', 'ACTIVE')
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_resource_mappings (id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time) VALUES
+ ('smp-71ee2172bc07537ec447', 'smp-0384acb90162ba62c422', 'res-cpu', 2500, 3600)
+ON CONFLICT DO NOTHING;
+INSERT INTO compute_allocation_usages (id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id) VALUES
('smp-8973a187de40708db0da', 'smp-0384acb90162ba62c422', 12622, 12622, '2026-02-15 12:00:00', 'smp-02b1cd6cafbd27dd7201', 'job-ses240029-02', 'res-cpu'),
('smp-33251ff628cd501dc185', 'smp-0384acb90162ba62c422', 12744, 12744, '2026-04-15 12:00:00', 'smp-02b1cd6cafbd27dd7201', 'job-ses240029-04', 'res-cpu'),
('smp-34541172ae248bfde92a', 'smp-0384acb90162ba62c422', 12866, 12866, '2026-06-15 12:00:00', 'smp-02b1cd6cafbd27dd7201', 'job-ses240029-06', 'res-cpu'),
@@ -651,11 +807,13 @@ INSERT IGNORE INTO compute_allocation_usages (id, compute_allocation_id, used_ra
('smp-5f848d4163e3d823d89f', 'smp-0384acb90162ba62c422', 13003, 13003, '2026-06-15 12:00:00', 'smp-318427dda738e0504620', 'job-ses240029-16', 'res-cpu'),
('smp-87de20f2ff9c078f1bad', 'smp-0384acb90162ba62c422', 12896, 12896, '2026-02-15 12:00:00', 'smp-2fd62ff24cea6be8de4c', 'job-ses240029-22', 'res-cpu'),
('smp-63be3f2e1dc38e8cc659', 'smp-0384acb90162ba62c422', 13018, 13018, '2026-04-15 12:00:00', 'smp-2fd62ff24cea6be8de4c', 'job-ses240029-24', 'res-cpu'),
- ('smp-c90ca83a563957d78e84', 'smp-0384acb90162ba62c422', 13140, 13140, '2026-06-15 12:00:00', 'smp-2fd62ff24cea6be8de4c', 'job-ses240029-26', 'res-cpu');
+ ('smp-c90ca83a563957d78e84', 'smp-0384acb90162ba62c422', 13140, 13140, '2026-06-15 12:00:00', 'smp-2fd62ff24cea6be8de4c', 'job-ses240029-26', 'res-cpu')
+ON CONFLICT DO NOTHING;
-- Allocation history entries so the allocation History tab has rows.
-INSERT IGNORE INTO compute_allocation_diffs (id, compute_allocation_id, diff_type, new_su_amount, status, timestamp, description) VALUES
+INSERT INTO compute_allocation_diffs (id, compute_allocation_id, diff_type, new_su_amount, status, timestamp, description) VALUES
('smp-diff-1', 'smp-c4071741a17cbea31f46', 'ALLOCATION_STATUS_CHANGE', 500000, 'ACTIVE', '2026-01-15 09:00:00', 'Allocation activated after project approval'),
('smp-diff-2', 'smp-c4071741a17cbea31f46', 'USAGE_UPDATE', 500000, 'ACTIVE', '2026-04-01 02:00:00', 'SU usage updated from quarterly accounting run'),
('smp-diff-3', 'smp-c4071741a17cbea31f46', 'ALLOCATION_INCREASE', 650000, 'ACTIVE', '2026-06-20 14:30:00', 'Supplement approved via change request'),
- ('smp-diff-4', 'smp-81d26521f4dc52bb030b', 'ALLOCATION_STATUS_CHANGE', 250000, 'INACTIVE', '2026-05-30 00:00:00', 'Allocation end date reached');
+ ('smp-diff-4', 'smp-81d26521f4dc52bb030b', 'ALLOCATION_STATUS_CHANGE', 250000, 'INACTIVE', '2026-05-30 00:00:00', 'Allocation end date reached')
+ON CONFLICT DO NOTHING;
diff --git a/dev-ops/compose/seeds/sample_resources.sql b/dev-ops/compose/seeds/sample_resources.sql
index 89835a529..89b28b783 100644
--- a/dev-ops/compose/seeds/sample_resources.sql
+++ b/dev-ops/compose/seeds/sample_resources.sql
@@ -9,51 +9,60 @@
-- to apply on its own after migrations. Rate windows are chosen so every
-- status renders: expired, superseded (overlapped by a later start), active,
-- scheduled, and one resource with no rates at all.
--- Idempotent via INSERT IGNORE + deterministic IDs.
+-- Idempotent via ON CONFLICT DO NOTHING + deterministic IDs.
-USE custos;
-INSERT IGNORE INTO compute_clusters (id, name) VALUES
+SET TIME ZONE 'UTC';
+
+INSERT INTO compute_clusters (id, name) VALUES
('smpr-cluster-a', 'ClusterA'),
- ('smpr-cluster-b', 'ClusterB');
+ ('smpr-cluster-b', 'ClusterB')
+ON CONFLICT DO NOTHING;
-INSERT IGNORE INTO compute_allocation_resources (id, name, resource_type, resource_amount, compute_cluster_id) VALUES
+INSERT INTO compute_allocation_resources (id, name, resource_type, resource_amount, compute_cluster_id) VALUES
('smpr-res-a-cpu', 'ClusterA CPU Core Hours', 'CPU_HOURS', 2000000, 'smpr-cluster-a'),
('smpr-res-a-storage', 'ClusterA Project Storage', 'STORAGE_TB', 500, 'smpr-cluster-a'),
('smpr-res-b-gpu', 'ClusterB GPU Hours', 'GPU_HOURS', 150000, 'smpr-cluster-b'),
- ('smpr-res-b-mem', 'ClusterB Large Memory Hours', 'CPU_HOURS', 300000, 'smpr-cluster-b');
+ ('smpr-res-b-mem', 'ClusterB Large Memory Hours', 'CPU_HOURS', 300000, 'smpr-cluster-b')
+ON CONFLICT DO NOTHING;
-- ClusterA CPU: full lifecycle. The 2025 window is superseded from
-- 2026-05-01 by the later-starting 1.25 rate (latest start wins); a 2027
-- change is already scheduled.
-INSERT IGNORE INTO compute_allocation_resource_rates (id, compute_allocation_resource_id, rate, start_time, end_time) VALUES
+INSERT INTO compute_allocation_resource_rates (id, compute_allocation_resource_id, rate, start_time, end_time) VALUES
('smpr-rate-acpu-2024', 'smpr-res-a-cpu', 1.00, '2024-01-01 00:00:00', '2025-01-01 00:00:00'),
('smpr-rate-acpu-2025', 'smpr-res-a-cpu', 1.10, '2025-01-01 00:00:00', '2026-12-31 00:00:00'),
('smpr-rate-acpu-cur', 'smpr-res-a-cpu', 1.25, '2026-05-01 00:00:00', '2027-05-01 00:00:00'),
- ('smpr-rate-acpu-next', 'smpr-res-a-cpu', 1.40, '2027-05-01 00:00:00', '2028-05-01 00:00:00');
+ ('smpr-rate-acpu-next', 'smpr-res-a-cpu', 1.40, '2027-05-01 00:00:00', '2028-05-01 00:00:00')
+ON CONFLICT DO NOTHING;
-- ClusterB GPU: one active rate and one scheduled increase.
-INSERT IGNORE INTO compute_allocation_resource_rates (id, compute_allocation_resource_id, rate, start_time, end_time) VALUES
+INSERT INTO compute_allocation_resource_rates (id, compute_allocation_resource_id, rate, start_time, end_time) VALUES
('smpr-rate-bgpu-cur', 'smpr-res-b-gpu', 8.00, '2026-01-01 00:00:00', '2027-01-01 00:00:00'),
- ('smpr-rate-bgpu-next', 'smpr-res-b-gpu', 9.50, '2027-01-01 00:00:00', '2028-01-01 00:00:00');
+ ('smpr-rate-bgpu-next', 'smpr-res-b-gpu', 9.50, '2027-01-01 00:00:00', '2028-01-01 00:00:00')
+ON CONFLICT DO NOTHING;
-- ClusterB Large Memory: a single expired rate, so the resource currently
-- has no effective rate (the gap case).
-INSERT IGNORE INTO compute_allocation_resource_rates (id, compute_allocation_resource_id, rate, start_time, end_time) VALUES
- ('smpr-rate-bmem-old', 'smpr-res-b-mem', 2.00, '2024-06-01 00:00:00', '2025-06-01 00:00:00');
+INSERT INTO compute_allocation_resource_rates (id, compute_allocation_resource_id, rate, start_time, end_time) VALUES
+ ('smpr-rate-bmem-old', 'smpr-res-b-mem', 2.00, '2024-06-01 00:00:00', '2025-06-01 00:00:00')
+ON CONFLICT DO NOTHING;
-- ClusterA Project Storage intentionally has no rates (empty-state case).
-- Two sample users with provisioned cluster accounts so the cluster local
-- users view has rows. ClusterB intentionally keeps only one account.
-INSERT IGNORE INTO organizations (id, originated_id, name) VALUES
- ('sample-org', 'sample-org', 'Sample Research Consortium');
+INSERT INTO organizations (id, originated_id, name) VALUES
+ ('sample-org', 'sample-org', 'Sample Research Consortium')
+ON CONFLICT DO NOTHING;
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES
('smpr-user-nvasquez', 'sample-org', 'Nadia', 'Vasquez', '', 'nadia.vasquez@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL'),
- ('smpr-user-tokada', 'sample-org', 'Takeshi', 'Okada', '', 'takeshi.okada@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL');
+ ('smpr-user-tokada', 'sample-org', 'Takeshi', 'Okada', '', 'takeshi.okada@sample.example.edu', 'ACTIVE', 'CLUSTER_LOCAL')
+ON CONFLICT DO NOTHING;
-INSERT IGNORE INTO compute_cluster_users (id, compute_cluster_id, user_id, local_username) VALUES
+INSERT INTO compute_cluster_users (id, compute_cluster_id, user_id, local_username) VALUES
('smpr-ccu-a-1', 'smpr-cluster-a', 'smpr-user-nvasquez', 'nvasquez'),
('smpr-ccu-a-2', 'smpr-cluster-a', 'smpr-user-tokada', 'tokada'),
- ('smpr-ccu-b-1', 'smpr-cluster-b', 'smpr-user-nvasquez', 'nvasquez');
\ No newline at end of file
+ ('smpr-ccu-b-1', 'smpr-cluster-b', 'smpr-user-nvasquez', 'nvasquez')
+ON CONFLICT DO NOTHING;
\ No newline at end of file
diff --git a/dev-ops/compose/seeds/sample_usage_series.sql b/dev-ops/compose/seeds/sample_usage_series.sql
index 0f0a0e2d6..227f4d8ad 100644
--- a/dev-ops/compose/seeds/sample_usage_series.sql
+++ b/dev-ops/compose/seeds/sample_usage_series.sql
@@ -12,59 +12,68 @@
-- several members, so the burn-down, usage-over-time, by-resource, and
-- by-member widgets all render with realistic shapes.
--
--- Idempotent via INSERT IGNORE + deterministic IDs (smpu- prefix). To view it
+-- Idempotent via ON CONFLICT DO NOTHING + deterministic IDs (smpu- prefix). To view it
-- signed in as yourself, attach your user to the project once (not committed):
--- INSERT IGNORE INTO project_memberships (project_id, user_id, role, added_time)
--- SELECT 'smpu-project', id, 'PI', NOW(6) FROM users WHERE email = '<your-email>';
+-- INSERT INTO project_memberships (project_id, user_id, role, added_time)
+-- SELECT 'smpu-project', id, 'PI', NOW() FROM users WHERE email = '<your-email>' ON CONFLICT DO NOTHING;
-USE custos;
-INSERT IGNORE INTO organizations (id, originated_id, name) VALUES
- ('smpu-org', 'SMPU-ORG', 'Aurora Research Lab');
+SET TIME ZONE 'UTC';
-INSERT IGNORE INTO users (id, organization_id, first_name, last_name, middle_name, email, status) VALUES
+INSERT INTO organizations (id, originated_id, name) VALUES
+ ('smpu-org', 'SMPU-ORG', 'Aurora Research Lab')
+ON CONFLICT DO NOTHING;
+
+INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status) VALUES
('smpu-user-pi', 'smpu-org', 'Maria', 'Alvarez', '', 'maria.alvarez@aurora.example.edu', 'ACTIVE'),
('smpu-user-mgr', 'smpu-org', 'David', 'Okafor', '', 'david.okafor@aurora.example.edu', 'ACTIVE'),
('smpu-user-mem1', 'smpu-org', 'Lena', 'Petrov', '', 'lena.petrov@aurora.example.edu', 'ACTIVE'),
- ('smpu-user-mem2', 'smpu-org', 'Sam', 'Nguyen', '', 'sam.nguyen@aurora.example.edu', 'ACTIVE');
+ ('smpu-user-mem2', 'smpu-org', 'Sam', 'Nguyen', '', 'sam.nguyen@aurora.example.edu', 'ACTIVE')
+ON CONFLICT DO NOTHING;
-INSERT IGNORE INTO compute_clusters (id, name) VALUES
- ('smpu-cluster', 'Aurora');
+INSERT INTO compute_clusters (id, name) VALUES
+ ('smpu-cluster', 'Aurora')
+ON CONFLICT DO NOTHING;
-INSERT IGNORE INTO compute_allocation_resources (id, name, resource_type, resource_amount, compute_cluster_id) VALUES
+INSERT INTO compute_allocation_resources (id, name, resource_type, resource_amount, compute_cluster_id) VALUES
('smpu-res-gpu', 'B200 GPU Hours', 'GPU_HOURS', 100000, 'smpu-cluster'),
('smpu-res-cpu', 'CPU Core Hours', 'CPU_HOURS', 4000000, 'smpu-cluster'),
- ('smpu-res-storage', 'Project Storage', 'STORAGE_TB', 800, 'smpu-cluster');
+ ('smpu-res-storage', 'Project Storage', 'STORAGE_TB', 800, 'smpu-cluster')
+ON CONFLICT DO NOTHING;
-INSERT IGNORE INTO projects (id, originated_id, title, origination, project_pi_id, status, created_time) VALUES
- ('smpu-project', 'SMPU-REC-001', 'Aurora Genomics', 'TEST', 'smpu-user-pi', 'ACTIVE', NOW(6));
+INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status, created_time) VALUES
+ ('smpu-project', 'SMPU-REC-001', 'Aurora Genomics', 'TEST', 'smpu-user-pi', 'ACTIVE', NOW())
+ON CONFLICT DO NOTHING;
-INSERT IGNORE INTO project_memberships (project_id, user_id, role, added_time) VALUES
- ('smpu-project', 'smpu-user-pi', 'PI', NOW(6)),
- ('smpu-project', 'smpu-user-mgr', 'ALLOCATION_MANAGER', NOW(6));
+INSERT INTO project_memberships (project_id, user_id, role, added_time) VALUES
+ ('smpu-project', 'smpu-user-pi', 'PI', NOW()),
+ ('smpu-project', 'smpu-user-mgr', 'ALLOCATION_MANAGER', NOW())
+ON CONFLICT DO NOTHING;
-- At-risk allocation: started 40 days ago, ends 15 days out, ~90% burned.
-- Healthy allocation: started 40 days ago, ends 120 days out, ~25% burned.
-INSERT IGNORE INTO compute_allocations
+INSERT INTO compute_allocations
(id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time) VALUES
('smpu-alloc-atrisk', 'smpu-project', 'Aurora GPU', 'ACTIVE', 'smpu-cluster', 500000,
- DATE_SUB(CURDATE(), INTERVAL 40 DAY), DATE_ADD(CURDATE(), INTERVAL 15 DAY)),
+ CURRENT_DATE - INTERVAL '40 days', CURRENT_DATE + INTERVAL '15 days'),
('smpu-alloc-healthy', 'smpu-project', 'Aurora Mixed', 'ACTIVE', 'smpu-cluster', 2000000,
- DATE_SUB(CURDATE(), INTERVAL 40 DAY), DATE_ADD(CURDATE(), INTERVAL 120 DAY));
+ CURRENT_DATE - INTERVAL '40 days', CURRENT_DATE + INTERVAL '120 days')
+ON CONFLICT DO NOTHING;
-INSERT IGNORE INTO compute_allocation_memberships
+INSERT INTO compute_allocation_memberships
(id, compute_allocation_id, user_id, start_time, end_time, membership_status) VALUES
- ('smpu-cam-ar-pi', 'smpu-alloc-atrisk', 'smpu-user-pi', DATE_SUB(CURDATE(), INTERVAL 40 DAY), DATE_ADD(CURDATE(), INTERVAL 15 DAY), 'ACTIVE'),
- ('smpu-cam-ar-mem1', 'smpu-alloc-atrisk', 'smpu-user-mem1', DATE_SUB(CURDATE(), INTERVAL 40 DAY), DATE_ADD(CURDATE(), INTERVAL 15 DAY), 'ACTIVE'),
- ('smpu-cam-ar-mem2', 'smpu-alloc-atrisk', 'smpu-user-mem2', DATE_SUB(CURDATE(), INTERVAL 40 DAY), DATE_ADD(CURDATE(), INTERVAL 15 DAY), 'ACTIVE'),
- ('smpu-cam-he-pi', 'smpu-alloc-healthy', 'smpu-user-pi', DATE_SUB(CURDATE(), INTERVAL 40 DAY), DATE_ADD(CURDATE(), INTERVAL 120 DAY), 'ACTIVE'),
- ('smpu-cam-he-mem1', 'smpu-alloc-healthy', 'smpu-user-mem1', DATE_SUB(CURDATE(), INTERVAL 40 DAY), DATE_ADD(CURDATE(), INTERVAL 120 DAY), 'ACTIVE');
+ ('smpu-cam-ar-pi', 'smpu-alloc-atrisk', 'smpu-user-pi', CURRENT_DATE - INTERVAL '40 days', CURRENT_DATE + INTERVAL '15 days', 'ACTIVE'),
+ ('smpu-cam-ar-mem1', 'smpu-alloc-atrisk', 'smpu-user-mem1', CURRENT_DATE - INTERVAL '40 days', CURRENT_DATE + INTERVAL '15 days', 'ACTIVE'),
+ ('smpu-cam-ar-mem2', 'smpu-alloc-atrisk', 'smpu-user-mem2', CURRENT_DATE - INTERVAL '40 days', CURRENT_DATE + INTERVAL '15 days', 'ACTIVE'),
+ ('smpu-cam-he-pi', 'smpu-alloc-healthy', 'smpu-user-pi', CURRENT_DATE - INTERVAL '40 days', CURRENT_DATE + INTERVAL '120 days', 'ACTIVE'),
+ ('smpu-cam-he-mem1', 'smpu-alloc-healthy', 'smpu-user-mem1', CURRENT_DATE - INTERVAL '40 days', CURRENT_DATE + INTERVAL '120 days', 'ACTIVE')
+ON CONFLICT DO NOTHING;
-- Usage series. Each row below is one (allocation, resource, member, daily
-- rate) stream expanded over 35 days by the recursive day counter. Amounts
-- carry a small deterministic wobble (via the day index) so bars are not flat.
-- su = credits (BIGINT); raw = native units shown in tooltips.
-INSERT IGNORE INTO compute_allocation_usages
+INSERT INTO compute_allocation_usages
(id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id)
WITH RECURSIVE days AS (
SELECT 0 AS n UNION ALL SELECT n + 1 FROM days WHERE n < 34
@@ -81,10 +90,11 @@ streams AS (
SELECT
CONCAT('smpu-use-', st.k, '-', d.n),
st.a,
- GREATEST(1, (st.b + st.s * ((d.n * 37 + 11) % 9)) DIV st.rd),
+ GREATEST(1, (st.b + st.s * ((d.n * 37 + 11) % 9)) / st.rd),
st.b + st.s * ((d.n * 37 + 11) % 9),
- DATE_SUB(CURDATE(), INTERVAL (34 - d.n) DAY) + INTERVAL 12 HOUR,
+ CURRENT_DATE - (34 - d.n) * INTERVAL '1 day' + INTERVAL '12 hours',
st.u,
CONCAT('job-', st.k, '-', d.n),
st.r
-FROM streams st CROSS JOIN days d;
+FROM streams st CROSS JOIN days d
+ON CONFLICT DO NOTHING;
diff --git a/dev-ops/local-amie/README.md b/dev-ops/local-amie/README.md
index 1b581648e..43a5844fa 100644
--- a/dev-ops/local-amie/README.md
+++ b/dev-ops/local-amie/README.md
@@ -26,7 +26,7 @@ ports so it coexists with the main dev stack in `dev-ops/compose/`.
| Service | Container | Host port |
|-------------|-------------------------|-----------|
-| MariaDB | `custos_amie_test_db` | `3307` |
+| PostgreSQL | `custos_amie_test_db` | `5433` |
| mock-amie | `custos_amie_test_mock` | `8181` |
## Usage
diff --git a/dev-ops/local-amie/compose.yaml b/dev-ops/local-amie/compose.yaml
index e0b158081..5be2209a0 100644
--- a/dev-ops/local-amie/compose.yaml
+++ b/dev-ops/local-amie/compose.yaml
@@ -23,20 +23,19 @@
services:
db:
container_name: custos_amie_test_db
- image: mariadb:11.2
+ image: postgres:17
restart: "no"
- command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
ports:
- - "3307:3306"
+ - "5433:5432"
environment:
- MARIADB_ROOT_PASSWORD: root
- MARIADB_USER: admin
- MARIADB_PASSWORD: admin
- MAX_ALLOWED_PACKET: 1073741824
+ POSTGRES_USER: admin
+ POSTGRES_PASSWORD: admin
+ POSTGRES_DB: custos
+ TZ: UTC
volumes:
- ../compose/dbinit:/docker-entrypoint-initdb.d:ro
healthcheck:
- test: [ "CMD", "mariadb-admin", "ping", "-uroot", "-proot", "--silent" ]
+ test: [ "CMD-SHELL", "pg_isready -U admin -d custos" ]
interval: 2s
timeout: 5s
retries: 30
diff --git a/docs/API-Docs.md b/docs/API-Docs.md
index 2a55a7576..9d41f0ec0 100644
--- a/docs/API-Docs.md
+++ b/docs/API-Docs.md
@@ -1446,7 +1446,7 @@ A minimal `config/custos.yaml`:
```yaml
core:
database:
- url: "custos:secret@tcp(127.0.0.1:3306)/custos?parseTime=true&charset=utf8mb4"
+ url: "postgres://custos:secret@127.0.0.1:5432/custos?sslmode=disable"
api:
port: 8080
log_level: "info"
@@ -1459,7 +1459,7 @@ Secrets can stay out of the file via `${VAR}` substitution — e.g.
| Setting | Source | Default | Purpose |
|---------|--------|---------|---------|
-| `core.database.url` | YAML | *(required)* | MariaDB / MySQL DSN. `parseTime=true` is mandatory. |
+| `core.database.url` | YAML | *(required)* | PostgreSQL DSN. |
| `core.api.port` | YAML | `8080` | HTTP API port. |
| `core.log_level` | YAML | `info` | One of `debug`, `info`, `warn`, `error`. |
| `DB_MAX_OPEN_CONNS` | env | `25` | Maximum open database connections. |
diff --git a/docs/contributing/writing-a-connector.md b/docs/contributing/writing-a-connector.md
index 32807635d..b287122b0 100644
--- a/docs/contributing/writing-a-connector.md
+++ b/docs/contributing/writing-a-connector.md
@@ -550,17 +550,17 @@ Mark the file with `//go:build integration` on the first line. Then provide
the database DSN via env var and run with the tag:
```bash
-export CORE_TEST_DATABASE_DSN='admin:admin@tcp(localhost:3306)/custos?...&multiStatements=true'
+export CORE_TEST_DATABASE_DSN='postgres://admin:admin@localhost:5432/custos_test?sslmode=disable'
go test -tags integration ./connectors/Lustre/Sync/...
```
-The dev compose stack on `:3306` is sufficient. If your tests need destructive
+The dev compose stack on `:5432` is sufficient. If your tests need destructive
fixtures (truncate tables, seed deterministic rows), spin up your own
isolated stack under `dev-ops/local-<connector>/` so the dev DB stays
untouched. AMIE is the existing example.
[CONTRIBUTING.md](../../CONTRIBUTING.md) covers the full test pyramid,
-including the AMIE-specific isolated stack on `:3307`.
+including the AMIE-specific isolated stack on `:5433`.
---
diff --git a/go.mod b/go.mod
index ee14e4a0e..eff48a3f2 100644
--- a/go.mod
+++ b/go.mod
@@ -4,9 +4,9 @@ go 1.25.0
require (
github.com/coreos/go-oidc/v3 v3.19.0
- github.com/go-sql-driver/mysql v1.8.1
github.com/golang-migrate/migrate/v4 v4.19.1
github.com/google/uuid v1.6.0
+ github.com/jackc/pgx/v5 v5.10.0
github.com/jmoiron/sqlx v1.4.0
github.com/prometheus/client_golang v1.23.2
github.com/prometheus/client_model v0.6.2
@@ -31,6 +31,10 @@ require (
github.com/go-openapi/jsonreference v0.21.0 // indirect
github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.23.0 // indirect
+ github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect
+ github.com/jackc/pgpassfile v1.0.0 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
+ github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
diff --git a/go.sum b/go.sum
index 96b01fe44..29e28d579 100644
--- a/go.sum
+++ b/go.sum
@@ -69,6 +69,16 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw=
+github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
+github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
+github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
+github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o=
github.com/jmoiron/sqlx v1.4.0/go.mod h1:ZrZ7UsYB/weZdl2Bxg6jCRO9c3YHl8r3ahlKmRT4JLY=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -126,6 +136,7 @@ github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
diff --git a/internal/db/db.go b/internal/db/db.go
index a662d5467..477bbd861 100644
--- a/internal/db/db.go
+++ b/internal/db/db.go
@@ -20,7 +20,7 @@ package db
import (
"fmt"
- _ "github.com/go-sql-driver/mysql"
+ _ "github.com/jackc/pgx/v5/stdlib"
"github.com/jmoiron/sqlx"
)
@@ -31,9 +31,9 @@ type Config struct {
MaxIdleConns int
}
-// Open opens and validates a MySQL/MariaDB connection using the supplied config.
+// Open opens and validates a PostgreSQL connection using the supplied config.
func Open(cfg Config) (*sqlx.DB, error) {
- db, err := sqlx.Open("mysql", cfg.DSN)
+ db, err := sqlx.Open("pgx", cfg.DSN)
if err != nil {
return nil, fmt.Errorf("open database: %w", err)
}
diff --git a/internal/db/migrate.go b/internal/db/migrate.go
index f8a53cfea..cae6258fe 100644
--- a/internal/db/migrate.go
+++ b/internal/db/migrate.go
@@ -25,7 +25,7 @@ import (
"regexp"
"github.com/golang-migrate/migrate/v4"
- "github.com/golang-migrate/migrate/v4/database/mysql"
+ pgxmigrate "github.com/golang-migrate/migrate/v4/database/pgx/v5"
"github.com/golang-migrate/migrate/v4/source/iofs"
"github.com/jmoiron/sqlx"
)
@@ -34,7 +34,7 @@ import (
// migrations/ directory against the supplied database.
// Returns nil when there is nothing to apply.
func MigrateEmbedded(database *sqlx.DB) error {
- driver, err := mysql.WithInstance(database.DB, &mysql.Config{})
+ driver, err := pgxmigrate.WithInstance(database.DB, &pgxmigrate.Config{})
if err != nil {
return fmt.Errorf("create migration driver: %w", err)
}
@@ -44,7 +44,7 @@ func MigrateEmbedded(database *sqlx.DB) error {
return fmt.Errorf("create migration source: %w", err)
}
- m, err := migrate.NewWithInstance("iofs", source, "mysql", driver)
+ m, err := migrate.NewWithInstance("iofs", source, "pgx", driver)
if err != nil {
return fmt.Errorf("create migrator: %w", err)
}
@@ -78,7 +78,7 @@ func MigrateConnectorFS(database *sqlx.DB, src fs.FS, dir, name string) error {
return fmt.Errorf("connector name %q is invalid; must match %s", name, connectorNamePattern)
}
- driver, err := mysql.WithInstance(database.DB, &mysql.Config{
+ driver, err := pgxmigrate.WithInstance(database.DB, &pgxmigrate.Config{
MigrationsTable: "schema_migrations_" + name,
})
if err != nil {
@@ -90,7 +90,7 @@ func MigrateConnectorFS(database *sqlx.DB, src fs.FS, dir, name string) error {
return fmt.Errorf("create migration source for %s: %w", name, err)
}
- m, err := migrate.NewWithInstance("iofs", source, "mysql", driver)
+ m, err := migrate.NewWithInstance("iofs", source, "pgx", driver)
if err != nil {
return fmt.Errorf("create migrator for %s: %w", name, err)
}
diff --git a/internal/db/migrations/000001_identity.down.sql b/internal/db/migrations/000001_identity.down.sql
index 67a59e0b9..c4f00c9b7 100644
--- a/internal/db/migrations/000001_identity.down.sql
+++ b/internal/db/migrations/000001_identity.down.sql
@@ -18,3 +18,4 @@
DROP TABLE IF EXISTS user_identities;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS organizations;
+DROP FUNCTION IF EXISTS set_updated_at();
diff --git a/internal/db/migrations/000001_identity.up.sql b/internal/db/migrations/000001_identity.up.sql
index 071d4436a..4730a28c0 100644
--- a/internal/db/migrations/000001_identity.up.sql
+++ b/internal/db/migrations/000001_identity.up.sql
@@ -15,19 +15,18 @@
-- specific language governing permissions and limitations
-- under the License.
-SET NAMES utf8mb4;
-SET time_zone = '+00:00';
+CREATE EXTENSION IF NOT EXISTS citext;
CREATE TABLE IF NOT EXISTS organizations
(
id VARCHAR(255) NOT NULL,
originated_id VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
- PRIMARY KEY (id),
- KEY idx_organizations_originated_id (originated_id)
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ updated_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ PRIMARY KEY (id)
+);
+CREATE INDEX IF NOT EXISTS idx_organizations_originated_id ON organizations (originated_id);
CREATE TABLE IF NOT EXISTS users
(
@@ -36,17 +35,17 @@ CREATE TABLE IF NOT EXISTS users
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255) NOT NULL,
middle_name VARCHAR(255) NOT NULL DEFAULT '',
- email VARCHAR(255) NOT NULL,
+ email CITEXT NOT NULL CHECK (length(email) <= 255),
status VARCHAR(32) NOT NULL DEFAULT 'PENDING',
type VARCHAR(32) NOT NULL DEFAULT 'CLUSTER_LOCAL',
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ updated_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- UNIQUE KEY uq_users_email (email),
- KEY idx_users_organization_id (organization_id),
- KEY idx_users_status (status),
+ CONSTRAINT uq_users_email UNIQUE (email),
CONSTRAINT fk_users_organization FOREIGN KEY (organization_id) REFERENCES organizations (id) ON DELETE RESTRICT
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_users_organization_id ON users (organization_id);
+CREATE INDEX IF NOT EXISTS idx_users_status ON users (status);
-- email captures the address the source IdP configured for this identity.
-- oidc_sub is nullable: not every external identity issues an OIDC subject
@@ -58,14 +57,32 @@ CREATE TABLE IF NOT EXISTS user_identities
user_id VARCHAR(255) NOT NULL,
source VARCHAR(64) NOT NULL,
external_id VARCHAR(255) NOT NULL,
- email VARCHAR(255) NULL DEFAULT NULL,
+ email CITEXT NULL DEFAULT NULL CHECK (email IS NULL OR length(email) <= 255),
oidc_sub VARCHAR(255) NULL DEFAULT NULL,
metadata TEXT NULL DEFAULT NULL,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ updated_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- UNIQUE KEY uq_user_identities_source_external (source, external_id),
- UNIQUE KEY uq_user_identities_oidc_sub (oidc_sub),
- KEY idx_user_identities_user (user_id),
+ CONSTRAINT uq_user_identities_source_external UNIQUE (source, external_id),
+ CONSTRAINT uq_user_identities_oidc_sub UNIQUE (oidc_sub),
CONSTRAINT fk_user_identities_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_user_identities_user ON user_identities (user_id);
+
+CREATE OR REPLACE FUNCTION set_updated_at() RETURNS TRIGGER AS $$
+BEGIN
+ NEW.updated_at = NOW();
+ RETURN NEW;
+END $$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE TRIGGER trg_organizations_updated_at
+ BEFORE UPDATE ON organizations
+ FOR EACH ROW EXECUTE FUNCTION set_updated_at();
+
+CREATE OR REPLACE TRIGGER trg_users_updated_at
+ BEFORE UPDATE ON users
+ FOR EACH ROW EXECUTE FUNCTION set_updated_at();
+
+CREATE OR REPLACE TRIGGER trg_user_identities_updated_at
+ BEFORE UPDATE ON user_identities
+ FOR EACH ROW EXECUTE FUNCTION set_updated_at();
diff --git a/internal/db/migrations/000002_compute_clusters.up.sql b/internal/db/migrations/000002_compute_clusters.up.sql
index f8d07017d..2aefb80f9 100644
--- a/internal/db/migrations/000002_compute_clusters.up.sql
+++ b/internal/db/migrations/000002_compute_clusters.up.sql
@@ -15,18 +15,15 @@
-- specific language governing permissions and limitations
-- under the License.
-SET NAMES utf8mb4;
-SET time_zone = '+00:00';
-
CREATE TABLE IF NOT EXISTS compute_clusters
(
id VARCHAR(255) NOT NULL,
name VARCHAR(255) NOT NULL,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ updated_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- UNIQUE KEY uq_compute_clusters_name (name)
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+ CONSTRAINT uq_compute_clusters_name UNIQUE (name)
+);
CREATE TABLE IF NOT EXISTS compute_cluster_users
(
@@ -34,13 +31,21 @@ CREATE TABLE IF NOT EXISTS compute_cluster_users
compute_cluster_id VARCHAR(255) NOT NULL,
user_id VARCHAR(255) NOT NULL,
local_username VARCHAR(255) NOT NULL,
- provisioned_at TIMESTAMP(6) NULL,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
+ provisioned_at TIMESTAMPTZ(6) NULL,
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ updated_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- UNIQUE KEY uq_compute_cluster_users_pair (compute_cluster_id, user_id),
- UNIQUE KEY uq_compute_cluster_users_local_username (compute_cluster_id, local_username),
- KEY idx_compute_cluster_users_user (user_id),
+ CONSTRAINT uq_compute_cluster_users_pair UNIQUE (compute_cluster_id, user_id),
+ CONSTRAINT uq_compute_cluster_users_local_username UNIQUE (compute_cluster_id, local_username),
CONSTRAINT fk_compute_cluster_users_cluster FOREIGN KEY (compute_cluster_id) REFERENCES compute_clusters (id) ON DELETE CASCADE,
CONSTRAINT fk_compute_cluster_users_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_compute_cluster_users_user ON compute_cluster_users (user_id);
+
+CREATE OR REPLACE TRIGGER trg_compute_clusters_updated_at
+ BEFORE UPDATE ON compute_clusters
+ FOR EACH ROW EXECUTE FUNCTION set_updated_at();
+
+CREATE OR REPLACE TRIGGER trg_compute_cluster_users_updated_at
+ BEFORE UPDATE ON compute_cluster_users
+ FOR EACH ROW EXECUTE FUNCTION set_updated_at();
diff --git a/internal/db/migrations/000003_projects.up.sql b/internal/db/migrations/000003_projects.up.sql
index b9278a8dc..3bc2151a8 100644
--- a/internal/db/migrations/000003_projects.up.sql
+++ b/internal/db/migrations/000003_projects.up.sql
@@ -15,9 +15,6 @@
-- specific language governing permissions and limitations
-- under the License.
-SET NAMES utf8mb4;
-SET time_zone = '+00:00';
-
CREATE TABLE IF NOT EXISTS projects
(
id VARCHAR(255) NOT NULL,
@@ -26,13 +23,13 @@ CREATE TABLE IF NOT EXISTS projects
origination VARCHAR(255) NOT NULL,
project_pi_id VARCHAR(255) NOT NULL,
status VARCHAR(32) NOT NULL DEFAULT 'ACTIVE',
- created_time TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ created_time TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- KEY idx_projects_originated_id (originated_id),
- KEY idx_projects_pi (project_pi_id),
- KEY idx_projects_status (status),
CONSTRAINT fk_projects_pi FOREIGN KEY (project_pi_id) REFERENCES users (id) ON DELETE RESTRICT
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_projects_originated_id ON projects (originated_id);
+CREATE INDEX IF NOT EXISTS idx_projects_pi ON projects (project_pi_id);
+CREATE INDEX IF NOT EXISTS idx_projects_status ON projects (status);
-- Project-level governance roles (PI / CO_PI / ALLOCATION_MANAGER). MEMBER
-- status is derived from compute_allocation_memberships, so it is not stored
@@ -42,10 +39,10 @@ CREATE TABLE IF NOT EXISTS project_memberships
project_id VARCHAR(255) NOT NULL,
user_id VARCHAR(255) NOT NULL,
role VARCHAR(32) NOT NULL,
- added_time TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ added_time TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (project_id, user_id),
- KEY idx_project_memberships_role (role),
CONSTRAINT fk_project_memberships_project FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE CASCADE,
CONSTRAINT fk_project_memberships_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
CONSTRAINT chk_project_memberships_role CHECK (role IN ('PI', 'CO_PI', 'ALLOCATION_MANAGER'))
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_project_memberships_role ON project_memberships (role);
diff --git a/internal/db/migrations/000004_allocations.up.sql b/internal/db/migrations/000004_allocations.up.sql
index 6488a5314..31ef1fdc8 100644
--- a/internal/db/migrations/000004_allocations.up.sql
+++ b/internal/db/migrations/000004_allocations.up.sql
@@ -15,9 +15,6 @@
-- specific language governing permissions and limitations
-- under the License.
-SET NAMES utf8mb4;
-SET time_zone = '+00:00';
-
CREATE TABLE IF NOT EXISTS compute_allocations
(
id VARCHAR(255) NOT NULL,
@@ -26,16 +23,16 @@ CREATE TABLE IF NOT EXISTS compute_allocations
status VARCHAR(64) NOT NULL,
compute_cluster_id VARCHAR(255) NOT NULL,
initial_su_amount BIGINT NOT NULL DEFAULT 0,
- start_time TIMESTAMP(6) NOT NULL,
- end_time TIMESTAMP(6) NOT NULL,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
+ start_time TIMESTAMPTZ(6) NOT NULL,
+ end_time TIMESTAMPTZ(6) NOT NULL,
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ updated_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- KEY idx_compute_allocations_project (project_id),
- KEY idx_compute_allocations_cluster (compute_cluster_id),
CONSTRAINT fk_compute_allocations_project FOREIGN KEY (project_id) REFERENCES projects (id) ON DELETE RESTRICT,
CONSTRAINT fk_compute_allocations_cluster FOREIGN KEY (compute_cluster_id) REFERENCES compute_clusters (id) ON DELETE RESTRICT
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_compute_allocations_project ON compute_allocations (project_id);
+CREATE INDEX IF NOT EXISTS idx_compute_allocations_cluster ON compute_allocations (compute_cluster_id);
CREATE TABLE IF NOT EXISTS compute_allocation_resources
(
@@ -44,15 +41,15 @@ CREATE TABLE IF NOT EXISTS compute_allocation_resources
resource_type VARCHAR(64) NOT NULL,
resource_amount BIGINT NOT NULL DEFAULT 0,
compute_cluster_id VARCHAR(255) NOT NULL,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ updated_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- UNIQUE KEY uq_compute_allocation_resources_cluster_name (compute_cluster_id, name),
- KEY idx_compute_allocation_resources_type (resource_type),
- KEY idx_compute_allocation_resources_cluster (compute_cluster_id),
+ CONSTRAINT uq_compute_allocation_resources_cluster_name UNIQUE (compute_cluster_id, name),
CONSTRAINT fk_compute_allocation_resources_cluster FOREIGN KEY (compute_cluster_id)
REFERENCES compute_clusters (id) ON DELETE RESTRICT
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_resources_type ON compute_allocation_resources (resource_type);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_resources_cluster ON compute_allocation_resources (compute_cluster_id);
CREATE TABLE IF NOT EXISTS compute_allocation_resource_mappings
(
@@ -61,32 +58,32 @@ CREATE TABLE IF NOT EXISTS compute_allocation_resource_mappings
compute_allocation_resource_id VARCHAR(255) NOT NULL,
resource_amount BIGINT NOT NULL DEFAULT 0,
resource_time BIGINT NOT NULL DEFAULT 0,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- UNIQUE KEY uq_carm_pair (compute_allocation_id, compute_allocation_resource_id),
- KEY idx_carm_allocation (compute_allocation_id),
- KEY idx_carm_resource (compute_allocation_resource_id),
+ CONSTRAINT uq_carm_pair UNIQUE (compute_allocation_id, compute_allocation_resource_id),
CONSTRAINT fk_carm_allocation FOREIGN KEY (compute_allocation_id)
REFERENCES compute_allocations (id) ON DELETE CASCADE,
CONSTRAINT fk_carm_resource FOREIGN KEY (compute_allocation_resource_id)
REFERENCES compute_allocation_resources (id) ON DELETE CASCADE
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_carm_allocation ON compute_allocation_resource_mappings (compute_allocation_id);
+CREATE INDEX IF NOT EXISTS idx_carm_resource ON compute_allocation_resource_mappings (compute_allocation_resource_id);
CREATE TABLE IF NOT EXISTS compute_allocation_resource_rates
(
id VARCHAR(255) NOT NULL,
compute_allocation_resource_id VARCHAR(255) NOT NULL,
- rate DOUBLE NOT NULL DEFAULT 0,
- start_time TIMESTAMP(6) NOT NULL,
- end_time TIMESTAMP(6) NOT NULL,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
+ rate DOUBLE PRECISION NOT NULL DEFAULT 0,
+ start_time TIMESTAMPTZ(6) NOT NULL,
+ end_time TIMESTAMPTZ(6) NOT NULL,
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ updated_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- KEY idx_carr_rates_resource (compute_allocation_resource_id),
- KEY idx_carr_rates_window (compute_allocation_resource_id, start_time, end_time),
CONSTRAINT fk_carr_rates_resource FOREIGN KEY (compute_allocation_resource_id)
REFERENCES compute_allocation_resources (id) ON DELETE CASCADE
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_carr_rates_resource ON compute_allocation_resource_rates (compute_allocation_resource_id);
+CREATE INDEX IF NOT EXISTS idx_carr_rates_window ON compute_allocation_resource_rates (compute_allocation_resource_id, start_time, end_time);
CREATE TABLE IF NOT EXISTS compute_allocation_diffs
(
@@ -95,15 +92,15 @@ CREATE TABLE IF NOT EXISTS compute_allocation_diffs
diff_type VARCHAR(64) NOT NULL,
new_su_amount BIGINT NOT NULL DEFAULT 0,
status VARCHAR(64) NOT NULL,
- timestamp TIMESTAMP(6) NOT NULL,
+ timestamp TIMESTAMPTZ(6) NOT NULL,
description TEXT NOT NULL,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- KEY idx_compute_allocation_diffs_allocation (compute_allocation_id, timestamp),
- KEY idx_compute_allocation_diffs_type (diff_type),
CONSTRAINT fk_compute_allocation_diffs_allocation FOREIGN KEY (compute_allocation_id)
REFERENCES compute_allocations (id) ON DELETE CASCADE
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_diffs_allocation ON compute_allocation_diffs (compute_allocation_id, timestamp);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_diffs_type ON compute_allocation_diffs (diff_type);
CREATE TABLE IF NOT EXISTS compute_allocation_change_requests
(
@@ -115,15 +112,15 @@ CREATE TABLE IF NOT EXISTS compute_allocation_change_requests
change_status VARCHAR(64) NOT NULL,
requester_id VARCHAR(255) NOT NULL,
approver_id VARCHAR(255) NOT NULL DEFAULT '',
- timestamp TIMESTAMP(6) NOT NULL,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ timestamp TIMESTAMPTZ(6) NOT NULL,
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- KEY idx_compute_allocation_change_requests_allocation (compute_allocation_id, timestamp),
- KEY idx_compute_allocation_change_requests_status (change_status),
- KEY idx_compute_allocation_change_requests_requester (requester_id),
CONSTRAINT fk_compute_allocation_change_requests_allocation FOREIGN KEY (compute_allocation_id)
REFERENCES compute_allocations (id) ON DELETE CASCADE
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_change_requests_allocation ON compute_allocation_change_requests (compute_allocation_id, timestamp);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_change_requests_status ON compute_allocation_change_requests (change_status);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_change_requests_requester ON compute_allocation_change_requests (requester_id);
CREATE TABLE IF NOT EXISTS compute_allocation_change_request_events
(
@@ -131,47 +128,47 @@ CREATE TABLE IF NOT EXISTS compute_allocation_change_request_events
compute_allocation_change_request_id VARCHAR(255) NOT NULL,
event_type VARCHAR(64) NOT NULL,
description TEXT NOT NULL,
- timestamp TIMESTAMP(6) NOT NULL,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- PRIMARY KEY (id),
- KEY idx_compute_allocation_change_request_events_request (compute_allocation_change_request_id, timestamp),
- KEY idx_compute_allocation_change_request_events_type (event_type)
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+ timestamp TIMESTAMPTZ(6) NOT NULL,
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ PRIMARY KEY (id)
+);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_change_request_events_request ON compute_allocation_change_request_events (compute_allocation_change_request_id, timestamp);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_change_request_events_type ON compute_allocation_change_request_events (event_type);
CREATE TABLE IF NOT EXISTS compute_allocation_memberships
(
id VARCHAR(255) NOT NULL,
compute_allocation_id VARCHAR(255) NOT NULL,
user_id VARCHAR(255) NOT NULL,
- start_time TIMESTAMP(6) NOT NULL,
- end_time TIMESTAMP(6) NOT NULL,
+ start_time TIMESTAMPTZ(6) NOT NULL,
+ end_time TIMESTAMPTZ(6) NOT NULL,
membership_status VARCHAR(64) NOT NULL,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- UNIQUE KEY uq_compute_allocation_memberships_allocation_user (compute_allocation_id, user_id),
- KEY idx_compute_allocation_memberships_user (user_id),
- KEY idx_compute_allocation_memberships_status (membership_status),
+ CONSTRAINT uq_compute_allocation_memberships_allocation_user UNIQUE (compute_allocation_id, user_id),
CONSTRAINT fk_compute_allocation_memberships_allocation FOREIGN KEY (compute_allocation_id)
REFERENCES compute_allocations (id) ON DELETE CASCADE
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_memberships_user ON compute_allocation_memberships (user_id);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_memberships_status ON compute_allocation_memberships (membership_status);
CREATE TABLE IF NOT EXISTS compute_allocation_usages
(
id VARCHAR(255) NOT NULL,
compute_allocation_id VARCHAR(255) NOT NULL,
- used_raw_amount DOUBLE NOT NULL DEFAULT 0,
- used_su_amount DOUBLE NOT NULL DEFAULT 0,
- calculated_time TIMESTAMP(6) NOT NULL,
+ used_raw_amount DOUBLE PRECISION NOT NULL DEFAULT 0,
+ used_su_amount DOUBLE PRECISION NOT NULL DEFAULT 0,
+ calculated_time TIMESTAMPTZ(6) NOT NULL,
user_id VARCHAR(255) NOT NULL DEFAULT '',
job_id VARCHAR(255) NOT NULL DEFAULT '',
compute_allocation_resource_id VARCHAR(255) NOT NULL DEFAULT '',
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- PRIMARY KEY (id),
- KEY idx_compute_allocation_usages_allocation (compute_allocation_id, calculated_time),
- KEY idx_compute_allocation_usages_user (user_id),
- KEY idx_compute_allocation_usages_job (job_id),
- KEY idx_compute_allocation_usages_resource (compute_allocation_resource_id)
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ PRIMARY KEY (id)
+);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_usages_allocation ON compute_allocation_usages (compute_allocation_id, calculated_time);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_usages_user ON compute_allocation_usages (user_id);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_usages_job ON compute_allocation_usages (job_id);
+CREATE INDEX IF NOT EXISTS idx_compute_allocation_usages_resource ON compute_allocation_usages (compute_allocation_resource_id);
CREATE TABLE IF NOT EXISTS compute_allocation_membership_resource_overrides
(
@@ -180,13 +177,29 @@ CREATE TABLE IF NOT EXISTS compute_allocation_membership_resource_overrides
compute_allocation_resource_id VARCHAR(255) NOT NULL,
override_resource_amount BIGINT NOT NULL DEFAULT 0,
override_resource_time BIGINT NOT NULL DEFAULT 0,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6),
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ updated_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- UNIQUE KEY uq_camro_membership_resource (compute_allocation_membership_id, compute_allocation_resource_id),
- KEY idx_camro_resource (compute_allocation_resource_id),
+ CONSTRAINT uq_camro_membership_resource UNIQUE (compute_allocation_membership_id, compute_allocation_resource_id),
CONSTRAINT fk_camro_membership FOREIGN KEY (compute_allocation_membership_id)
REFERENCES compute_allocation_memberships (id) ON DELETE CASCADE,
CONSTRAINT fk_camro_resource FOREIGN KEY (compute_allocation_resource_id)
REFERENCES compute_allocation_resources (id) ON DELETE CASCADE
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_camro_resource ON compute_allocation_membership_resource_overrides (compute_allocation_resource_id);
+
+CREATE OR REPLACE TRIGGER trg_compute_allocations_updated_at
+ BEFORE UPDATE ON compute_allocations
+ FOR EACH ROW EXECUTE FUNCTION set_updated_at();
+
+CREATE OR REPLACE TRIGGER trg_compute_allocation_resources_updated_at
+ BEFORE UPDATE ON compute_allocation_resources
+ FOR EACH ROW EXECUTE FUNCTION set_updated_at();
+
+CREATE OR REPLACE TRIGGER trg_compute_allocation_resource_rates_updated_at
+ BEFORE UPDATE ON compute_allocation_resource_rates
+ FOR EACH ROW EXECUTE FUNCTION set_updated_at();
+
+CREATE OR REPLACE TRIGGER trg_compute_allocation_membership_resource_overrides_updated_at
+ BEFORE UPDATE ON compute_allocation_membership_resource_overrides
+ FOR EACH ROW EXECUTE FUNCTION set_updated_at();
diff --git a/internal/db/migrations/000005_audit.up.sql b/internal/db/migrations/000005_audit.up.sql
index a214d120e..e315202b2 100644
--- a/internal/db/migrations/000005_audit.up.sql
+++ b/internal/db/migrations/000005_audit.up.sql
@@ -15,9 +15,6 @@
-- specific language governing permissions and limitations
-- under the License.
-SET NAMES utf8mb4;
-SET time_zone = '+00:00';
-
-- entity_type records the kind of resource the row is about ("user", "role",
-- "compute_cluster_user", "packet", etc.). source names the subsystem that
-- produced the event.
@@ -25,19 +22,19 @@ CREATE TABLE IF NOT EXISTS audit_events
(
id VARCHAR(255) NOT NULL,
event_type VARCHAR(128) NOT NULL,
- event_time TIMESTAMP(6) NOT NULL,
+ event_time TIMESTAMPTZ(6) NOT NULL,
entity_id VARCHAR(255) NOT NULL,
entity_type VARCHAR(64) NOT NULL DEFAULT '',
details TEXT NOT NULL,
source VARCHAR(64) NOT NULL DEFAULT 'core',
- trace_id CHAR(32) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '',
- span_id CHAR(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '',
- parent_span_id CHAR(16) CHARACTER SET ascii COLLATE ascii_bin NOT NULL DEFAULT '',
- PRIMARY KEY (id),
- KEY idx_audit_events_entity (entity_id, event_time),
- KEY idx_audit_events_entity_type (entity_type),
- KEY idx_audit_events_type (event_type, event_time),
- KEY idx_audit_events_time (event_time),
- KEY idx_audit_events_source (source),
- KEY idx_audit_events_trace (trace_id)
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+ trace_id CHAR(32) NOT NULL DEFAULT '',
+ span_id CHAR(16) NOT NULL DEFAULT '',
+ parent_span_id CHAR(16) NOT NULL DEFAULT '',
+ PRIMARY KEY (id)
+);
+CREATE INDEX IF NOT EXISTS idx_audit_events_entity ON audit_events (entity_id, event_time);
+CREATE INDEX IF NOT EXISTS idx_audit_events_entity_type ON audit_events (entity_type);
+CREATE INDEX IF NOT EXISTS idx_audit_events_type ON audit_events (event_type, event_time);
+CREATE INDEX IF NOT EXISTS idx_audit_events_time ON audit_events (event_time);
+CREATE INDEX IF NOT EXISTS idx_audit_events_source ON audit_events (source);
+CREATE INDEX IF NOT EXISTS idx_audit_events_trace ON audit_events (trace_id);
diff --git a/internal/db/migrations/000006_authorization.up.sql b/internal/db/migrations/000006_authorization.up.sql
index 0bed464b6..8ddf33c7b 100644
--- a/internal/db/migrations/000006_authorization.up.sql
+++ b/internal/db/migrations/000006_authorization.up.sql
@@ -15,9 +15,6 @@
-- specific language governing permissions and limitations
-- under the License.
-SET NAMES utf8mb4;
-SET time_zone = '+00:00';
-
-- Privileges held by a user. Only active grants live here; revoke is DELETE.
-- The full grant/revoke history (who, when, why) is recorded in audit_events.
CREATE TABLE IF NOT EXISTS user_privileges
@@ -26,15 +23,15 @@ CREATE TABLE IF NOT EXISTS user_privileges
user_id VARCHAR(255) NOT NULL,
privilege VARCHAR(64) NOT NULL,
granted_by VARCHAR(255) NULL,
- granted_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ granted_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
reason TEXT NULL,
PRIMARY KEY (id),
- UNIQUE KEY uq_user_privileges (user_id, privilege),
- KEY idx_user_privileges_user (user_id),
- KEY idx_user_privileges_priv (privilege),
+ CONSTRAINT uq_user_privileges UNIQUE (user_id, privilege),
CONSTRAINT fk_user_privileges_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
CONSTRAINT fk_user_privileges_granted_by FOREIGN KEY (granted_by) REFERENCES users (id) ON DELETE SET NULL
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_user_privileges_user ON user_privileges (user_id);
+CREATE INDEX IF NOT EXISTS idx_user_privileges_priv ON user_privileges (privilege);
-- Roles are named bundles of privileges. Granting a role to a user is
-- equivalent to granting each of the role's privileges.
@@ -43,11 +40,11 @@ CREATE TABLE IF NOT EXISTS roles
id VARCHAR(255) NOT NULL,
name VARCHAR(64) NOT NULL,
description TEXT NULL,
- is_system TINYINT(1) NOT NULL DEFAULT 0,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ is_system BOOLEAN NOT NULL DEFAULT FALSE,
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (id),
- UNIQUE KEY uq_roles_name (name)
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+ CONSTRAINT uq_roles_name UNIQUE (name)
+);
-- Privileges that a role bundles. Holding the role implies holding every
-- listed privilege.
@@ -55,10 +52,10 @@ CREATE TABLE IF NOT EXISTS role_privileges
(
role_id VARCHAR(255) NOT NULL,
privilege VARCHAR(64) NOT NULL,
- added_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ added_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
PRIMARY KEY (role_id, privilege),
CONSTRAINT fk_role_privileges_role FOREIGN KEY (role_id) REFERENCES roles (id) ON DELETE CASCADE
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
-- Active role assignments. Revoke is DELETE; full history lives in audit_events.
-- A user may hold any number of roles simultaneously.
@@ -67,11 +64,11 @@ CREATE TABLE IF NOT EXISTS user_roles
user_id VARCHAR(255) NOT NULL,
role_id VARCHAR(255) NOT NULL,
granted_by VARCHAR(255) NULL,
- granted_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ granted_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
reason TEXT NULL,
PRIMARY KEY (user_id, role_id),
- KEY idx_user_roles_role (role_id),
CONSTRAINT fk_user_roles_user FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE CASCADE,
CONSTRAINT fk_user_roles_role FOREIGN KEY (role_id) REFERENCES roles (id) ON DELETE CASCADE,
CONSTRAINT fk_user_roles_granted_by FOREIGN KEY (granted_by) REFERENCES users (id) ON DELETE SET NULL
-) ENGINE = InnoDB DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_unicode_ci;
+);
+CREATE INDEX IF NOT EXISTS idx_user_roles_role ON user_roles (role_id);
diff --git a/internal/server/integration_common_test.go b/internal/server/integration_common_test.go
index f1a6ef04e..cb287a7c7 100644
--- a/internal/server/integration_common_test.go
+++ b/internal/server/integration_common_test.go
@@ -22,6 +22,7 @@ package server
import (
"net/http"
"os"
+ "strings"
"sync"
"testing"
@@ -96,16 +97,8 @@ func truncateAll(t *testing.T, database *sqlx.DB) {
"users",
"organizations",
}
- if _, err := database.Exec("SET FOREIGN_KEY_CHECKS = 0"); err != nil {
- t.Fatalf("disable FK: %v", err)
- }
- for _, tbl := range tables {
- if _, err := database.Exec("TRUNCATE TABLE " + tbl); err != nil {
- t.Fatalf("truncate %s: %v", tbl, err)
- }
- }
- if _, err := database.Exec("SET FOREIGN_KEY_CHECKS = 1"); err != nil {
- t.Fatalf("re-enable FK: %v", err)
+ if _, err := database.Exec("TRUNCATE TABLE " + strings.Join(tables, ", ") + " CASCADE"); err != nil {
+ t.Fatalf("truncate: %v", err)
}
}
@@ -113,14 +106,14 @@ func seedUser(t *testing.T, database *sqlx.DB, email string) string {
t.Helper()
orgID := uuid.NewString()
if _, err := database.Exec(
- "INSERT INTO organizations (id, originated_id, name) VALUES (?, ?, ?)",
+ "INSERT INTO organizations (id, originated_id, name) VALUES ($1, $2, $3)",
orgID, "TEST-ORG-"+orgID[:8], "Test Org",
); err != nil {
t.Fatalf("seed org: %v", err)
}
userID := uuid.NewString()
if _, err := database.Exec(
- "INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status) VALUES (?, ?, ?, ?, ?, ?, ?)",
+ "INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status) VALUES ($1, $2, $3, $4, $5, $6, $7)",
userID, orgID, "Test", "User", "", email, string(models.UserActive),
); err != nil {
t.Fatalf("seed user %s: %v", email, err)
@@ -135,7 +128,7 @@ func seedPrivilegesGrant(t *testing.T, database *sqlx.DB, userID string) {
t.Helper()
if _, err := database.Exec(
`INSERT INTO user_privileges (id, user_id, privilege, granted_at, reason)
- VALUES (?, ?, ?, NOW(6), 'seed')`,
+ VALUES ($1, $2, $3, NOW(), 'seed')`,
uuid.NewString(), userID, string(models.PrivilegesGrant),
); err != nil {
t.Fatalf("seed privileges:grant for %s: %v", userID, err)
diff --git a/internal/server/privilege_integration_test.go b/internal/server/privilege_integration_test.go
index 4d225d124..59d9ccf51 100644
--- a/internal/server/privilege_integration_test.go
+++ b/internal/server/privilege_integration_test.go
@@ -124,19 +124,19 @@ func TestGetCallerProfile_IncludesOwnRoles(t *testing.T) {
database, _, srv := setupTestStack(t)
user := seedUser(t, database, "roleholder@example.edu")
if _, err := database.Exec(
- "INSERT INTO roles (id, name, description, is_system) VALUES (?, ?, ?, ?)",
+ "INSERT INTO roles (id, name, description, is_system) VALUES ($1, $2, $3, $4)",
"role-me-test", "Reviewer", "Reads reports", false,
); err != nil {
t.Fatalf("seed role: %v", err)
}
if _, err := database.Exec(
- "INSERT INTO user_roles (user_id, role_id) VALUES (?, ?)",
+ "INSERT INTO user_roles (user_id, role_id) VALUES ($1, $2)",
user, "role-me-test",
); err != nil {
t.Fatalf("seed grant: %v", err)
}
if _, err := database.Exec(
- "INSERT INTO role_privileges (role_id, privilege) VALUES (?, ?)",
+ "INSERT INTO role_privileges (role_id, privilege) VALUES ($1, $2)",
"role-me-test", "core:traces:read",
); err != nil {
t.Fatalf("seed role privilege: %v", err)
diff --git a/internal/store/audit_event_store.go b/internal/store/audit_event_store.go
index 8eb95a8a3..fb849dd87 100644
--- a/internal/store/audit_event_store.go
+++ b/internal/store/audit_event_store.go
@@ -29,19 +29,19 @@ import (
const auditEventColumns = "id, event_type, event_time, entity_id, entity_type, details, source, trace_id, span_id, parent_span_id"
-type mysqlAuditEventStore struct {
+type pgAuditEventStore struct {
db *sqlx.DB
}
-// NewAuditEventStore returns a MySQL-backed AuditEventStore.
+// NewAuditEventStore returns a PostgreSQL-backed AuditEventStore.
func NewAuditEventStore(db *sqlx.DB) AuditEventStore {
- return &mysqlAuditEventStore{db: db}
+ return &pgAuditEventStore{db: db}
}
-func (s *mysqlAuditEventStore) FindByID(ctx context.Context, id string) (*models.AuditEvent, error) {
+func (s *pgAuditEventStore) FindByID(ctx context.Context, id string) (*models.AuditEvent, error) {
var e models.AuditEvent
err := s.db.GetContext(ctx, &e,
- `SELECT `+auditEventColumns+` FROM audit_events WHERE id = ?`, id)
+ `SELECT `+auditEventColumns+` FROM audit_events WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -51,12 +51,12 @@ func (s *mysqlAuditEventStore) FindByID(ctx context.Context, id string) (*models
return &e, nil
}
-func (s *mysqlAuditEventStore) FindByEntity(ctx context.Context, entityID string) ([]models.AuditEvent, error) {
+func (s *pgAuditEventStore) FindByEntity(ctx context.Context, entityID string) ([]models.AuditEvent, error) {
var rows []models.AuditEvent
err := s.db.SelectContext(ctx, &rows,
`SELECT `+auditEventColumns+`
FROM audit_events
- WHERE entity_id = ?
+ WHERE entity_id = $1
ORDER BY event_time`, entityID)
if err != nil {
return nil, err
@@ -64,12 +64,12 @@ func (s *mysqlAuditEventStore) FindByEntity(ctx context.Context, entityID string
return rows, nil
}
-func (s *mysqlAuditEventStore) FindByEventType(ctx context.Context, eventType string) ([]models.AuditEvent, error) {
+func (s *pgAuditEventStore) FindByEventType(ctx context.Context, eventType string) ([]models.AuditEvent, error) {
var rows []models.AuditEvent
err := s.db.SelectContext(ctx, &rows,
`SELECT `+auditEventColumns+`
FROM audit_events
- WHERE event_type = ?
+ WHERE event_type = $1
ORDER BY event_time`, eventType)
if err != nil {
return nil, err
@@ -77,7 +77,7 @@ func (s *mysqlAuditEventStore) FindByEventType(ctx context.Context, eventType st
return rows, nil
}
-func (s *mysqlAuditEventStore) ListAll(ctx context.Context) ([]*models.AuditEvent, error) {
+func (s *pgAuditEventStore) ListAll(ctx context.Context) ([]*models.AuditEvent, error) {
var rows []*models.AuditEvent
err := s.db.SelectContext(ctx, &rows,
`SELECT `+auditEventColumns+`
@@ -89,15 +89,15 @@ func (s *mysqlAuditEventStore) ListAll(ctx context.Context) ([]*models.AuditEven
return rows, nil
}
-func (s *mysqlAuditEventStore) Create(ctx context.Context, tx *sql.Tx, e *models.AuditEvent) error {
+func (s *pgAuditEventStore) Create(ctx context.Context, tx *sql.Tx, e *models.AuditEvent) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO audit_events (id, event_type, event_time, entity_id, entity_type, details, source, trace_id, span_id, parent_span_id)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`,
e.ID, e.EventType, e.EventTime, e.EntityID, e.EntityType, e.Details, e.Source, e.TraceID, e.SpanID, e.ParentSpanID)
return err
}
-func (s *mysqlAuditEventStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM audit_events WHERE id = ?`, id)
+func (s *pgAuditEventStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM audit_events WHERE id = $1`, id)
return err
}
diff --git a/internal/store/audit_trace_store.go b/internal/store/audit_trace_store.go
index 4fafb0eca..6c0159a3f 100644
--- a/internal/store/audit_trace_store.go
+++ b/internal/store/audit_trace_store.go
@@ -69,12 +69,12 @@ FROM audit_events
WHERE trace_id <> ''
`
-type mysqlAuditTraceStore struct {
+type pgAuditTraceStore struct {
db *sqlx.DB
}
func NewAuditTraceStore(db *sqlx.DB) AuditTraceStore {
- return &mysqlAuditTraceStore{db: db}
+ return &pgAuditTraceStore{db: db}
}
type rowEvent struct {
@@ -105,7 +105,7 @@ func (r rowEvent) toTraceEvent() models.TraceEvent {
// ListTraces aggregates by trace_id then runs one summary probe per row.
// Acceptable while page sizes stay <= 200.
-func (s *mysqlAuditTraceStore) ListTraces(ctx context.Context, f TraceFilter) ([]models.TraceSummary, int, error) {
+func (s *pgAuditTraceStore) ListTraces(ctx context.Context, f TraceFilter) ([]models.TraceSummary, int, error) {
whereSQL, args := buildTraceWhere(f)
limit := f.Limit
@@ -118,7 +118,7 @@ func (s *mysqlAuditTraceStore) ListTraces(ctx context.Context, f TraceFilter) ([
GROUP BY trace_id
) g`
var total int
- if err := s.db.GetContext(ctx, &total, countQuery, args...); err != nil {
+ if err := s.db.GetContext(ctx, &total, s.db.Rebind(countQuery), args...); err != nil {
return nil, 0, fmt.Errorf("audit_trace_store: count: %w", err)
}
@@ -140,7 +140,7 @@ func (s *mysqlAuditTraceStore) ListTraces(ctx context.Context, f TraceFilter) ([
EventCount int `db:"event_count"`
}
var raw []listRow
- if err := s.db.SelectContext(ctx, &raw, listQuery, listArgs...); err != nil {
+ if err := s.db.SelectContext(ctx, &raw, s.db.Rebind(listQuery), listArgs...); err != nil {
return nil, 0, fmt.Errorf("audit_trace_store: list: %w", err)
}
@@ -166,13 +166,13 @@ func (s *mysqlAuditTraceStore) ListTraces(ctx context.Context, f TraceFilter) ([
return out, total, nil
}
-func (s *mysqlAuditTraceStore) summariseTrace(ctx context.Context, traceID string) (string, string, string, error) {
+func (s *pgAuditTraceStore) summariseTrace(ctx context.Context, traceID string) (string, string, string, error) {
q := `SELECT trace_id, span_id, parent_span_id, source, event_type, entity_type, entity_id, description, created_at
FROM (` + auditTraceSelect + `) u
WHERE trace_id = ?
ORDER BY created_at ASC, span_id ASC`
var rows []rowEvent
- if err := s.db.SelectContext(ctx, &rows, q, traceID); err != nil {
+ if err := s.db.SelectContext(ctx, &rows, s.db.Rebind(q), traceID); err != nil {
return "", "", "", fmt.Errorf("audit_trace_store: summarise: %w", err)
}
if len(rows) == 0 {
@@ -207,14 +207,14 @@ func dominantSource(rows []rowEvent) string {
return rows[0].Source
}
-func (s *mysqlAuditTraceStore) GetTraceTree(ctx context.Context, traceID string) (*models.TraceNode, bool, error) {
+func (s *pgAuditTraceStore) GetTraceTree(ctx context.Context, traceID string) (*models.TraceNode, bool, error) {
q := `SELECT trace_id, span_id, parent_span_id, source, event_type, entity_type, entity_id, description, created_at
FROM (` + auditTraceSelect + `) u
WHERE trace_id = ?
ORDER BY created_at ASC, span_id ASC
LIMIT ?`
var rows []rowEvent
- if err := s.db.SelectContext(ctx, &rows, q, traceID, TreeRowLimit+1); err != nil {
+ if err := s.db.SelectContext(ctx, &rows, s.db.Rebind(q), traceID, TreeRowLimit+1); err != nil {
return nil, false, fmt.Errorf("audit_trace_store: tree: %w", err)
}
if len(rows) == 0 {
@@ -249,7 +249,7 @@ func buildTree(rows []rowEvent) *models.TraceNode {
return root
}
-func (s *mysqlAuditTraceStore) ListEvents(ctx context.Context, traceID, spanID string) ([]models.TraceEvent, error) {
+func (s *pgAuditTraceStore) ListEvents(ctx context.Context, traceID, spanID string) ([]models.TraceEvent, error) {
q := `SELECT trace_id, span_id, parent_span_id, source, event_type, entity_type, entity_id, description, created_at
FROM (` + auditTraceSelect + `) u
WHERE trace_id = ?`
@@ -261,7 +261,7 @@ func (s *mysqlAuditTraceStore) ListEvents(ctx context.Context, traceID, spanID s
q += ` ORDER BY created_at ASC, span_id ASC`
var rows []rowEvent
- if err := s.db.SelectContext(ctx, &rows, q, args...); err != nil {
+ if err := s.db.SelectContext(ctx, &rows, s.db.Rebind(q), args...); err != nil {
return nil, fmt.Errorf("audit_trace_store: events: %w", err)
}
out := make([]models.TraceEvent, len(rows))
@@ -271,7 +271,7 @@ func (s *mysqlAuditTraceStore) ListEvents(ctx context.Context, traceID, spanID s
return out, nil
}
-func (s *mysqlAuditTraceStore) ListSources(_ context.Context) ([]string, error) {
+func (s *pgAuditTraceStore) ListSources(_ context.Context) ([]string, error) {
return []string{"amie", "comanage", "core", "slurm"}, nil
}
@@ -296,7 +296,7 @@ func buildTraceWhere(f TraceFilter) (string, []any) {
args = append(args, f.To)
}
if f.Q != "" {
- clauses = append(clauses, "(u.trace_id LIKE ? OR u.event_type LIKE ?)")
+ clauses = append(clauses, "(u.trace_id ILIKE ? OR u.event_type ILIKE ?)")
args = append(args, strings.ToLower(f.Q)+"%", "%"+f.Q+"%")
}
diff --git a/internal/store/audit_trace_store_test.go b/internal/store/audit_trace_store_test.go
index 770361844..fb753c0dd 100644
--- a/internal/store/audit_trace_store_test.go
+++ b/internal/store/audit_trace_store_test.go
@@ -110,8 +110,8 @@ func TestBuildTraceWhereTimeAndQ(t *testing.T) {
if !strings.Contains(w, "u.created_at >= ?") || !strings.Contains(w, "u.created_at <= ?") {
t.Errorf("where missing time clauses: %q", w)
}
- if !strings.Contains(w, "u.trace_id LIKE ?") {
- t.Errorf("where missing trace_id LIKE: %q", w)
+ if !strings.Contains(w, "u.trace_id ILIKE ?") {
+ t.Errorf("where missing trace_id ILIKE: %q", w)
}
// 2 timestamps + 2 LIKE values
if len(args) != 4 {
diff --git a/internal/store/compute_allocation_change_request_event_store.go b/internal/store/compute_allocation_change_request_event_store.go
index 087a696d5..ad66a34da 100644
--- a/internal/store/compute_allocation_change_request_event_store.go
+++ b/internal/store/compute_allocation_change_request_event_store.go
@@ -29,20 +29,20 @@ import (
const computeAllocationChangeRequestEventColumns = "id, compute_allocation_change_request_id, event_type, description, timestamp"
-type mysqlComputeAllocationChangeRequestEventStore struct {
+type pgComputeAllocationChangeRequestEventStore struct {
db *sqlx.DB
}
-// NewComputeAllocationChangeRequestEventStore returns a MySQL-backed
+// NewComputeAllocationChangeRequestEventStore returns a PostgreSQL-backed
// ComputeAllocationChangeRequestEventStore.
func NewComputeAllocationChangeRequestEventStore(db *sqlx.DB) ComputeAllocationChangeRequestEventStore {
- return &mysqlComputeAllocationChangeRequestEventStore{db: db}
+ return &pgComputeAllocationChangeRequestEventStore{db: db}
}
-func (s *mysqlComputeAllocationChangeRequestEventStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationChangeRequestEvent, error) {
+func (s *pgComputeAllocationChangeRequestEventStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationChangeRequestEvent, error) {
var e models.ComputeAllocationChangeRequestEvent
err := s.db.GetContext(ctx, &e,
- `SELECT `+computeAllocationChangeRequestEventColumns+` FROM compute_allocation_change_request_events WHERE id = ?`, id)
+ `SELECT `+computeAllocationChangeRequestEventColumns+` FROM compute_allocation_change_request_events WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -52,12 +52,12 @@ func (s *mysqlComputeAllocationChangeRequestEventStore) FindByID(ctx context.Con
return &e, nil
}
-func (s *mysqlComputeAllocationChangeRequestEventStore) FindByChangeRequest(ctx context.Context, changeRequestID string) ([]models.ComputeAllocationChangeRequestEvent, error) {
+func (s *pgComputeAllocationChangeRequestEventStore) FindByChangeRequest(ctx context.Context, changeRequestID string) ([]models.ComputeAllocationChangeRequestEvent, error) {
var rows []models.ComputeAllocationChangeRequestEvent
err := s.db.SelectContext(ctx, &rows,
`SELECT `+computeAllocationChangeRequestEventColumns+`
FROM compute_allocation_change_request_events
- WHERE compute_allocation_change_request_id = ?
+ WHERE compute_allocation_change_request_id = $1
ORDER BY timestamp`, changeRequestID)
if err != nil {
return nil, err
@@ -65,12 +65,12 @@ func (s *mysqlComputeAllocationChangeRequestEventStore) FindByChangeRequest(ctx
return rows, nil
}
-func (s *mysqlComputeAllocationChangeRequestEventStore) FindLatestByChangeRequest(ctx context.Context, changeRequestID string) (*models.ComputeAllocationChangeRequestEvent, error) {
+func (s *pgComputeAllocationChangeRequestEventStore) FindLatestByChangeRequest(ctx context.Context, changeRequestID string) (*models.ComputeAllocationChangeRequestEvent, error) {
var e models.ComputeAllocationChangeRequestEvent
err := s.db.GetContext(ctx, &e,
`SELECT `+computeAllocationChangeRequestEventColumns+`
FROM compute_allocation_change_request_events
- WHERE compute_allocation_change_request_id = ?
+ WHERE compute_allocation_change_request_id = $1
ORDER BY timestamp DESC
LIMIT 1`, changeRequestID)
if err != nil {
@@ -82,16 +82,16 @@ func (s *mysqlComputeAllocationChangeRequestEventStore) FindLatestByChangeReques
return &e, nil
}
-func (s *mysqlComputeAllocationChangeRequestEventStore) Create(ctx context.Context, tx *sql.Tx, e *models.ComputeAllocationChangeRequestEvent) error {
+func (s *pgComputeAllocationChangeRequestEventStore) Create(ctx context.Context, tx *sql.Tx, e *models.ComputeAllocationChangeRequestEvent) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO compute_allocation_change_request_events
(id, compute_allocation_change_request_id, event_type, description, timestamp)
- VALUES (?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5)`,
e.ID, e.ComputeAllocationChangeRequestID, e.EventType, e.Description, e.Timestamp)
return err
}
-func (s *mysqlComputeAllocationChangeRequestEventStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_change_request_events WHERE id = ?`, id)
+func (s *pgComputeAllocationChangeRequestEventStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_change_request_events WHERE id = $1`, id)
return err
}
diff --git a/internal/store/compute_allocation_change_request_store.go b/internal/store/compute_allocation_change_request_store.go
index bd2010778..d8cbbe876 100644
--- a/internal/store/compute_allocation_change_request_store.go
+++ b/internal/store/compute_allocation_change_request_store.go
@@ -29,20 +29,20 @@ import (
const computeAllocationChangeRequestColumns = "id, compute_allocation_id, requested_su_amount, requested_status, reason, change_status, requester_id, approver_id, timestamp"
-type mysqlComputeAllocationChangeRequestStore struct {
+type pgComputeAllocationChangeRequestStore struct {
db *sqlx.DB
}
-// NewComputeAllocationChangeRequestStore returns a MySQL-backed
+// NewComputeAllocationChangeRequestStore returns a PostgreSQL-backed
// ComputeAllocationChangeRequestStore.
func NewComputeAllocationChangeRequestStore(db *sqlx.DB) ComputeAllocationChangeRequestStore {
- return &mysqlComputeAllocationChangeRequestStore{db: db}
+ return &pgComputeAllocationChangeRequestStore{db: db}
}
-func (s *mysqlComputeAllocationChangeRequestStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationChangeRequest, error) {
+func (s *pgComputeAllocationChangeRequestStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationChangeRequest, error) {
var c models.ComputeAllocationChangeRequest
err := s.db.GetContext(ctx, &c,
- `SELECT `+computeAllocationChangeRequestColumns+` FROM compute_allocation_change_requests WHERE id = ?`, id)
+ `SELECT `+computeAllocationChangeRequestColumns+` FROM compute_allocation_change_requests WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -52,12 +52,12 @@ func (s *mysqlComputeAllocationChangeRequestStore) FindByID(ctx context.Context,
return &c, nil
}
-func (s *mysqlComputeAllocationChangeRequestStore) FindByAllocation(ctx context.Context, allocationID string) ([]models.ComputeAllocationChangeRequest, error) {
+func (s *pgComputeAllocationChangeRequestStore) FindByAllocation(ctx context.Context, allocationID string) ([]models.ComputeAllocationChangeRequest, error) {
var rows []models.ComputeAllocationChangeRequest
err := s.db.SelectContext(ctx, &rows,
`SELECT `+computeAllocationChangeRequestColumns+`
FROM compute_allocation_change_requests
- WHERE compute_allocation_id = ?
+ WHERE compute_allocation_id = $1
ORDER BY timestamp`, allocationID)
if err != nil {
return nil, err
@@ -65,12 +65,12 @@ func (s *mysqlComputeAllocationChangeRequestStore) FindByAllocation(ctx context.
return rows, nil
}
-func (s *mysqlComputeAllocationChangeRequestStore) FindByRequester(ctx context.Context, requesterID string) ([]models.ComputeAllocationChangeRequest, error) {
+func (s *pgComputeAllocationChangeRequestStore) FindByRequester(ctx context.Context, requesterID string) ([]models.ComputeAllocationChangeRequest, error) {
var rows []models.ComputeAllocationChangeRequest
err := s.db.SelectContext(ctx, &rows,
`SELECT `+computeAllocationChangeRequestColumns+`
FROM compute_allocation_change_requests
- WHERE requester_id = ?
+ WHERE requester_id = $1
ORDER BY timestamp`, requesterID)
if err != nil {
return nil, err
@@ -78,37 +78,37 @@ func (s *mysqlComputeAllocationChangeRequestStore) FindByRequester(ctx context.C
return rows, nil
}
-func (s *mysqlComputeAllocationChangeRequestStore) Create(ctx context.Context, tx *sql.Tx, c *models.ComputeAllocationChangeRequest) error {
+func (s *pgComputeAllocationChangeRequestStore) Create(ctx context.Context, tx *sql.Tx, c *models.ComputeAllocationChangeRequest) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO compute_allocation_change_requests
(id, compute_allocation_id, requested_su_amount, requested_status, reason, change_status, requester_id, approver_id, timestamp)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
c.ID, c.ComputeAllocationID, c.RequestedSUAmount, string(c.RequestedStatus), c.Reason, c.ChangeStatus, c.RequesterID, c.ApproverID, c.Timestamp)
return err
}
-func (s *mysqlComputeAllocationChangeRequestStore) Update(ctx context.Context, tx *sql.Tx, c *models.ComputeAllocationChangeRequest) error {
+func (s *pgComputeAllocationChangeRequestStore) Update(ctx context.Context, tx *sql.Tx, c *models.ComputeAllocationChangeRequest) error {
_, err := tx.ExecContext(ctx,
`UPDATE compute_allocation_change_requests
- SET compute_allocation_id = ?,
- requested_su_amount = ?,
- requested_status = ?,
- reason = ?,
- change_status = ?,
- requester_id = ?,
- approver_id = ?,
- timestamp = ?
- WHERE id = ?`,
+ SET compute_allocation_id = $1,
+ requested_su_amount = $2,
+ requested_status = $3,
+ reason = $4,
+ change_status = $5,
+ requester_id = $6,
+ approver_id = $7,
+ timestamp = $8
+ WHERE id = $9`,
c.ComputeAllocationID, c.RequestedSUAmount, string(c.RequestedStatus), c.Reason, c.ChangeStatus, c.RequesterID, c.ApproverID, c.Timestamp, c.ID)
return err
}
-func (s *mysqlComputeAllocationChangeRequestStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_change_requests WHERE id = ?`, id)
+func (s *pgComputeAllocationChangeRequestStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_change_requests WHERE id = $1`, id)
return err
}
-func (s *mysqlComputeAllocationChangeRequestStore) List(ctx context.Context, f ChangeRequestListFilter) ([]models.ComputeAllocationChangeRequest, error) {
+func (s *pgComputeAllocationChangeRequestStore) List(ctx context.Context, f ChangeRequestListFilter) ([]models.ComputeAllocationChangeRequest, error) {
limit := f.Limit
if limit <= 0 {
limit = 50
@@ -120,14 +120,14 @@ func (s *mysqlComputeAllocationChangeRequestStore) List(ctx context.Context, f C
var rows []models.ComputeAllocationChangeRequest
err := s.db.SelectContext(ctx, &rows,
`SELECT `+computeAllocationChangeRequestColumns+
- ` FROM compute_allocation_change_requests WHERE change_status = ?`+
- ` ORDER BY timestamp DESC LIMIT ?`, f.Status, limit)
+ ` FROM compute_allocation_change_requests WHERE change_status = $1`+
+ ` ORDER BY timestamp DESC LIMIT $2`, f.Status, limit)
return rows, err
}
var rows []models.ComputeAllocationChangeRequest
err := s.db.SelectContext(ctx, &rows,
`SELECT `+computeAllocationChangeRequestColumns+
` FROM compute_allocation_change_requests`+
- ` ORDER BY timestamp DESC LIMIT ?`, limit)
+ ` ORDER BY timestamp DESC LIMIT $1`, limit)
return rows, err
}
diff --git a/internal/store/compute_allocation_diff_store.go b/internal/store/compute_allocation_diff_store.go
index f4fd0f10a..960a03e15 100644
--- a/internal/store/compute_allocation_diff_store.go
+++ b/internal/store/compute_allocation_diff_store.go
@@ -29,20 +29,20 @@ import (
const computeAllocationDiffColumns = "id, compute_allocation_id, diff_type, new_su_amount, status, timestamp, description"
-type mysqlComputeAllocationDiffStore struct {
+type pgComputeAllocationDiffStore struct {
db *sqlx.DB
}
-// NewComputeAllocationDiffStore returns a MySQL-backed
+// NewComputeAllocationDiffStore returns a PostgreSQL-backed
// ComputeAllocationDiffStore.
func NewComputeAllocationDiffStore(db *sqlx.DB) ComputeAllocationDiffStore {
- return &mysqlComputeAllocationDiffStore{db: db}
+ return &pgComputeAllocationDiffStore{db: db}
}
-func (s *mysqlComputeAllocationDiffStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationDiff, error) {
+func (s *pgComputeAllocationDiffStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationDiff, error) {
var d models.ComputeAllocationDiff
err := s.db.GetContext(ctx, &d,
- `SELECT `+computeAllocationDiffColumns+` FROM compute_allocation_diffs WHERE id = ?`, id)
+ `SELECT `+computeAllocationDiffColumns+` FROM compute_allocation_diffs WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -52,12 +52,12 @@ func (s *mysqlComputeAllocationDiffStore) FindByID(ctx context.Context, id strin
return &d, nil
}
-func (s *mysqlComputeAllocationDiffStore) FindByAllocation(ctx context.Context, allocationID string) ([]models.ComputeAllocationDiff, error) {
+func (s *pgComputeAllocationDiffStore) FindByAllocation(ctx context.Context, allocationID string) ([]models.ComputeAllocationDiff, error) {
var diffs []models.ComputeAllocationDiff
err := s.db.SelectContext(ctx, &diffs,
`SELECT `+computeAllocationDiffColumns+`
FROM compute_allocation_diffs
- WHERE compute_allocation_id = ?
+ WHERE compute_allocation_id = $1
ORDER BY timestamp`, allocationID)
if err != nil {
return nil, err
@@ -65,12 +65,12 @@ func (s *mysqlComputeAllocationDiffStore) FindByAllocation(ctx context.Context,
return diffs, nil
}
-func (s *mysqlComputeAllocationDiffStore) FindLatestByAllocation(ctx context.Context, allocationID string) (*models.ComputeAllocationDiff, error) {
+func (s *pgComputeAllocationDiffStore) FindLatestByAllocation(ctx context.Context, allocationID string) (*models.ComputeAllocationDiff, error) {
var d models.ComputeAllocationDiff
err := s.db.GetContext(ctx, &d,
`SELECT `+computeAllocationDiffColumns+`
FROM compute_allocation_diffs
- WHERE compute_allocation_id = ?
+ WHERE compute_allocation_id = $1
ORDER BY timestamp DESC
LIMIT 1`, allocationID)
if err != nil {
@@ -82,16 +82,16 @@ func (s *mysqlComputeAllocationDiffStore) FindLatestByAllocation(ctx context.Con
return &d, nil
}
-func (s *mysqlComputeAllocationDiffStore) Create(ctx context.Context, tx *sql.Tx, d *models.ComputeAllocationDiff) error {
+func (s *pgComputeAllocationDiffStore) Create(ctx context.Context, tx *sql.Tx, d *models.ComputeAllocationDiff) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO compute_allocation_diffs
(id, compute_allocation_id, diff_type, new_su_amount, status, timestamp, description)
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
d.ID, d.ComputeAllocationID, d.DiffType, d.NewSUAmount, string(d.Status), d.Timestamp, d.Description)
return err
}
-func (s *mysqlComputeAllocationDiffStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_diffs WHERE id = ?`, id)
+func (s *pgComputeAllocationDiffStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_diffs WHERE id = $1`, id)
return err
}
diff --git a/internal/store/compute_allocation_membership_resource_override_store.go b/internal/store/compute_allocation_membership_resource_override_store.go
index d89f11e9c..c53edd5ad 100644
--- a/internal/store/compute_allocation_membership_resource_override_store.go
+++ b/internal/store/compute_allocation_membership_resource_override_store.go
@@ -29,22 +29,22 @@ import (
const computeAllocationMembershipResourceOverrideColumns = "id, compute_allocation_membership_id, compute_allocation_resource_id, override_resource_amount, override_resource_time"
-type mysqlComputeAllocationMembershipResourceOverrideStore struct {
+type pgComputeAllocationMembershipResourceOverrideStore struct {
db *sqlx.DB
}
-// NewComputeAllocationMembershipResourceOverrideStore returns a MySQL-backed
+// NewComputeAllocationMembershipResourceOverrideStore returns a PostgreSQL-backed
// ComputeAllocationMembershipResourceOverrideStore.
func NewComputeAllocationMembershipResourceOverrideStore(db *sqlx.DB) ComputeAllocationMembershipResourceOverrideStore {
- return &mysqlComputeAllocationMembershipResourceOverrideStore{db: db}
+ return &pgComputeAllocationMembershipResourceOverrideStore{db: db}
}
-func (s *mysqlComputeAllocationMembershipResourceOverrideStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationMembershipResourceOverride, error) {
+func (s *pgComputeAllocationMembershipResourceOverrideStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationMembershipResourceOverride, error) {
var o models.ComputeAllocationMembershipResourceOverride
err := s.db.GetContext(ctx, &o,
`SELECT `+computeAllocationMembershipResourceOverrideColumns+`
FROM compute_allocation_membership_resource_overrides
- WHERE id = ?`, id)
+ WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -54,12 +54,12 @@ func (s *mysqlComputeAllocationMembershipResourceOverrideStore) FindByID(ctx con
return &o, nil
}
-func (s *mysqlComputeAllocationMembershipResourceOverrideStore) FindByPair(ctx context.Context, membershipID, resourceID string) (*models.ComputeAllocationMembershipResourceOverride, error) {
+func (s *pgComputeAllocationMembershipResourceOverrideStore) FindByPair(ctx context.Context, membershipID, resourceID string) (*models.ComputeAllocationMembershipResourceOverride, error) {
var o models.ComputeAllocationMembershipResourceOverride
err := s.db.GetContext(ctx, &o,
`SELECT `+computeAllocationMembershipResourceOverrideColumns+`
FROM compute_allocation_membership_resource_overrides
- WHERE compute_allocation_membership_id = ? AND compute_allocation_resource_id = ?`,
+ WHERE compute_allocation_membership_id = $1 AND compute_allocation_resource_id = $2`,
membershipID, resourceID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
@@ -70,12 +70,12 @@ func (s *mysqlComputeAllocationMembershipResourceOverrideStore) FindByPair(ctx c
return &o, nil
}
-func (s *mysqlComputeAllocationMembershipResourceOverrideStore) FindByMembership(ctx context.Context, membershipID string) ([]models.ComputeAllocationMembershipResourceOverride, error) {
+func (s *pgComputeAllocationMembershipResourceOverrideStore) FindByMembership(ctx context.Context, membershipID string) ([]models.ComputeAllocationMembershipResourceOverride, error) {
var rows []models.ComputeAllocationMembershipResourceOverride
err := s.db.SelectContext(ctx, &rows,
`SELECT `+computeAllocationMembershipResourceOverrideColumns+`
FROM compute_allocation_membership_resource_overrides
- WHERE compute_allocation_membership_id = ?
+ WHERE compute_allocation_membership_id = $1
ORDER BY compute_allocation_resource_id`, membershipID)
if err != nil {
return nil, err
@@ -83,12 +83,12 @@ func (s *mysqlComputeAllocationMembershipResourceOverrideStore) FindByMembership
return rows, nil
}
-func (s *mysqlComputeAllocationMembershipResourceOverrideStore) FindByResource(ctx context.Context, resourceID string) ([]models.ComputeAllocationMembershipResourceOverride, error) {
+func (s *pgComputeAllocationMembershipResourceOverrideStore) FindByResource(ctx context.Context, resourceID string) ([]models.ComputeAllocationMembershipResourceOverride, error) {
var rows []models.ComputeAllocationMembershipResourceOverride
err := s.db.SelectContext(ctx, &rows,
`SELECT `+computeAllocationMembershipResourceOverrideColumns+`
FROM compute_allocation_membership_resource_overrides
- WHERE compute_allocation_resource_id = ?
+ WHERE compute_allocation_resource_id = $1
ORDER BY compute_allocation_membership_id`, resourceID)
if err != nil {
return nil, err
@@ -96,29 +96,29 @@ func (s *mysqlComputeAllocationMembershipResourceOverrideStore) FindByResource(c
return rows, nil
}
-func (s *mysqlComputeAllocationMembershipResourceOverrideStore) Create(ctx context.Context, tx *sql.Tx, o *models.ComputeAllocationMembershipResourceOverride) error {
+func (s *pgComputeAllocationMembershipResourceOverrideStore) Create(ctx context.Context, tx *sql.Tx, o *models.ComputeAllocationMembershipResourceOverride) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO compute_allocation_membership_resource_overrides
(id, compute_allocation_membership_id, compute_allocation_resource_id, override_resource_amount, override_resource_time)
- VALUES (?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5)`,
o.ID, o.ComputeAllocationMembershipID, o.ComputeAllocationResourceID, o.OverrideResourceAmount, o.OverrideResourceTime)
return err
}
-func (s *mysqlComputeAllocationMembershipResourceOverrideStore) Update(ctx context.Context, tx *sql.Tx, o *models.ComputeAllocationMembershipResourceOverride) error {
+func (s *pgComputeAllocationMembershipResourceOverrideStore) Update(ctx context.Context, tx *sql.Tx, o *models.ComputeAllocationMembershipResourceOverride) error {
_, err := tx.ExecContext(ctx,
`UPDATE compute_allocation_membership_resource_overrides
- SET compute_allocation_membership_id = ?,
- compute_allocation_resource_id = ?,
- override_resource_amount = ?,
- override_resource_time = ?
- WHERE id = ?`,
+ SET compute_allocation_membership_id = $1,
+ compute_allocation_resource_id = $2,
+ override_resource_amount = $3,
+ override_resource_time = $4
+ WHERE id = $5`,
o.ComputeAllocationMembershipID, o.ComputeAllocationResourceID, o.OverrideResourceAmount, o.OverrideResourceTime, o.ID)
return err
}
-func (s *mysqlComputeAllocationMembershipResourceOverrideStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+func (s *pgComputeAllocationMembershipResourceOverrideStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
_, err := tx.ExecContext(ctx,
- `DELETE FROM compute_allocation_membership_resource_overrides WHERE id = ?`, id)
+ `DELETE FROM compute_allocation_membership_resource_overrides WHERE id = $1`, id)
return err
}
diff --git a/internal/store/compute_allocation_membership_store.go b/internal/store/compute_allocation_membership_store.go
index 53b188834..dbb7344ca 100644
--- a/internal/store/compute_allocation_membership_store.go
+++ b/internal/store/compute_allocation_membership_store.go
@@ -41,20 +41,20 @@ type MembershipWithUser struct {
AllocationName string
}
-type mysqlComputeAllocationMembershipStore struct {
+type pgComputeAllocationMembershipStore struct {
db *sqlx.DB
}
-// NewComputeAllocationMembershipStore returns a MySQL-backed
+// NewComputeAllocationMembershipStore returns a PostgreSQL-backed
// ComputeAllocationMembershipStore.
func NewComputeAllocationMembershipStore(db *sqlx.DB) ComputeAllocationMembershipStore {
- return &mysqlComputeAllocationMembershipStore{db: db}
+ return &pgComputeAllocationMembershipStore{db: db}
}
-func (s *mysqlComputeAllocationMembershipStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationMembership, error) {
+func (s *pgComputeAllocationMembershipStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationMembership, error) {
var m models.ComputeAllocationMembership
err := s.db.GetContext(ctx, &m,
- `SELECT `+computeAllocationMembershipColumns+` FROM compute_allocation_memberships WHERE id = ?`, id)
+ `SELECT `+computeAllocationMembershipColumns+` FROM compute_allocation_memberships WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -64,12 +64,12 @@ func (s *mysqlComputeAllocationMembershipStore) FindByID(ctx context.Context, id
return &m, nil
}
-func (s *mysqlComputeAllocationMembershipStore) FindByPair(ctx context.Context, allocationID, userID string) (*models.ComputeAllocationMembership, error) {
+func (s *pgComputeAllocationMembershipStore) FindByPair(ctx context.Context, allocationID, userID string) (*models.ComputeAllocationMembership, error) {
var m models.ComputeAllocationMembership
err := s.db.GetContext(ctx, &m,
`SELECT `+computeAllocationMembershipColumns+`
FROM compute_allocation_memberships
- WHERE compute_allocation_id = ? AND user_id = ?`, allocationID, userID)
+ WHERE compute_allocation_id = $1 AND user_id = $2`, allocationID, userID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -79,12 +79,12 @@ func (s *mysqlComputeAllocationMembershipStore) FindByPair(ctx context.Context,
return &m, nil
}
-func (s *mysqlComputeAllocationMembershipStore) FindByAllocation(ctx context.Context, allocationID string) ([]models.ComputeAllocationMembership, error) {
+func (s *pgComputeAllocationMembershipStore) FindByAllocation(ctx context.Context, allocationID string) ([]models.ComputeAllocationMembership, error) {
var rows []models.ComputeAllocationMembership
err := s.db.SelectContext(ctx, &rows,
`SELECT `+computeAllocationMembershipColumns+`
FROM compute_allocation_memberships
- WHERE compute_allocation_id = ?
+ WHERE compute_allocation_id = $1
ORDER BY start_time`, allocationID)
if err != nil {
return nil, err
@@ -92,12 +92,12 @@ func (s *mysqlComputeAllocationMembershipStore) FindByAllocation(ctx context.Con
return rows, nil
}
-func (s *mysqlComputeAllocationMembershipStore) FindByUser(ctx context.Context, userID string) ([]models.ComputeAllocationMembership, error) {
+func (s *pgComputeAllocationMembershipStore) FindByUser(ctx context.Context, userID string) ([]models.ComputeAllocationMembership, error) {
var rows []models.ComputeAllocationMembership
err := s.db.SelectContext(ctx, &rows,
`SELECT `+computeAllocationMembershipColumns+`
FROM compute_allocation_memberships
- WHERE user_id = ?
+ WHERE user_id = $1
ORDER BY start_time`, userID)
if err != nil {
return nil, err
@@ -105,48 +105,48 @@ func (s *mysqlComputeAllocationMembershipStore) FindByUser(ctx context.Context,
return rows, nil
}
-func (s *mysqlComputeAllocationMembershipStore) Create(ctx context.Context, tx *sql.Tx, m *models.ComputeAllocationMembership) error {
+func (s *pgComputeAllocationMembershipStore) Create(ctx context.Context, tx *sql.Tx, m *models.ComputeAllocationMembership) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO compute_allocation_memberships
(id, compute_allocation_id, user_id, start_time, end_time, membership_status)
- VALUES (?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6)`,
m.ID, m.ComputeAllocationID, m.UserID, m.StartTime, m.EndTime, string(m.MembershipStatus))
return err
}
-func (s *mysqlComputeAllocationMembershipStore) Update(ctx context.Context, tx *sql.Tx, m *models.ComputeAllocationMembership) error {
+func (s *pgComputeAllocationMembershipStore) Update(ctx context.Context, tx *sql.Tx, m *models.ComputeAllocationMembership) error {
_, err := tx.ExecContext(ctx,
`UPDATE compute_allocation_memberships
- SET compute_allocation_id = ?,
- user_id = ?,
- start_time = ?,
- end_time = ?,
- membership_status = ?
- WHERE id = ?`,
+ SET compute_allocation_id = $1,
+ user_id = $2,
+ start_time = $3,
+ end_time = $4,
+ membership_status = $5
+ WHERE id = $6`,
m.ComputeAllocationID, m.UserID, m.StartTime, m.EndTime, string(m.MembershipStatus), m.ID)
return err
}
-func (s *mysqlComputeAllocationMembershipStore) ReassignUser(ctx context.Context, tx *sql.Tx, fromUserID, toUserID string) error {
+func (s *pgComputeAllocationMembershipStore) ReassignUser(ctx context.Context, tx *sql.Tx, fromUserID, toUserID string) error {
if _, err := tx.ExecContext(ctx,
`DELETE FROM compute_allocation_memberships
- WHERE user_id = ?
+ WHERE user_id = $1
AND compute_allocation_id IN (
SELECT compute_allocation_id FROM (
- SELECT compute_allocation_id FROM compute_allocation_memberships WHERE user_id = ?
+ SELECT compute_allocation_id FROM compute_allocation_memberships WHERE user_id = $2
) AS s
)`,
fromUserID, toUserID); err != nil {
return err
}
_, err := tx.ExecContext(ctx,
- `UPDATE compute_allocation_memberships SET user_id = ? WHERE user_id = ?`,
+ `UPDATE compute_allocation_memberships SET user_id = $1 WHERE user_id = $2`,
toUserID, fromUserID)
return err
}
-func (s *mysqlComputeAllocationMembershipStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_memberships WHERE id = ?`, id)
+func (s *pgComputeAllocationMembershipStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_memberships WHERE id = $1`, id)
return err
}
@@ -154,7 +154,7 @@ func (s *mysqlComputeAllocationMembershipStore) Delete(ctx context.Context, tx *
// users + project_memberships so each row carries display_name, email, and
// the project-level role (defaulted to MEMBER when no project_memberships
// row exists).
-func (s *mysqlComputeAllocationMembershipStore) FindByAllocationWithUser(ctx context.Context, allocationID string) ([]MembershipWithUser, error) {
+func (s *pgComputeAllocationMembershipStore) FindByAllocationWithUser(ctx context.Context, allocationID string) ([]MembershipWithUser, error) {
type row struct {
models.ComputeAllocationMembership
Role string `db:"role"`
@@ -173,7 +173,7 @@ func (s *mysqlComputeAllocationMembershipStore) FindByAllocationWithUser(ctx con
JOIN users u ON u.id = m.user_id
LEFT JOIN project_memberships pm
ON pm.project_id = a.project_id AND pm.user_id = m.user_id
- WHERE m.compute_allocation_id = ?
+ WHERE m.compute_allocation_id = $1
ORDER BY m.start_time`, allocationID)
if err != nil {
return nil, err
@@ -194,7 +194,7 @@ func (s *mysqlComputeAllocationMembershipStore) FindByAllocationWithUser(ctx con
// the project, joined with the user, the owning allocation name, and the
// project-level role. Returned rows are ordered by user; callers aggregate it
// per-user (collapsing into the response's allocation list).
-func (s *mysqlComputeAllocationMembershipStore) FindByProjectWithUser(ctx context.Context, projectID string) ([]MembershipWithUser, error) {
+func (s *pgComputeAllocationMembershipStore) FindByProjectWithUser(ctx context.Context, projectID string) ([]MembershipWithUser, error) {
type row struct {
models.ComputeAllocationMembership
Role string `db:"role"`
@@ -215,7 +215,7 @@ func (s *mysqlComputeAllocationMembershipStore) FindByProjectWithUser(ctx contex
JOIN users u ON u.id = m.user_id
LEFT JOIN project_memberships pm
ON pm.project_id = ca.project_id AND pm.user_id = m.user_id
- WHERE ca.project_id = ?
+ WHERE ca.project_id = $1
ORDER BY u.email, ca.name`, projectID)
if err != nil {
return nil, err
diff --git a/internal/store/compute_allocation_resource_mapping_store.go b/internal/store/compute_allocation_resource_mapping_store.go
index 7d96e41ca..a972cc91c 100644
--- a/internal/store/compute_allocation_resource_mapping_store.go
+++ b/internal/store/compute_allocation_resource_mapping_store.go
@@ -27,22 +27,22 @@ import (
"github.com/apache/airavata-custos/pkg/models"
)
-type mysqlComputeAllocationResourceMappingStore struct {
+type pgComputeAllocationResourceMappingStore struct {
db *sqlx.DB
}
-// NewComputeAllocationResourceMappingStore returns a MySQL-backed
+// NewComputeAllocationResourceMappingStore returns a PostgreSQL-backed
// ComputeAllocationResourceMappingStore.
func NewComputeAllocationResourceMappingStore(db *sqlx.DB) ComputeAllocationResourceMappingStore {
- return &mysqlComputeAllocationResourceMappingStore{db: db}
+ return &pgComputeAllocationResourceMappingStore{db: db}
}
-func (s *mysqlComputeAllocationResourceMappingStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationResourceMapping, error) {
+func (s *pgComputeAllocationResourceMappingStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationResourceMapping, error) {
var m models.ComputeAllocationResourceMapping
err := s.db.GetContext(ctx, &m,
`SELECT id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time
FROM compute_allocation_resource_mappings
- WHERE id = ?`, id)
+ WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -52,12 +52,12 @@ func (s *mysqlComputeAllocationResourceMappingStore) FindByID(ctx context.Contex
return &m, nil
}
-func (s *mysqlComputeAllocationResourceMappingStore) FindByPair(ctx context.Context, allocationID, resourceID string) (*models.ComputeAllocationResourceMapping, error) {
+func (s *pgComputeAllocationResourceMappingStore) FindByPair(ctx context.Context, allocationID, resourceID string) (*models.ComputeAllocationResourceMapping, error) {
var m models.ComputeAllocationResourceMapping
err := s.db.GetContext(ctx, &m,
`SELECT id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time
FROM compute_allocation_resource_mappings
- WHERE compute_allocation_id = ? AND compute_allocation_resource_id = ?`,
+ WHERE compute_allocation_id = $1 AND compute_allocation_resource_id = $2`,
allocationID, resourceID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
@@ -68,14 +68,14 @@ func (s *mysqlComputeAllocationResourceMappingStore) FindByPair(ctx context.Cont
return &m, nil
}
-func (s *mysqlComputeAllocationResourceMappingStore) FindResourcesByAllocation(ctx context.Context, allocationID string) ([]models.ComputeAllocationResource, error) {
+func (s *pgComputeAllocationResourceMappingStore) FindResourcesByAllocation(ctx context.Context, allocationID string) ([]models.ComputeAllocationResource, error) {
var resources []models.ComputeAllocationResource
err := s.db.SelectContext(ctx, &resources,
`SELECT r.id, r.name, r.resource_type, r.resource_amount, r.compute_cluster_id
FROM compute_allocation_resources r
JOIN compute_allocation_resource_mappings m
ON m.compute_allocation_resource_id = r.id
- WHERE m.compute_allocation_id = ?
+ WHERE m.compute_allocation_id = $1
ORDER BY r.name`, allocationID)
if err != nil {
return nil, err
@@ -83,7 +83,7 @@ func (s *mysqlComputeAllocationResourceMappingStore) FindResourcesByAllocation(c
return resources, nil
}
-func (s *mysqlComputeAllocationResourceMappingStore) FindAllocationsByResource(ctx context.Context, resourceID string) ([]models.ComputeAllocation, error) {
+func (s *pgComputeAllocationResourceMappingStore) FindAllocationsByResource(ctx context.Context, resourceID string) ([]models.ComputeAllocation, error) {
var allocs []models.ComputeAllocation
err := s.db.SelectContext(ctx, &allocs,
`SELECT a.id, a.project_id, a.name, a.status, a.compute_cluster_id,
@@ -91,7 +91,7 @@ func (s *mysqlComputeAllocationResourceMappingStore) FindAllocationsByResource(c
FROM compute_allocations a
JOIN compute_allocation_resource_mappings m
ON m.compute_allocation_id = a.id
- WHERE m.compute_allocation_resource_id = ?
+ WHERE m.compute_allocation_resource_id = $1
ORDER BY a.name`, resourceID)
if err != nil {
return nil, err
@@ -99,29 +99,29 @@ func (s *mysqlComputeAllocationResourceMappingStore) FindAllocationsByResource(c
return allocs, nil
}
-func (s *mysqlComputeAllocationResourceMappingStore) Create(ctx context.Context, tx *sql.Tx, m *models.ComputeAllocationResourceMapping) error {
+func (s *pgComputeAllocationResourceMappingStore) Create(ctx context.Context, tx *sql.Tx, m *models.ComputeAllocationResourceMapping) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO compute_allocation_resource_mappings
(id, compute_allocation_id, compute_allocation_resource_id, resource_amount, resource_time)
- VALUES (?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5)`,
m.ID, m.ComputeAllocationID, m.ComputeAllocationResourceID, m.ResourceAmount, m.ResourceTime)
return err
}
-func (s *mysqlComputeAllocationResourceMappingStore) Update(ctx context.Context, tx *sql.Tx, m *models.ComputeAllocationResourceMapping) error {
+func (s *pgComputeAllocationResourceMappingStore) Update(ctx context.Context, tx *sql.Tx, m *models.ComputeAllocationResourceMapping) error {
_, err := tx.ExecContext(ctx,
`UPDATE compute_allocation_resource_mappings
- SET resource_amount = ?,
- resource_time = ?
- WHERE id = ?`,
+ SET resource_amount = $1,
+ resource_time = $2
+ WHERE id = $3`,
m.ResourceAmount, m.ResourceTime, m.ID)
return err
}
-func (s *mysqlComputeAllocationResourceMappingStore) DeleteByPair(ctx context.Context, tx *sql.Tx, allocationID, resourceID string) error {
+func (s *pgComputeAllocationResourceMappingStore) DeleteByPair(ctx context.Context, tx *sql.Tx, allocationID, resourceID string) error {
_, err := tx.ExecContext(ctx,
`DELETE FROM compute_allocation_resource_mappings
- WHERE compute_allocation_id = ? AND compute_allocation_resource_id = ?`,
+ WHERE compute_allocation_id = $1 AND compute_allocation_resource_id = $2`,
allocationID, resourceID)
return err
}
diff --git a/internal/store/compute_allocation_resource_rate_store.go b/internal/store/compute_allocation_resource_rate_store.go
index de9426797..118641c6e 100644
--- a/internal/store/compute_allocation_resource_rate_store.go
+++ b/internal/store/compute_allocation_resource_rate_store.go
@@ -30,21 +30,21 @@ import (
const computeAllocationResourceRateColumns = "id, compute_allocation_resource_id, rate, start_time, end_time"
-type mysqlComputeAllocationResourceRateStore struct {
+type pgComputeAllocationResourceRateStore struct {
db *sqlx.DB
}
-// NewComputeAllocationResourceRateStore returns a MySQL-backed
+// NewComputeAllocationResourceRateStore returns a PostgreSQL-backed
// ComputeAllocationResourceRateStore.
func NewComputeAllocationResourceRateStore(db *sqlx.DB) ComputeAllocationResourceRateStore {
- return &mysqlComputeAllocationResourceRateStore{db: db}
+ return &pgComputeAllocationResourceRateStore{db: db}
}
-func (s *mysqlComputeAllocationResourceRateStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationResourceRate, error) {
+func (s *pgComputeAllocationResourceRateStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationResourceRate, error) {
var r models.ComputeAllocationResourceRate
err := s.db.GetContext(ctx, &r,
`SELECT `+computeAllocationResourceRateColumns+`
- FROM compute_allocation_resource_rates WHERE id = ?`, id)
+ FROM compute_allocation_resource_rates WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -54,12 +54,12 @@ func (s *mysqlComputeAllocationResourceRateStore) FindByID(ctx context.Context,
return &r, nil
}
-func (s *mysqlComputeAllocationResourceRateStore) FindByResource(ctx context.Context, resourceID string) ([]models.ComputeAllocationResourceRate, error) {
+func (s *pgComputeAllocationResourceRateStore) FindByResource(ctx context.Context, resourceID string) ([]models.ComputeAllocationResourceRate, error) {
var rates []models.ComputeAllocationResourceRate
err := s.db.SelectContext(ctx, &rates,
`SELECT `+computeAllocationResourceRateColumns+`
FROM compute_allocation_resource_rates
- WHERE compute_allocation_resource_id = ?
+ WHERE compute_allocation_resource_id = $1
ORDER BY start_time`, resourceID)
if err != nil {
return nil, err
@@ -67,14 +67,14 @@ func (s *mysqlComputeAllocationResourceRateStore) FindByResource(ctx context.Con
return rates, nil
}
-func (s *mysqlComputeAllocationResourceRateStore) FindEffective(ctx context.Context, resourceID string, at time.Time) (*models.ComputeAllocationResourceRate, error) {
+func (s *pgComputeAllocationResourceRateStore) FindEffective(ctx context.Context, resourceID string, at time.Time) (*models.ComputeAllocationResourceRate, error) {
var r models.ComputeAllocationResourceRate
err := s.db.GetContext(ctx, &r,
`SELECT `+computeAllocationResourceRateColumns+`
FROM compute_allocation_resource_rates
- WHERE compute_allocation_resource_id = ?
- AND start_time <= ?
- AND end_time > ?
+ WHERE compute_allocation_resource_id = $1
+ AND start_time <= $2
+ AND end_time > $3
ORDER BY start_time DESC
LIMIT 1`, resourceID, at, at)
if err != nil {
@@ -86,25 +86,25 @@ func (s *mysqlComputeAllocationResourceRateStore) FindEffective(ctx context.Cont
return &r, nil
}
-func (s *mysqlComputeAllocationResourceRateStore) Create(ctx context.Context, tx *sql.Tx, r *models.ComputeAllocationResourceRate) error {
+func (s *pgComputeAllocationResourceRateStore) Create(ctx context.Context, tx *sql.Tx, r *models.ComputeAllocationResourceRate) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO compute_allocation_resource_rates
(id, compute_allocation_resource_id, rate, start_time, end_time)
- VALUES (?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5)`,
r.ID, r.ComputeAllocationResourceID, r.Rate, r.StartTime, r.EndTime)
return err
}
-func (s *mysqlComputeAllocationResourceRateStore) Update(ctx context.Context, tx *sql.Tx, r *models.ComputeAllocationResourceRate) error {
+func (s *pgComputeAllocationResourceRateStore) Update(ctx context.Context, tx *sql.Tx, r *models.ComputeAllocationResourceRate) error {
_, err := tx.ExecContext(ctx,
`UPDATE compute_allocation_resource_rates
- SET rate = ?, start_time = ?, end_time = ?
- WHERE id = ?`,
+ SET rate = $1, start_time = $2, end_time = $3
+ WHERE id = $4`,
r.Rate, r.StartTime, r.EndTime, r.ID)
return err
}
-func (s *mysqlComputeAllocationResourceRateStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_resource_rates WHERE id = ?`, id)
+func (s *pgComputeAllocationResourceRateStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_resource_rates WHERE id = $1`, id)
return err
}
diff --git a/internal/store/compute_allocation_resource_store.go b/internal/store/compute_allocation_resource_store.go
index f5c69e38a..e8e69426f 100644
--- a/internal/store/compute_allocation_resource_store.go
+++ b/internal/store/compute_allocation_resource_store.go
@@ -27,20 +27,20 @@ import (
"github.com/apache/airavata-custos/pkg/models"
)
-type mysqlComputeAllocationResourceStore struct {
+type pgComputeAllocationResourceStore struct {
db *sqlx.DB
}
-// NewComputeAllocationResourceStore returns a MySQL-backed
+// NewComputeAllocationResourceStore returns a PostgreSQL-backed
// ComputeAllocationResourceStore.
func NewComputeAllocationResourceStore(db *sqlx.DB) ComputeAllocationResourceStore {
- return &mysqlComputeAllocationResourceStore{db: db}
+ return &pgComputeAllocationResourceStore{db: db}
}
-func (s *mysqlComputeAllocationResourceStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationResource, error) {
+func (s *pgComputeAllocationResourceStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationResource, error) {
var r models.ComputeAllocationResource
err := s.db.GetContext(ctx, &r,
- `SELECT id, name, resource_type, resource_amount, compute_cluster_id FROM compute_allocation_resources WHERE id = ?`, id)
+ `SELECT id, name, resource_type, resource_amount, compute_cluster_id FROM compute_allocation_resources WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -50,12 +50,12 @@ func (s *mysqlComputeAllocationResourceStore) FindByID(ctx context.Context, id s
return &r, nil
}
-func (s *mysqlComputeAllocationResourceStore) FindByNameAndCluster(ctx context.Context, name, clusterID string) (*models.ComputeAllocationResource, error) {
+func (s *pgComputeAllocationResourceStore) FindByNameAndCluster(ctx context.Context, name, clusterID string) (*models.ComputeAllocationResource, error) {
var r models.ComputeAllocationResource
err := s.db.GetContext(ctx, &r,
`SELECT id, name, resource_type, resource_amount, compute_cluster_id
FROM compute_allocation_resources
- WHERE name = ? AND compute_cluster_id = ?`, name, clusterID)
+ WHERE name = $1 AND compute_cluster_id = $2`, name, clusterID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -65,12 +65,12 @@ func (s *mysqlComputeAllocationResourceStore) FindByNameAndCluster(ctx context.C
return &r, nil
}
-func (s *mysqlComputeAllocationResourceStore) FindByTypeAndCluster(ctx context.Context, resourceType, clusterID string) ([]models.ComputeAllocationResource, error) {
+func (s *pgComputeAllocationResourceStore) FindByTypeAndCluster(ctx context.Context, resourceType, clusterID string) ([]models.ComputeAllocationResource, error) {
var resources []models.ComputeAllocationResource
err := s.db.SelectContext(ctx, &resources,
`SELECT id, name, resource_type, resource_amount, compute_cluster_id
FROM compute_allocation_resources
- WHERE resource_type = ? AND compute_cluster_id = ?
+ WHERE resource_type = $1 AND compute_cluster_id = $2
ORDER BY name`, resourceType, clusterID)
if err != nil {
return nil, err
@@ -78,7 +78,7 @@ func (s *mysqlComputeAllocationResourceStore) FindByTypeAndCluster(ctx context.C
return resources, nil
}
-func (s *mysqlComputeAllocationResourceStore) List(ctx context.Context) ([]models.ComputeAllocationResource, error) {
+func (s *pgComputeAllocationResourceStore) List(ctx context.Context) ([]models.ComputeAllocationResource, error) {
var resources []models.ComputeAllocationResource
err := s.db.SelectContext(ctx, &resources,
`SELECT id, name, resource_type, resource_amount, compute_cluster_id FROM compute_allocation_resources ORDER BY name`)
@@ -88,7 +88,7 @@ func (s *mysqlComputeAllocationResourceStore) List(ctx context.Context) ([]model
return resources, nil
}
-func (s *mysqlComputeAllocationResourceStore) ListSummaries(ctx context.Context) ([]ComputeAllocationResourceSummary, error) {
+func (s *pgComputeAllocationResourceStore) ListSummaries(ctx context.Context) ([]ComputeAllocationResourceSummary, error) {
var rows []ComputeAllocationResourceSummary
err := s.db.SelectContext(ctx, &rows,
`SELECT r.id, r.name, r.resource_type, r.resource_amount, r.compute_cluster_id,
@@ -113,24 +113,24 @@ func (s *mysqlComputeAllocationResourceStore) ListSummaries(ctx context.Context)
return rows, nil
}
-func (s *mysqlComputeAllocationResourceStore) Create(ctx context.Context, tx *sql.Tx, r *models.ComputeAllocationResource) error {
+func (s *pgComputeAllocationResourceStore) Create(ctx context.Context, tx *sql.Tx, r *models.ComputeAllocationResource) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO compute_allocation_resources (id, name, resource_type, resource_amount, compute_cluster_id)
- VALUES (?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5)`,
r.ID, r.Name, r.ResourceType, r.ResourceAmount, r.ComputeClusterID)
return err
}
-func (s *mysqlComputeAllocationResourceStore) Update(ctx context.Context, tx *sql.Tx, r *models.ComputeAllocationResource) error {
+func (s *pgComputeAllocationResourceStore) Update(ctx context.Context, tx *sql.Tx, r *models.ComputeAllocationResource) error {
_, err := tx.ExecContext(ctx,
`UPDATE compute_allocation_resources
- SET name = ?, resource_type = ?, resource_amount = ?, compute_cluster_id = ?
- WHERE id = ?`,
+ SET name = $1, resource_type = $2, resource_amount = $3, compute_cluster_id = $4
+ WHERE id = $5`,
r.Name, r.ResourceType, r.ResourceAmount, r.ComputeClusterID, r.ID)
return err
}
-func (s *mysqlComputeAllocationResourceStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_resources WHERE id = ?`, id)
+func (s *pgComputeAllocationResourceStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_resources WHERE id = $1`, id)
return err
}
diff --git a/internal/store/compute_allocation_store.go b/internal/store/compute_allocation_store.go
index 861225f73..e6b76784b 100644
--- a/internal/store/compute_allocation_store.go
+++ b/internal/store/compute_allocation_store.go
@@ -28,21 +28,21 @@ import (
"github.com/apache/airavata-custos/pkg/models"
)
-type mysqlComputeAllocationStore struct {
+type pgComputeAllocationStore struct {
db *sqlx.DB
}
-// NewComputeAllocationStore returns a MySQL-backed ComputeAllocationStore.
+// NewComputeAllocationStore returns a PostgreSQL-backed ComputeAllocationStore.
func NewComputeAllocationStore(db *sqlx.DB) ComputeAllocationStore {
- return &mysqlComputeAllocationStore{db: db}
+ return &pgComputeAllocationStore{db: db}
}
const computeAllocationColumns = `id, project_id, name, status, compute_cluster_id, initial_su_amount, start_time, end_time`
-func (s *mysqlComputeAllocationStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocation, error) {
+func (s *pgComputeAllocationStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocation, error) {
var a models.ComputeAllocation
err := s.db.GetContext(ctx, &a,
- `SELECT `+computeAllocationColumns+` FROM compute_allocations WHERE id = ?`, id)
+ `SELECT `+computeAllocationColumns+` FROM compute_allocations WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -52,54 +52,54 @@ func (s *mysqlComputeAllocationStore) FindByID(ctx context.Context, id string) (
return &a, nil
}
-func (s *mysqlComputeAllocationStore) FindByProject(ctx context.Context, projectID string) ([]models.ComputeAllocation, error) {
+func (s *pgComputeAllocationStore) FindByProject(ctx context.Context, projectID string) ([]models.ComputeAllocation, error) {
var allocs []models.ComputeAllocation
err := s.db.SelectContext(ctx, &allocs,
- `SELECT `+computeAllocationColumns+` FROM compute_allocations WHERE project_id = ?`, projectID)
+ `SELECT `+computeAllocationColumns+` FROM compute_allocations WHERE project_id = $1`, projectID)
if err != nil {
return nil, err
}
return allocs, nil
}
-func (s *mysqlComputeAllocationStore) FindByCluster(ctx context.Context, clusterID string) ([]models.ComputeAllocation, error) {
+func (s *pgComputeAllocationStore) FindByCluster(ctx context.Context, clusterID string) ([]models.ComputeAllocation, error) {
var allocs []models.ComputeAllocation
err := s.db.SelectContext(ctx, &allocs,
- `SELECT `+computeAllocationColumns+` FROM compute_allocations WHERE compute_cluster_id = ?`, clusterID)
+ `SELECT `+computeAllocationColumns+` FROM compute_allocations WHERE compute_cluster_id = $1`, clusterID)
if err != nil {
return nil, err
}
return allocs, nil
}
-func (s *mysqlComputeAllocationStore) Create(ctx context.Context, tx *sql.Tx, a *models.ComputeAllocation) error {
+func (s *pgComputeAllocationStore) Create(ctx context.Context, tx *sql.Tx, a *models.ComputeAllocation) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO compute_allocations (`+computeAllocationColumns+`)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
a.ID, a.ProjectID, a.Name, string(a.Status), a.ComputeClusterID, a.InitialSUAmount, a.StartTime, a.EndTime)
return err
}
-func (s *mysqlComputeAllocationStore) Update(ctx context.Context, tx *sql.Tx, a *models.ComputeAllocation) error {
+func (s *pgComputeAllocationStore) Update(ctx context.Context, tx *sql.Tx, a *models.ComputeAllocation) error {
_, err := tx.ExecContext(ctx,
`UPDATE compute_allocations
- SET project_id = ?, name = ?, status = ?, compute_cluster_id = ?,
- initial_su_amount = ?, start_time = ?, end_time = ?
- WHERE id = ?`,
+ SET project_id = $1, name = $2, status = $3, compute_cluster_id = $4,
+ initial_su_amount = $5, start_time = $6, end_time = $7
+ WHERE id = $8`,
a.ProjectID, a.Name, string(a.Status), a.ComputeClusterID,
a.InitialSUAmount, a.StartTime, a.EndTime, a.ID)
return err
}
-func (s *mysqlComputeAllocationStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocations WHERE id = ?`, id)
+func (s *pgComputeAllocationStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocations WHERE id = $1`, id)
return err
}
// FindByParticipant returns the allocations where the user holds an active
// membership, or a governance role on the parent project. Ordered like List
// (start_time descending).
-func (s *mysqlComputeAllocationStore) FindByParticipant(ctx context.Context, userID string) ([]models.ComputeAllocation, error) {
+func (s *pgComputeAllocationStore) FindByParticipant(ctx context.Context, userID string) ([]models.ComputeAllocation, error) {
var rows []models.ComputeAllocation
query, args, err := sqlx.In(
`SELECT `+computeAllocationColumns+` FROM compute_allocations a
@@ -120,7 +120,7 @@ func (s *mysqlComputeAllocationStore) FindByParticipant(ctx context.Context, use
return rows, nil
}
-func (s *mysqlComputeAllocationStore) List(ctx context.Context, f AllocationListFilter) ([]models.ComputeAllocation, int, error) {
+func (s *pgComputeAllocationStore) List(ctx context.Context, f AllocationListFilter) ([]models.ComputeAllocation, int, error) {
where := []string{}
args := []any{}
if f.ProjectID != "" {
@@ -132,7 +132,7 @@ func (s *mysqlComputeAllocationStore) List(ctx context.Context, f AllocationList
args = append(args, f.Status)
}
if f.Query != "" {
- where = append(where, `name LIKE ?`)
+ where = append(where, `name ILIKE ?`)
args = append(args, "%"+f.Query+"%")
}
clause := ""
@@ -140,7 +140,7 @@ func (s *mysqlComputeAllocationStore) List(ctx context.Context, f AllocationList
clause = " WHERE " + strings.Join(where, " AND ")
}
var total int
- if err := s.db.GetContext(ctx, &total, `SELECT COUNT(*) FROM compute_allocations`+clause, args...); err != nil {
+ if err := s.db.GetContext(ctx, &total, s.db.Rebind(`SELECT COUNT(*) FROM compute_allocations`+clause), args...); err != nil {
return nil, 0, err
}
limit := f.Limit
@@ -158,7 +158,7 @@ func (s *mysqlComputeAllocationStore) List(ctx context.Context, f AllocationList
` ORDER BY start_time DESC LIMIT ? OFFSET ?`
args = append(args, limit, offset)
var rows []models.ComputeAllocation
- if err := s.db.SelectContext(ctx, &rows, query, args...); err != nil {
+ if err := s.db.SelectContext(ctx, &rows, s.db.Rebind(query), args...); err != nil {
return nil, 0, err
}
return rows, total, nil
diff --git a/internal/store/compute_allocation_usage_store.go b/internal/store/compute_allocation_usage_store.go
index 9d1dbceba..aba96ee10 100644
--- a/internal/store/compute_allocation_usage_store.go
+++ b/internal/store/compute_allocation_usage_store.go
@@ -29,20 +29,20 @@ import (
const computeAllocationUsageColumns = "id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id"
-type mysqlComputeAllocationUsageStore struct {
+type pgComputeAllocationUsageStore struct {
db *sqlx.DB
}
-// NewComputeAllocationUsageStore returns a MySQL-backed
+// NewComputeAllocationUsageStore returns a PostgreSQL-backed
// ComputeAllocationUsageStore.
func NewComputeAllocationUsageStore(db *sqlx.DB) ComputeAllocationUsageStore {
- return &mysqlComputeAllocationUsageStore{db: db}
+ return &pgComputeAllocationUsageStore{db: db}
}
-func (s *mysqlComputeAllocationUsageStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationUsage, error) {
+func (s *pgComputeAllocationUsageStore) FindByID(ctx context.Context, id string) (*models.ComputeAllocationUsage, error) {
var u models.ComputeAllocationUsage
err := s.db.GetContext(ctx, &u,
- `SELECT `+computeAllocationUsageColumns+` FROM compute_allocation_usages WHERE id = ?`, id)
+ `SELECT `+computeAllocationUsageColumns+` FROM compute_allocation_usages WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -52,12 +52,12 @@ func (s *mysqlComputeAllocationUsageStore) FindByID(ctx context.Context, id stri
return &u, nil
}
-func (s *mysqlComputeAllocationUsageStore) FindByAllocation(ctx context.Context, allocationID string) ([]models.ComputeAllocationUsage, error) {
+func (s *pgComputeAllocationUsageStore) FindByAllocation(ctx context.Context, allocationID string) ([]models.ComputeAllocationUsage, error) {
var rows []models.ComputeAllocationUsage
err := s.db.SelectContext(ctx, &rows,
`SELECT `+computeAllocationUsageColumns+`
FROM compute_allocation_usages
- WHERE compute_allocation_id = ?
+ WHERE compute_allocation_id = $1
ORDER BY calculated_time`, allocationID)
if err != nil {
return nil, err
@@ -65,12 +65,12 @@ func (s *mysqlComputeAllocationUsageStore) FindByAllocation(ctx context.Context,
return rows, nil
}
-func (s *mysqlComputeAllocationUsageStore) FindByUser(ctx context.Context, userID string) ([]models.ComputeAllocationUsage, error) {
+func (s *pgComputeAllocationUsageStore) FindByUser(ctx context.Context, userID string) ([]models.ComputeAllocationUsage, error) {
var rows []models.ComputeAllocationUsage
err := s.db.SelectContext(ctx, &rows,
`SELECT `+computeAllocationUsageColumns+`
FROM compute_allocation_usages
- WHERE user_id = ?
+ WHERE user_id = $1
ORDER BY calculated_time`, userID)
if err != nil {
return nil, err
@@ -78,10 +78,10 @@ func (s *mysqlComputeAllocationUsageStore) FindByUser(ctx context.Context, userI
return rows, nil
}
-func (s *mysqlComputeAllocationUsageStore) FindByComputeAllocationIDAndJobID(ctx context.Context, allocationID, jobID string) (*models.ComputeAllocationUsage, error) {
+func (s *pgComputeAllocationUsageStore) FindByComputeAllocationIDAndJobID(ctx context.Context, allocationID, jobID string) (*models.ComputeAllocationUsage, error) {
var u models.ComputeAllocationUsage
err := s.db.GetContext(ctx, &u,
- `SELECT `+computeAllocationUsageColumns+` FROM compute_allocation_usages WHERE compute_allocation_id = ? AND job_id = ?`, allocationID, jobID)
+ `SELECT `+computeAllocationUsageColumns+` FROM compute_allocation_usages WHERE compute_allocation_id = $1 AND job_id = $2`, allocationID, jobID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -91,40 +91,40 @@ func (s *mysqlComputeAllocationUsageStore) FindByComputeAllocationIDAndJobID(ctx
return &u, nil
}
-func (s *mysqlComputeAllocationUsageStore) SumSUForAllocation(ctx context.Context, allocationID string) (int64, error) {
+func (s *pgComputeAllocationUsageStore) SumSUForAllocation(ctx context.Context, allocationID string) (int64, error) {
var total sql.NullInt64
err := s.db.GetContext(ctx, &total,
- `SELECT COALESCE(SUM(used_su_amount), 0)
+ `SELECT CAST(ROUND(COALESCE(SUM(used_su_amount), 0)) AS BIGINT)
FROM compute_allocation_usages
- WHERE compute_allocation_id = ?`, allocationID)
+ WHERE compute_allocation_id = $1`, allocationID)
if err != nil {
return 0, err
}
return total.Int64, nil
}
-func (s *mysqlComputeAllocationUsageStore) SumSUForUserInAllocation(ctx context.Context, allocationID, userID string) (int64, error) {
+func (s *pgComputeAllocationUsageStore) SumSUForUserInAllocation(ctx context.Context, allocationID, userID string) (int64, error) {
var total sql.NullInt64
err := s.db.GetContext(ctx, &total,
- `SELECT COALESCE(SUM(used_su_amount), 0)
+ `SELECT CAST(ROUND(COALESCE(SUM(used_su_amount), 0)) AS BIGINT)
FROM compute_allocation_usages
- WHERE compute_allocation_id = ? AND user_id = ?`, allocationID, userID)
+ WHERE compute_allocation_id = $1 AND user_id = $2`, allocationID, userID)
if err != nil {
return 0, err
}
return total.Int64, nil
}
-func (s *mysqlComputeAllocationUsageStore) Create(ctx context.Context, tx *sql.Tx, u *models.ComputeAllocationUsage) error {
+func (s *pgComputeAllocationUsageStore) Create(ctx context.Context, tx *sql.Tx, u *models.ComputeAllocationUsage) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO compute_allocation_usages
(id, compute_allocation_id, used_raw_amount, used_su_amount, calculated_time, user_id, job_id, compute_allocation_resource_id)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
u.ID, u.ComputeAllocationID, u.UsedRawAmount, u.UsedSUAmount, u.CalculatedTime, u.UserID, u.JobID, u.ComputeAllocationResourceID)
return err
}
-func (s *mysqlComputeAllocationUsageStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_usages WHERE id = ?`, id)
+func (s *pgComputeAllocationUsageStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM compute_allocation_usages WHERE id = $1`, id)
return err
}
diff --git a/internal/store/compute_cluster_store.go b/internal/store/compute_cluster_store.go
index c27b77bae..72064c28c 100644
--- a/internal/store/compute_cluster_store.go
+++ b/internal/store/compute_cluster_store.go
@@ -27,19 +27,19 @@ import (
"github.com/apache/airavata-custos/pkg/models"
)
-type mysqlComputeClusterStore struct {
+type pgComputeClusterStore struct {
db *sqlx.DB
}
-// NewComputeClusterStore returns a MySQL-backed ComputeClusterStore.
+// NewComputeClusterStore returns a PostgreSQL-backed ComputeClusterStore.
func NewComputeClusterStore(db *sqlx.DB) ComputeClusterStore {
- return &mysqlComputeClusterStore{db: db}
+ return &pgComputeClusterStore{db: db}
}
-func (s *mysqlComputeClusterStore) FindByID(ctx context.Context, id string) (*models.ComputeCluster, error) {
+func (s *pgComputeClusterStore) FindByID(ctx context.Context, id string) (*models.ComputeCluster, error) {
var c models.ComputeCluster
err := s.db.GetContext(ctx, &c,
- `SELECT id, name FROM compute_clusters WHERE id = ?`, id)
+ `SELECT id, name FROM compute_clusters WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -49,10 +49,10 @@ func (s *mysqlComputeClusterStore) FindByID(ctx context.Context, id string) (*mo
return &c, nil
}
-func (s *mysqlComputeClusterStore) FindByName(ctx context.Context, name string) (*models.ComputeCluster, error) {
+func (s *pgComputeClusterStore) FindByName(ctx context.Context, name string) (*models.ComputeCluster, error) {
var c models.ComputeCluster
err := s.db.GetContext(ctx, &c,
- `SELECT id, name FROM compute_clusters WHERE name = ?`, name)
+ `SELECT id, name FROM compute_clusters WHERE name = $1`, name)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -62,7 +62,7 @@ func (s *mysqlComputeClusterStore) FindByName(ctx context.Context, name string)
return &c, nil
}
-func (s *mysqlComputeClusterStore) List(ctx context.Context) ([]models.ComputeCluster, error) {
+func (s *pgComputeClusterStore) List(ctx context.Context) ([]models.ComputeCluster, error) {
var clusters []models.ComputeCluster
err := s.db.SelectContext(ctx, &clusters,
`SELECT id, name FROM compute_clusters ORDER BY name`)
@@ -72,21 +72,21 @@ func (s *mysqlComputeClusterStore) List(ctx context.Context) ([]models.ComputeCl
return clusters, nil
}
-func (s *mysqlComputeClusterStore) Create(ctx context.Context, tx *sql.Tx, c *models.ComputeCluster) error {
+func (s *pgComputeClusterStore) Create(ctx context.Context, tx *sql.Tx, c *models.ComputeCluster) error {
_, err := tx.ExecContext(ctx,
- `INSERT INTO compute_clusters (id, name) VALUES (?, ?)`,
+ `INSERT INTO compute_clusters (id, name) VALUES ($1, $2)`,
c.ID, c.Name)
return err
}
-func (s *mysqlComputeClusterStore) Update(ctx context.Context, tx *sql.Tx, c *models.ComputeCluster) error {
+func (s *pgComputeClusterStore) Update(ctx context.Context, tx *sql.Tx, c *models.ComputeCluster) error {
_, err := tx.ExecContext(ctx,
- `UPDATE compute_clusters SET name = ? WHERE id = ?`,
+ `UPDATE compute_clusters SET name = $1 WHERE id = $2`,
c.Name, c.ID)
return err
}
-func (s *mysqlComputeClusterStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM compute_clusters WHERE id = ?`, id)
+func (s *pgComputeClusterStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM compute_clusters WHERE id = $1`, id)
return err
}
diff --git a/internal/store/compute_cluster_user_store.go b/internal/store/compute_cluster_user_store.go
index 00b239532..2068137fc 100644
--- a/internal/store/compute_cluster_user_store.go
+++ b/internal/store/compute_cluster_user_store.go
@@ -27,22 +27,22 @@ import (
"github.com/apache/airavata-custos/pkg/models"
)
-type mysqlComputeClusterUserStore struct {
+type pgComputeClusterUserStore struct {
db *sqlx.DB
}
-// NewComputeClusterUserStore returns a MySQL-backed ComputeClusterUserStore.
+// NewComputeClusterUserStore returns a PostgreSQL-backed ComputeClusterUserStore.
func NewComputeClusterUserStore(db *sqlx.DB) ComputeClusterUserStore {
- return &mysqlComputeClusterUserStore{db: db}
+ return &pgComputeClusterUserStore{db: db}
}
const computeClusterUserColumns = `id, compute_cluster_id, user_id, local_username, provisioned_at`
-func (s *mysqlComputeClusterUserStore) FindByID(ctx context.Context, id string) (*models.ComputeClusterUser, error) {
+func (s *pgComputeClusterUserStore) FindByID(ctx context.Context, id string) (*models.ComputeClusterUser, error) {
var c models.ComputeClusterUser
err := s.db.GetContext(ctx, &c,
`SELECT `+computeClusterUserColumns+`
- FROM compute_cluster_users WHERE id = ?`, id)
+ FROM compute_cluster_users WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -52,12 +52,12 @@ func (s *mysqlComputeClusterUserStore) FindByID(ctx context.Context, id string)
return &c, nil
}
-func (s *mysqlComputeClusterUserStore) FindByPair(ctx context.Context, clusterID, userID string) (*models.ComputeClusterUser, error) {
+func (s *pgComputeClusterUserStore) FindByPair(ctx context.Context, clusterID, userID string) (*models.ComputeClusterUser, error) {
var c models.ComputeClusterUser
err := s.db.GetContext(ctx, &c,
`SELECT `+computeClusterUserColumns+`
FROM compute_cluster_users
- WHERE compute_cluster_id = ? AND user_id = ?`, clusterID, userID)
+ WHERE compute_cluster_id = $1 AND user_id = $2`, clusterID, userID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -67,12 +67,12 @@ func (s *mysqlComputeClusterUserStore) FindByPair(ctx context.Context, clusterID
return &c, nil
}
-func (s *mysqlComputeClusterUserStore) FindByClusterAndLocalUsername(ctx context.Context, clusterID, localUsername string) (*models.ComputeClusterUser, error) {
+func (s *pgComputeClusterUserStore) FindByClusterAndLocalUsername(ctx context.Context, clusterID, localUsername string) (*models.ComputeClusterUser, error) {
var c models.ComputeClusterUser
err := s.db.GetContext(ctx, &c,
`SELECT `+computeClusterUserColumns+`
FROM compute_cluster_users
- WHERE compute_cluster_id = ? AND local_username = ?`, clusterID, localUsername)
+ WHERE compute_cluster_id = $1 AND local_username = $2`, clusterID, localUsername)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -82,12 +82,12 @@ func (s *mysqlComputeClusterUserStore) FindByClusterAndLocalUsername(ctx context
return &c, nil
}
-func (s *mysqlComputeClusterUserStore) FindByCluster(ctx context.Context, clusterID string) ([]models.ComputeClusterUser, error) {
+func (s *pgComputeClusterUserStore) FindByCluster(ctx context.Context, clusterID string) ([]models.ComputeClusterUser, error) {
var users []models.ComputeClusterUser
err := s.db.SelectContext(ctx, &users,
`SELECT `+computeClusterUserColumns+`
FROM compute_cluster_users
- WHERE compute_cluster_id = ?
+ WHERE compute_cluster_id = $1
ORDER BY local_username`, clusterID)
if err != nil {
return nil, err
@@ -95,12 +95,12 @@ func (s *mysqlComputeClusterUserStore) FindByCluster(ctx context.Context, cluste
return users, nil
}
-func (s *mysqlComputeClusterUserStore) FindByUser(ctx context.Context, userID string) ([]models.ComputeClusterUser, error) {
+func (s *pgComputeClusterUserStore) FindByUser(ctx context.Context, userID string) ([]models.ComputeClusterUser, error) {
var users []models.ComputeClusterUser
err := s.db.SelectContext(ctx, &users,
`SELECT `+computeClusterUserColumns+`
FROM compute_cluster_users
- WHERE user_id = ?
+ WHERE user_id = $1
ORDER BY compute_cluster_id`, userID)
if err != nil {
return nil, err
@@ -108,49 +108,49 @@ func (s *mysqlComputeClusterUserStore) FindByUser(ctx context.Context, userID st
return users, nil
}
-func (s *mysqlComputeClusterUserStore) Create(ctx context.Context, tx *sql.Tx, c *models.ComputeClusterUser) error {
+func (s *pgComputeClusterUserStore) Create(ctx context.Context, tx *sql.Tx, c *models.ComputeClusterUser) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO compute_cluster_users (id, compute_cluster_id, user_id, local_username)
- VALUES (?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4)`,
c.ID, c.ComputeClusterID, c.UserID, c.LocalUsername)
return err
}
-func (s *mysqlComputeClusterUserStore) Update(ctx context.Context, tx *sql.Tx, c *models.ComputeClusterUser) error {
+func (s *pgComputeClusterUserStore) Update(ctx context.Context, tx *sql.Tx, c *models.ComputeClusterUser) error {
_, err := tx.ExecContext(ctx,
`UPDATE compute_cluster_users
- SET compute_cluster_id = ?,
- user_id = ?,
- local_username = ?
- WHERE id = ?`,
+ SET compute_cluster_id = $1,
+ user_id = $2,
+ local_username = $3
+ WHERE id = $4`,
c.ComputeClusterID, c.UserID, c.LocalUsername, c.ID)
return err
}
-func (s *mysqlComputeClusterUserStore) MarkProvisioned(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `UPDATE compute_cluster_users SET provisioned_at = NOW(6) WHERE id = ?`, id)
+func (s *pgComputeClusterUserStore) MarkProvisioned(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `UPDATE compute_cluster_users SET provisioned_at = NOW() WHERE id = $1`, id)
return err
}
-func (s *mysqlComputeClusterUserStore) ReassignUser(ctx context.Context, tx *sql.Tx, fromUserID, toUserID string) error {
+func (s *pgComputeClusterUserStore) ReassignUser(ctx context.Context, tx *sql.Tx, fromUserID, toUserID string) error {
if _, err := tx.ExecContext(ctx,
`DELETE FROM compute_cluster_users
- WHERE user_id = ?
+ WHERE user_id = $1
AND compute_cluster_id IN (
SELECT compute_cluster_id FROM (
- SELECT compute_cluster_id FROM compute_cluster_users WHERE user_id = ?
+ SELECT compute_cluster_id FROM compute_cluster_users WHERE user_id = $2
) AS s
)`,
fromUserID, toUserID); err != nil {
return err
}
_, err := tx.ExecContext(ctx,
- `UPDATE compute_cluster_users SET user_id = ? WHERE user_id = ?`,
+ `UPDATE compute_cluster_users SET user_id = $1 WHERE user_id = $2`,
toUserID, fromUserID)
return err
}
-func (s *mysqlComputeClusterUserStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM compute_cluster_users WHERE id = ?`, id)
+func (s *pgComputeClusterUserStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM compute_cluster_users WHERE id = $1`, id)
return err
}
diff --git a/internal/store/organization_store.go b/internal/store/organization_store.go
index 22bd7bc4d..753fd1825 100644
--- a/internal/store/organization_store.go
+++ b/internal/store/organization_store.go
@@ -27,19 +27,19 @@ import (
"github.com/apache/airavata-custos/pkg/models"
)
-type mysqlOrganizationStore struct {
+type pgOrganizationStore struct {
db *sqlx.DB
}
-// NewOrganizationStore returns a MySQL-backed OrganizationStore.
+// NewOrganizationStore returns a PostgreSQL-backed OrganizationStore.
func NewOrganizationStore(db *sqlx.DB) OrganizationStore {
- return &mysqlOrganizationStore{db: db}
+ return &pgOrganizationStore{db: db}
}
-func (s *mysqlOrganizationStore) FindByID(ctx context.Context, id string) (*models.Organization, error) {
+func (s *pgOrganizationStore) FindByID(ctx context.Context, id string) (*models.Organization, error) {
var o models.Organization
err := s.db.GetContext(ctx, &o,
- `SELECT id, originated_id, name FROM organizations WHERE id = ?`, id)
+ `SELECT id, originated_id, name FROM organizations WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -49,10 +49,10 @@ func (s *mysqlOrganizationStore) FindByID(ctx context.Context, id string) (*mode
return &o, nil
}
-func (s *mysqlOrganizationStore) FindByOriginatedID(ctx context.Context, originatedID string) (*models.Organization, error) {
+func (s *pgOrganizationStore) FindByOriginatedID(ctx context.Context, originatedID string) (*models.Organization, error) {
var o models.Organization
err := s.db.GetContext(ctx, &o,
- `SELECT id, originated_id, name FROM organizations WHERE originated_id = ?`, originatedID)
+ `SELECT id, originated_id, name FROM organizations WHERE originated_id = $1`, originatedID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -62,7 +62,7 @@ func (s *mysqlOrganizationStore) FindByOriginatedID(ctx context.Context, origina
return &o, nil
}
-func (s *mysqlOrganizationStore) List(ctx context.Context, limit, offset int) ([]models.Organization, int, error) {
+func (s *pgOrganizationStore) List(ctx context.Context, limit, offset int) ([]models.Organization, int, error) {
var total int
if err := s.db.GetContext(ctx, &total, `SELECT COUNT(*) FROM organizations`); err != nil {
return nil, 0, err
@@ -78,27 +78,27 @@ func (s *mysqlOrganizationStore) List(ctx context.Context, limit, offset int) ([
}
var rows []models.Organization
if err := s.db.SelectContext(ctx, &rows,
- `SELECT id, originated_id, name FROM organizations ORDER BY name LIMIT ? OFFSET ?`, limit, offset); err != nil {
+ `SELECT id, originated_id, name FROM organizations ORDER BY name LIMIT $1 OFFSET $2`, limit, offset); err != nil {
return nil, 0, err
}
return rows, total, nil
}
-func (s *mysqlOrganizationStore) Create(ctx context.Context, tx *sql.Tx, o *models.Organization) error {
+func (s *pgOrganizationStore) Create(ctx context.Context, tx *sql.Tx, o *models.Organization) error {
_, err := tx.ExecContext(ctx,
- `INSERT INTO organizations (id, originated_id, name) VALUES (?, ?, ?)`,
+ `INSERT INTO organizations (id, originated_id, name) VALUES ($1, $2, $3)`,
o.ID, o.OriginatedID, o.Name)
return err
}
-func (s *mysqlOrganizationStore) Update(ctx context.Context, tx *sql.Tx, o *models.Organization) error {
+func (s *pgOrganizationStore) Update(ctx context.Context, tx *sql.Tx, o *models.Organization) error {
_, err := tx.ExecContext(ctx,
- `UPDATE organizations SET originated_id = ?, name = ? WHERE id = ?`,
+ `UPDATE organizations SET originated_id = $1, name = $2 WHERE id = $3`,
o.OriginatedID, o.Name, o.ID)
return err
}
-func (s *mysqlOrganizationStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM organizations WHERE id = ?`, id)
+func (s *pgOrganizationStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM organizations WHERE id = $1`, id)
return err
}
diff --git a/internal/store/project_membership_store.go b/internal/store/project_membership_store.go
index 5373fe1b7..78105443b 100644
--- a/internal/store/project_membership_store.go
+++ b/internal/store/project_membership_store.go
@@ -29,21 +29,21 @@ import (
const projectMembershipColumns = "project_id, user_id, role, added_time"
-type mysqlProjectMembershipStore struct {
+type pgProjectMembershipStore struct {
db *sqlx.DB
}
-// NewProjectMembershipStore returns a MySQL-backed ProjectMembershipStore.
+// NewProjectMembershipStore returns a PostgreSQL-backed ProjectMembershipStore.
func NewProjectMembershipStore(db *sqlx.DB) ProjectMembershipStore {
- return &mysqlProjectMembershipStore{db: db}
+ return &pgProjectMembershipStore{db: db}
}
-func (s *mysqlProjectMembershipStore) FindByPair(ctx context.Context, projectID, userID string) (*models.ProjectMembership, error) {
+func (s *pgProjectMembershipStore) FindByPair(ctx context.Context, projectID, userID string) (*models.ProjectMembership, error) {
var pm models.ProjectMembership
err := s.db.GetContext(ctx, &pm,
`SELECT `+projectMembershipColumns+`
FROM project_memberships
- WHERE project_id = ? AND user_id = ?`, projectID, userID)
+ WHERE project_id = $1 AND user_id = $2`, projectID, userID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -53,12 +53,12 @@ func (s *mysqlProjectMembershipStore) FindByPair(ctx context.Context, projectID,
return &pm, nil
}
-func (s *mysqlProjectMembershipStore) FindByProject(ctx context.Context, projectID string) ([]models.ProjectMembership, error) {
+func (s *pgProjectMembershipStore) FindByProject(ctx context.Context, projectID string) ([]models.ProjectMembership, error) {
var rows []models.ProjectMembership
err := s.db.SelectContext(ctx, &rows,
`SELECT `+projectMembershipColumns+`
FROM project_memberships
- WHERE project_id = ?
+ WHERE project_id = $1
ORDER BY added_time`, projectID)
if err != nil {
return nil, err
@@ -67,12 +67,12 @@ func (s *mysqlProjectMembershipStore) FindByProject(ctx context.Context, project
}
// FindPIByProject returns the project's PI row when one exists.
-func (s *mysqlProjectMembershipStore) FindPIByProject(ctx context.Context, projectID string) (*models.ProjectMembership, error) {
+func (s *pgProjectMembershipStore) FindPIByProject(ctx context.Context, projectID string) (*models.ProjectMembership, error) {
var pm models.ProjectMembership
err := s.db.GetContext(ctx, &pm,
`SELECT `+projectMembershipColumns+`
FROM project_memberships
- WHERE project_id = ? AND role = 'PI'`, projectID)
+ WHERE project_id = $1 AND role = 'PI'`, projectID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -84,14 +84,14 @@ func (s *mysqlProjectMembershipStore) FindPIByProject(ctx context.Context, proje
// IsParticipant reports whether the user has a project_memberships row or an
// active membership on any of the project's allocations.
-func (s *mysqlProjectMembershipStore) IsParticipant(ctx context.Context, projectID, userID string) (bool, error) {
+func (s *pgProjectMembershipStore) IsParticipant(ctx context.Context, projectID, userID string) (bool, error) {
var participant bool
err := s.db.GetContext(ctx, &participant,
`SELECT EXISTS (SELECT 1 FROM project_memberships
- WHERE project_id = ? AND user_id = ?)
+ WHERE project_id = $1 AND user_id = $2)
OR EXISTS (SELECT 1 FROM compute_allocation_memberships cam
JOIN compute_allocations ca ON ca.id = cam.compute_allocation_id
- WHERE ca.project_id = ? AND cam.user_id = ?
+ WHERE ca.project_id = $3 AND cam.user_id = $4
AND cam.membership_status = 'ACTIVE')`,
projectID, userID, projectID, userID)
if err != nil {
@@ -100,24 +100,24 @@ func (s *mysqlProjectMembershipStore) IsParticipant(ctx context.Context, project
return participant, nil
}
-func (s *mysqlProjectMembershipStore) Create(ctx context.Context, tx *sql.Tx, pm *models.ProjectMembership) error {
+func (s *pgProjectMembershipStore) Create(ctx context.Context, tx *sql.Tx, pm *models.ProjectMembership) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO project_memberships (project_id, user_id, role, added_time)
- VALUES (?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4)`,
pm.ProjectID, pm.UserID, string(pm.Role), pm.AddedTime)
return err
}
-func (s *mysqlProjectMembershipStore) UpdateRole(ctx context.Context, tx *sql.Tx, projectID, userID string, role models.ProjectRole) error {
+func (s *pgProjectMembershipStore) UpdateRole(ctx context.Context, tx *sql.Tx, projectID, userID string, role models.ProjectRole) error {
_, err := tx.ExecContext(ctx,
- `UPDATE project_memberships SET role = ? WHERE project_id = ? AND user_id = ?`,
+ `UPDATE project_memberships SET role = $1 WHERE project_id = $2 AND user_id = $3`,
string(role), projectID, userID)
return err
}
-func (s *mysqlProjectMembershipStore) Delete(ctx context.Context, tx *sql.Tx, projectID, userID string) error {
+func (s *pgProjectMembershipStore) Delete(ctx context.Context, tx *sql.Tx, projectID, userID string) error {
_, err := tx.ExecContext(ctx,
- `DELETE FROM project_memberships WHERE project_id = ? AND user_id = ?`,
+ `DELETE FROM project_memberships WHERE project_id = $1 AND user_id = $2`,
projectID, userID)
return err
}
@@ -125,18 +125,18 @@ func (s *mysqlProjectMembershipStore) Delete(ctx context.Context, tx *sql.Tx, pr
// ReassignUser moves all project_memberships rows from fromUserID to toUserID.
// Rows that would collide on the survivor's PK (same project) are dropped
// from the retiring user first; remaining rows are then re-pointed.
-func (s *mysqlProjectMembershipStore) ReassignUser(ctx context.Context, tx *sql.Tx, fromUserID, toUserID string) error {
+func (s *pgProjectMembershipStore) ReassignUser(ctx context.Context, tx *sql.Tx, fromUserID, toUserID string) error {
if _, err := tx.ExecContext(ctx,
`DELETE FROM project_memberships
- WHERE user_id = ?
+ WHERE user_id = $1
AND project_id IN (SELECT project_id FROM (
- SELECT project_id FROM project_memberships WHERE user_id = ?
+ SELECT project_id FROM project_memberships WHERE user_id = $2
) AS s)`,
fromUserID, toUserID); err != nil {
return err
}
_, err := tx.ExecContext(ctx,
- `UPDATE project_memberships SET user_id = ? WHERE user_id = ?`,
+ `UPDATE project_memberships SET user_id = $1 WHERE user_id = $2`,
toUserID, fromUserID)
return err
}
diff --git a/internal/store/project_store.go b/internal/store/project_store.go
index 010d609c6..8845b1dbc 100644
--- a/internal/store/project_store.go
+++ b/internal/store/project_store.go
@@ -28,13 +28,13 @@ import (
"github.com/apache/airavata-custos/pkg/models"
)
-type mysqlProjectStore struct {
+type pgProjectStore struct {
db *sqlx.DB
}
-// NewProjectStore returns a MySQL-backed ProjectStore.
+// NewProjectStore returns a PostgreSQL-backed ProjectStore.
func NewProjectStore(db *sqlx.DB) ProjectStore {
- return &mysqlProjectStore{db: db}
+ return &pgProjectStore{db: db}
}
const projectColumns = `id, originated_id, title, origination, project_pi_id, status, created_time`
@@ -61,9 +61,9 @@ const projectWithPISelect = `SELECT p.id, p.originated_id, p.title, p.originatio
// FindByIDWithPI returns the project joined with its PI's display fields, or
// nil if no project matches.
-func (s *mysqlProjectStore) FindByIDWithPI(ctx context.Context, id string) (*ProjectWithPI, error) {
+func (s *pgProjectStore) FindByIDWithPI(ctx context.Context, id string) (*ProjectWithPI, error) {
var p ProjectWithPI
- err := s.db.GetContext(ctx, &p, projectWithPISelect+` WHERE p.id = ?`, id)
+ err := s.db.GetContext(ctx, &p, projectWithPISelect+` WHERE p.id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -73,10 +73,10 @@ func (s *mysqlProjectStore) FindByIDWithPI(ctx context.Context, id string) (*Pro
return &p, nil
}
-func (s *mysqlProjectStore) FindByID(ctx context.Context, id string) (*models.Project, error) {
+func (s *pgProjectStore) FindByID(ctx context.Context, id string) (*models.Project, error) {
var p models.Project
err := s.db.GetContext(ctx, &p,
- `SELECT `+projectColumns+` FROM projects WHERE id = ?`, id)
+ `SELECT `+projectColumns+` FROM projects WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -86,10 +86,10 @@ func (s *mysqlProjectStore) FindByID(ctx context.Context, id string) (*models.Pr
return &p, nil
}
-func (s *mysqlProjectStore) FindByOriginatedID(ctx context.Context, originatedID string) (*models.Project, error) {
+func (s *pgProjectStore) FindByOriginatedID(ctx context.Context, originatedID string) (*models.Project, error) {
var p models.Project
err := s.db.GetContext(ctx, &p,
- `SELECT `+projectColumns+` FROM projects WHERE originated_id = ?`, originatedID)
+ `SELECT `+projectColumns+` FROM projects WHERE originated_id = $1`, originatedID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -99,52 +99,52 @@ func (s *mysqlProjectStore) FindByOriginatedID(ctx context.Context, originatedID
return &p, nil
}
-func (s *mysqlProjectStore) FindByPI(ctx context.Context, piUserID string) ([]models.Project, error) {
+func (s *pgProjectStore) FindByPI(ctx context.Context, piUserID string) ([]models.Project, error) {
var projects []models.Project
err := s.db.SelectContext(ctx, &projects,
- `SELECT `+projectColumns+` FROM projects WHERE project_pi_id = ?`, piUserID)
+ `SELECT `+projectColumns+` FROM projects WHERE project_pi_id = $1`, piUserID)
if err != nil {
return nil, err
}
return projects, nil
}
-func (s *mysqlProjectStore) Create(ctx context.Context, tx *sql.Tx, p *models.Project) error {
+func (s *pgProjectStore) Create(ctx context.Context, tx *sql.Tx, p *models.Project) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO projects (id, originated_id, title, origination, project_pi_id, status, created_time)
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
p.ID, p.OriginatedID, p.Title, p.Origination, p.ProjectPIID, p.Status, p.CreatedTime)
return err
}
-func (s *mysqlProjectStore) Update(ctx context.Context, tx *sql.Tx, p *models.Project) error {
+func (s *pgProjectStore) Update(ctx context.Context, tx *sql.Tx, p *models.Project) error {
_, err := tx.ExecContext(ctx,
- `UPDATE projects SET originated_id = ?, title = ?, origination = ?, project_pi_id = ?, status = ?
- WHERE id = ?`,
+ `UPDATE projects SET originated_id = $1, title = $2, origination = $3, project_pi_id = $4, status = $5
+ WHERE id = $6`,
p.OriginatedID, p.Title, p.Origination, p.ProjectPIID, p.Status, p.ID)
return err
}
-func (s *mysqlProjectStore) UpdateStatus(ctx context.Context, tx *sql.Tx, id string, status models.ProjectStatus) error {
+func (s *pgProjectStore) UpdateStatus(ctx context.Context, tx *sql.Tx, id string, status models.ProjectStatus) error {
_, err := tx.ExecContext(ctx,
- `UPDATE projects SET status = ? WHERE id = ?`,
+ `UPDATE projects SET status = $1 WHERE id = $2`,
status, id)
return err
}
-func (s *mysqlProjectStore) ReassignPI(ctx context.Context, tx *sql.Tx, fromUserID, toUserID string) error {
+func (s *pgProjectStore) ReassignPI(ctx context.Context, tx *sql.Tx, fromUserID, toUserID string) error {
_, err := tx.ExecContext(ctx,
- `UPDATE projects SET project_pi_id = ? WHERE project_pi_id = ?`,
+ `UPDATE projects SET project_pi_id = $1 WHERE project_pi_id = $2`,
toUserID, fromUserID)
return err
}
-func (s *mysqlProjectStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM projects WHERE id = ?`, id)
+func (s *pgProjectStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM projects WHERE id = $1`, id)
return err
}
-func (s *mysqlProjectStore) List(ctx context.Context, f ProjectListFilter) ([]models.Project, int, error) {
+func (s *pgProjectStore) List(ctx context.Context, f ProjectListFilter) ([]models.Project, int, error) {
where := []string{}
args := []any{}
if f.PIID != "" {
@@ -156,7 +156,7 @@ func (s *mysqlProjectStore) List(ctx context.Context, f ProjectListFilter) ([]mo
args = append(args, f.Status)
}
if f.Query != "" {
- where = append(where, `(title LIKE ? OR originated_id LIKE ?)`)
+ where = append(where, `(title ILIKE ? OR originated_id ILIKE ?)`)
q := "%" + f.Query + "%"
args = append(args, q, q)
}
@@ -165,7 +165,7 @@ func (s *mysqlProjectStore) List(ctx context.Context, f ProjectListFilter) ([]mo
clause = " WHERE " + strings.Join(where, " AND ")
}
var total int
- if err := s.db.GetContext(ctx, &total, `SELECT COUNT(*) FROM projects`+clause, args...); err != nil {
+ if err := s.db.GetContext(ctx, &total, s.db.Rebind(`SELECT COUNT(*) FROM projects`+clause), args...); err != nil {
return nil, 0, err
}
limit := f.Limit
@@ -183,7 +183,7 @@ func (s *mysqlProjectStore) List(ctx context.Context, f ProjectListFilter) ([]mo
` ORDER BY created_time DESC LIMIT ? OFFSET ?`
args = append(args, limit, offset)
var rows []models.Project
- if err := s.db.SelectContext(ctx, &rows, query, args...); err != nil {
+ if err := s.db.SelectContext(ctx, &rows, s.db.Rebind(query), args...); err != nil {
return nil, 0, err
}
return rows, total, nil
@@ -192,14 +192,14 @@ func (s *mysqlProjectStore) List(ctx context.Context, f ProjectListFilter) ([]mo
// ListWithPIForParticipant returns the projects where the user holds a
// project membership or an active allocation membership, with the PI joined,
// newest first.
-func (s *mysqlProjectStore) ListWithPIForParticipant(ctx context.Context, userID string) ([]ProjectWithPI, error) {
+func (s *pgProjectStore) ListWithPIForParticipant(ctx context.Context, userID string) ([]ProjectWithPI, error) {
var rows []ProjectWithPI
err := s.db.SelectContext(ctx, &rows, projectWithPISelect+`
WHERE EXISTS (SELECT 1 FROM project_memberships pm
- WHERE pm.project_id = p.id AND pm.user_id = ?)
+ WHERE pm.project_id = p.id AND pm.user_id = $1)
OR EXISTS (SELECT 1 FROM compute_allocation_memberships cam
JOIN compute_allocations ca ON ca.id = cam.compute_allocation_id
- WHERE ca.project_id = p.id AND cam.user_id = ?
+ WHERE ca.project_id = p.id AND cam.user_id = $2
AND cam.membership_status = 'ACTIVE')
ORDER BY p.created_time DESC`, userID, userID)
if err != nil {
@@ -210,7 +210,7 @@ func (s *mysqlProjectStore) ListWithPIForParticipant(ctx context.Context, userID
// ListWithPI is List joined with the PI user. Replaces per-row GetUser calls
// the handler used to fan out across the result set.
-func (s *mysqlProjectStore) ListWithPI(ctx context.Context, f ProjectListFilter) ([]ProjectWithPI, int, error) {
+func (s *pgProjectStore) ListWithPI(ctx context.Context, f ProjectListFilter) ([]ProjectWithPI, int, error) {
where := []string{}
args := []any{}
if f.PIID != "" {
@@ -222,7 +222,7 @@ func (s *mysqlProjectStore) ListWithPI(ctx context.Context, f ProjectListFilter)
args = append(args, f.Status)
}
if f.Query != "" {
- where = append(where, `(p.title LIKE ? OR p.originated_id LIKE ?)`)
+ where = append(where, `(p.title ILIKE ? OR p.originated_id ILIKE ?)`)
q := "%" + f.Query + "%"
args = append(args, q, q)
}
@@ -231,7 +231,7 @@ func (s *mysqlProjectStore) ListWithPI(ctx context.Context, f ProjectListFilter)
clause = " WHERE " + strings.Join(where, " AND ")
}
var total int
- if err := s.db.GetContext(ctx, &total, `SELECT COUNT(*) FROM projects p`+clause, args...); err != nil {
+ if err := s.db.GetContext(ctx, &total, s.db.Rebind(`SELECT COUNT(*) FROM projects p`+clause), args...); err != nil {
return nil, 0, err
}
limit := f.Limit
@@ -248,7 +248,7 @@ func (s *mysqlProjectStore) ListWithPI(ctx context.Context, f ProjectListFilter)
query := projectWithPISelect + clause + ` ORDER BY p.created_time DESC LIMIT ? OFFSET ?`
args = append(args, limit, offset)
var rows []ProjectWithPI
- if err := s.db.SelectContext(ctx, &rows, query, args...); err != nil {
+ if err := s.db.SelectContext(ctx, &rows, s.db.Rebind(query), args...); err != nil {
return nil, 0, err
}
return rows, total, nil
diff --git a/internal/store/role_store.go b/internal/store/role_store.go
index 4d3f5d53d..f957763d6 100644
--- a/internal/store/role_store.go
+++ b/internal/store/role_store.go
@@ -29,18 +29,18 @@ import (
const roleColumns = "id, name, description, is_system, created_at"
-type mysqlRoleStore struct {
+type pgRoleStore struct {
db *sqlx.DB
}
func NewRoleStore(db *sqlx.DB) RoleStore {
- return &mysqlRoleStore{db: db}
+ return &pgRoleStore{db: db}
}
-func (s *mysqlRoleStore) FindByID(ctx context.Context, id string) (*models.Role, error) {
+func (s *pgRoleStore) FindByID(ctx context.Context, id string) (*models.Role, error) {
var r models.Role
err := s.db.GetContext(ctx, &r,
- `SELECT `+roleColumns+` FROM roles WHERE id = ?`, id)
+ `SELECT `+roleColumns+` FROM roles WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -50,10 +50,10 @@ func (s *mysqlRoleStore) FindByID(ctx context.Context, id string) (*models.Role,
return &r, nil
}
-func (s *mysqlRoleStore) FindByName(ctx context.Context, name string) (*models.Role, error) {
+func (s *pgRoleStore) FindByName(ctx context.Context, name string) (*models.Role, error) {
var r models.Role
err := s.db.GetContext(ctx, &r,
- `SELECT `+roleColumns+` FROM roles WHERE name = ?`, name)
+ `SELECT `+roleColumns+` FROM roles WHERE name = $1`, name)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -63,7 +63,7 @@ func (s *mysqlRoleStore) FindByName(ctx context.Context, name string) (*models.R
return &r, nil
}
-func (s *mysqlRoleStore) List(ctx context.Context) ([]models.Role, error) {
+func (s *pgRoleStore) List(ctx context.Context) ([]models.Role, error) {
var rows []models.Role
err := s.db.SelectContext(ctx, &rows,
`SELECT `+roleColumns+` FROM roles ORDER BY name`)
@@ -73,53 +73,53 @@ func (s *mysqlRoleStore) List(ctx context.Context) ([]models.Role, error) {
return rows, nil
}
-func (s *mysqlRoleStore) Create(ctx context.Context, tx *sql.Tx, r *models.Role) error {
+func (s *pgRoleStore) Create(ctx context.Context, tx *sql.Tx, r *models.Role) error {
_, err := tx.ExecContext(ctx,
- `INSERT INTO roles (id, name, description, is_system) VALUES (?, ?, ?, ?)`,
+ `INSERT INTO roles (id, name, description, is_system) VALUES ($1, $2, $3, $4)`,
r.ID, r.Name, r.Description, r.IsSystem)
return err
}
-func (s *mysqlRoleStore) Update(ctx context.Context, tx *sql.Tx, r *models.Role) error {
+func (s *pgRoleStore) Update(ctx context.Context, tx *sql.Tx, r *models.Role) error {
_, err := tx.ExecContext(ctx,
- `UPDATE roles SET name = ?, description = ? WHERE id = ?`,
+ `UPDATE roles SET name = $1, description = $2 WHERE id = $3`,
r.Name, r.Description, r.ID)
return err
}
-func (s *mysqlRoleStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM roles WHERE id = ?`, id)
+func (s *pgRoleStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM roles WHERE id = $1`, id)
return err
}
-func (s *mysqlRoleStore) ListPrivileges(ctx context.Context, roleID string) ([]models.PrivilegeKey, error) {
+func (s *pgRoleStore) ListPrivileges(ctx context.Context, roleID string) ([]models.PrivilegeKey, error) {
var keys []models.PrivilegeKey
err := s.db.SelectContext(ctx, &keys,
- `SELECT privilege FROM role_privileges WHERE role_id = ? ORDER BY privilege`, roleID)
+ `SELECT privilege FROM role_privileges WHERE role_id = $1 ORDER BY privilege`, roleID)
if err != nil {
return nil, err
}
return keys, nil
}
-func (s *mysqlRoleStore) AddPrivilege(ctx context.Context, tx *sql.Tx, roleID string, privilege models.PrivilegeKey) error {
+func (s *pgRoleStore) AddPrivilege(ctx context.Context, tx *sql.Tx, roleID string, privilege models.PrivilegeKey) error {
_, err := tx.ExecContext(ctx,
- `INSERT INTO role_privileges (role_id, privilege) VALUES (?, ?)`,
+ `INSERT INTO role_privileges (role_id, privilege) VALUES ($1, $2)`,
roleID, privilege)
return err
}
-func (s *mysqlRoleStore) RemovePrivilege(ctx context.Context, tx *sql.Tx, roleID string, privilege models.PrivilegeKey) error {
+func (s *pgRoleStore) RemovePrivilege(ctx context.Context, tx *sql.Tx, roleID string, privilege models.PrivilegeKey) error {
_, err := tx.ExecContext(ctx,
- `DELETE FROM role_privileges WHERE role_id = ? AND privilege = ?`,
+ `DELETE FROM role_privileges WHERE role_id = $1 AND privilege = $2`,
roleID, privilege)
return err
}
-func (s *mysqlRoleStore) HasPrivilege(ctx context.Context, tx *sql.Tx, roleID string, privilege models.PrivilegeKey) (bool, error) {
+func (s *pgRoleStore) HasPrivilege(ctx context.Context, tx *sql.Tx, roleID string, privilege models.PrivilegeKey) (bool, error) {
var n int
err := tx.QueryRowContext(ctx,
- `SELECT COUNT(*) FROM role_privileges WHERE role_id = ? AND privilege = ?`,
+ `SELECT COUNT(*) FROM role_privileges WHERE role_id = $1 AND privilege = $2`,
roleID, privilege).Scan(&n)
if err != nil {
return false, err
@@ -129,10 +129,10 @@ func (s *mysqlRoleStore) HasPrivilege(ctx context.Context, tx *sql.Tx, roleID st
// CountRolesGrantingPrivilege returns the number of roles carrying the
// given key. Used by the last-meta-holder guard.
-func (s *mysqlRoleStore) CountRolesGrantingPrivilege(ctx context.Context, tx *sql.Tx, privilege models.PrivilegeKey) (int, error) {
+func (s *pgRoleStore) CountRolesGrantingPrivilege(ctx context.Context, tx *sql.Tx, privilege models.PrivilegeKey) (int, error) {
var n int
err := tx.QueryRowContext(ctx,
- `SELECT COUNT(*) FROM role_privileges WHERE privilege = ?`,
+ `SELECT COUNT(*) FROM role_privileges WHERE privilege = $1`,
privilege).Scan(&n)
if err != nil {
return 0, err
diff --git a/internal/store/user_identity_store.go b/internal/store/user_identity_store.go
index a7143a1e4..54411b99d 100644
--- a/internal/store/user_identity_store.go
+++ b/internal/store/user_identity_store.go
@@ -27,20 +27,20 @@ import (
"github.com/apache/airavata-custos/pkg/models"
)
-type mysqlUserIdentityStore struct {
+type pgUserIdentityStore struct {
db *sqlx.DB
}
-// NewUserIdentityStore returns a MySQL-backed UserIdentityStore.
+// NewUserIdentityStore returns a PostgreSQL-backed UserIdentityStore.
func NewUserIdentityStore(db *sqlx.DB) UserIdentityStore {
- return &mysqlUserIdentityStore{db: db}
+ return &pgUserIdentityStore{db: db}
}
// email, oidc_sub and metadata are nullable; project NULL to "" for the model.
const userIdentityColumns = `id, user_id, source, external_id, COALESCE(email, '') AS email, COALESCE(oidc_sub, '') AS oidc_sub, COALESCE(metadata, '') AS metadata, created_at`
// nullableString returns nil when s is empty so the column stores SQL NULL
-// rather than "". NULL is the only value MySQL UNIQUE allows to repeat.
+// rather than "". NULL is the only value the UNIQUE index allows to repeat.
func nullableString(s string) any {
if s == "" {
return nil
@@ -48,10 +48,10 @@ func nullableString(s string) any {
return s
}
-func (s *mysqlUserIdentityStore) FindByID(ctx context.Context, id string) (*models.UserIdentity, error) {
+func (s *pgUserIdentityStore) FindByID(ctx context.Context, id string) (*models.UserIdentity, error) {
var e models.UserIdentity
err := s.db.GetContext(ctx, &e,
- `SELECT `+userIdentityColumns+` FROM user_identities WHERE id = ?`, id)
+ `SELECT `+userIdentityColumns+` FROM user_identities WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -61,10 +61,10 @@ func (s *mysqlUserIdentityStore) FindByID(ctx context.Context, id string) (*mode
return &e, nil
}
-func (s *mysqlUserIdentityStore) FindBySourceAndExternalID(ctx context.Context, source, externalID string) (*models.UserIdentity, error) {
+func (s *pgUserIdentityStore) FindBySourceAndExternalID(ctx context.Context, source, externalID string) (*models.UserIdentity, error) {
var e models.UserIdentity
err := s.db.GetContext(ctx, &e,
- `SELECT `+userIdentityColumns+` FROM user_identities WHERE source = ? AND external_id = ?`,
+ `SELECT `+userIdentityColumns+` FROM user_identities WHERE source = $1 AND external_id = $2`,
source, externalID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
@@ -75,13 +75,13 @@ func (s *mysqlUserIdentityStore) FindBySourceAndExternalID(ctx context.Context,
return &e, nil
}
-func (s *mysqlUserIdentityStore) FindByOIDCSub(ctx context.Context, oidcSub string) (*models.UserIdentity, error) {
+func (s *pgUserIdentityStore) FindByOIDCSub(ctx context.Context, oidcSub string) (*models.UserIdentity, error) {
if oidcSub == "" {
return nil, nil
}
var e models.UserIdentity
err := s.db.GetContext(ctx, &e,
- `SELECT `+userIdentityColumns+` FROM user_identities WHERE oidc_sub = ?`, oidcSub)
+ `SELECT `+userIdentityColumns+` FROM user_identities WHERE oidc_sub = $1`, oidcSub)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -91,10 +91,10 @@ func (s *mysqlUserIdentityStore) FindByOIDCSub(ctx context.Context, oidcSub stri
return &e, nil
}
-func (s *mysqlUserIdentityStore) FindByUser(ctx context.Context, userID string) ([]models.UserIdentity, error) {
+func (s *pgUserIdentityStore) FindByUser(ctx context.Context, userID string) ([]models.UserIdentity, error) {
var out []models.UserIdentity
err := s.db.SelectContext(ctx, &out,
- `SELECT `+userIdentityColumns+` FROM user_identities WHERE user_id = ? ORDER BY created_at ASC`,
+ `SELECT `+userIdentityColumns+` FROM user_identities WHERE user_id = $1 ORDER BY created_at ASC`,
userID)
if err != nil {
return nil, err
@@ -102,10 +102,10 @@ func (s *mysqlUserIdentityStore) FindByUser(ctx context.Context, userID string)
return out, nil
}
-func (s *mysqlUserIdentityStore) FindByUserAndSource(ctx context.Context, userID, source string) (*models.UserIdentity, error) {
+func (s *pgUserIdentityStore) FindByUserAndSource(ctx context.Context, userID, source string) (*models.UserIdentity, error) {
var e models.UserIdentity
err := s.db.GetContext(ctx, &e,
- `SELECT `+userIdentityColumns+` FROM user_identities WHERE user_id = ? AND source = ? LIMIT 1`, userID, source)
+ `SELECT `+userIdentityColumns+` FROM user_identities WHERE user_id = $1 AND source = $2 LIMIT 1`, userID, source)
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
}
@@ -115,31 +115,31 @@ func (s *mysqlUserIdentityStore) FindByUserAndSource(ctx context.Context, userID
return &e, nil
}
-func (s *mysqlUserIdentityStore) Create(ctx context.Context, tx *sql.Tx, e *models.UserIdentity) error {
+func (s *pgUserIdentityStore) Create(ctx context.Context, tx *sql.Tx, e *models.UserIdentity) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO user_identities (id, user_id, source, external_id, email, oidc_sub, metadata)
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
e.ID, e.UserID, e.Source, e.ExternalID, nullableString(e.Email), nullableString(e.OIDCSub), nullableString(e.Metadata))
return err
}
-func (s *mysqlUserIdentityStore) Update(ctx context.Context, tx *sql.Tx, e *models.UserIdentity) error {
+func (s *pgUserIdentityStore) Update(ctx context.Context, tx *sql.Tx, e *models.UserIdentity) error {
_, err := tx.ExecContext(ctx,
`UPDATE user_identities
- SET user_id = ?, source = ?, external_id = ?, email = ?, oidc_sub = ?, metadata = ?
- WHERE id = ?`,
+ SET user_id = $1, source = $2, external_id = $3, email = $4, oidc_sub = $5, metadata = $6
+ WHERE id = $7`,
e.UserID, e.Source, e.ExternalID, nullableString(e.Email), nullableString(e.OIDCSub), nullableString(e.Metadata), e.ID)
return err
}
-func (s *mysqlUserIdentityStore) ReassignUser(ctx context.Context, tx *sql.Tx, fromUserID, toUserID string) error {
+func (s *pgUserIdentityStore) ReassignUser(ctx context.Context, tx *sql.Tx, fromUserID, toUserID string) error {
_, err := tx.ExecContext(ctx,
- `UPDATE user_identities SET user_id = ? WHERE user_id = ?`,
+ `UPDATE user_identities SET user_id = $1 WHERE user_id = $2`,
toUserID, fromUserID)
return err
}
-func (s *mysqlUserIdentityStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM user_identities WHERE id = ?`, id)
+func (s *pgUserIdentityStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM user_identities WHERE id = $1`, id)
return err
}
diff --git a/internal/store/user_privilege_store.go b/internal/store/user_privilege_store.go
index 2ca42a84b..36d478b03 100644
--- a/internal/store/user_privilege_store.go
+++ b/internal/store/user_privilege_store.go
@@ -29,21 +29,21 @@ import (
const userPrivilegeColumns = "id, user_id, privilege, granted_by, granted_at, reason"
-type mysqlUserPrivilegeStore struct {
+type pgUserPrivilegeStore struct {
db *sqlx.DB
}
-// NewUserPrivilegeStore returns a MySQL-backed UserPrivilegeStore.
+// NewUserPrivilegeStore returns a PostgreSQL-backed UserPrivilegeStore.
func NewUserPrivilegeStore(db *sqlx.DB) UserPrivilegeStore {
- return &mysqlUserPrivilegeStore{db: db}
+ return &pgUserPrivilegeStore{db: db}
}
-func (s *mysqlUserPrivilegeStore) Find(ctx context.Context, userID string, privilege models.PrivilegeKey) (*models.UserPrivilege, error) {
+func (s *pgUserPrivilegeStore) Find(ctx context.Context, userID string, privilege models.PrivilegeKey) (*models.UserPrivilege, error) {
var r models.UserPrivilege
err := s.db.GetContext(ctx, &r,
`SELECT `+userPrivilegeColumns+`
FROM user_privileges
- WHERE user_id = ? AND privilege = ?`,
+ WHERE user_id = $1 AND privilege = $2`,
userID, privilege)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
@@ -54,11 +54,11 @@ func (s *mysqlUserPrivilegeStore) Find(ctx context.Context, userID string, privi
return &r, nil
}
-func (s *mysqlUserPrivilegeStore) FindForUpdate(ctx context.Context, tx *sql.Tx, userID string, privilege models.PrivilegeKey) (*models.UserPrivilege, error) {
+func (s *pgUserPrivilegeStore) FindForUpdate(ctx context.Context, tx *sql.Tx, userID string, privilege models.PrivilegeKey) (*models.UserPrivilege, error) {
row := tx.QueryRowContext(ctx,
`SELECT `+userPrivilegeColumns+`
FROM user_privileges
- WHERE user_id = ? AND privilege = ?
+ WHERE user_id = $1 AND privilege = $2
FOR UPDATE`,
userID, privilege)
var r models.UserPrivilege
@@ -74,12 +74,12 @@ func (s *mysqlUserPrivilegeStore) FindForUpdate(ctx context.Context, tx *sql.Tx,
return &r, nil
}
-func (s *mysqlUserPrivilegeStore) ListByUser(ctx context.Context, userID string) ([]models.UserPrivilege, error) {
+func (s *pgUserPrivilegeStore) ListByUser(ctx context.Context, userID string) ([]models.UserPrivilege, error) {
var rows []models.UserPrivilege
err := s.db.SelectContext(ctx, &rows,
`SELECT `+userPrivilegeColumns+`
FROM user_privileges
- WHERE user_id = ?
+ WHERE user_id = $1
ORDER BY granted_at`, userID)
if err != nil {
return nil, err
@@ -87,12 +87,12 @@ func (s *mysqlUserPrivilegeStore) ListByUser(ctx context.Context, userID string)
return rows, nil
}
-func (s *mysqlUserPrivilegeStore) ListByPrivilege(ctx context.Context, privilege models.PrivilegeKey) ([]models.UserPrivilege, error) {
+func (s *pgUserPrivilegeStore) ListByPrivilege(ctx context.Context, privilege models.PrivilegeKey) ([]models.UserPrivilege, error) {
var rows []models.UserPrivilege
err := s.db.SelectContext(ctx, &rows,
`SELECT `+userPrivilegeColumns+`
FROM user_privileges
- WHERE privilege = ?
+ WHERE privilege = $1
ORDER BY granted_at`, privilege)
if err != nil {
return nil, err
@@ -100,28 +100,28 @@ func (s *mysqlUserPrivilegeStore) ListByPrivilege(ctx context.Context, privilege
return rows, nil
}
-func (s *mysqlUserPrivilegeStore) CountByPrivilege(ctx context.Context, tx *sql.Tx, privilege models.PrivilegeKey) (int, error) {
+func (s *pgUserPrivilegeStore) CountByPrivilege(ctx context.Context, tx *sql.Tx, privilege models.PrivilegeKey) (int, error) {
var n int
err := tx.QueryRowContext(ctx,
- `SELECT COUNT(*) FROM user_privileges WHERE privilege = ?`, privilege).Scan(&n)
+ `SELECT COUNT(*) FROM user_privileges WHERE privilege = $1`, privilege).Scan(&n)
if err != nil {
return 0, err
}
return n, nil
}
-func (s *mysqlUserPrivilegeStore) Create(ctx context.Context, tx *sql.Tx, r *models.UserPrivilege) error {
+func (s *pgUserPrivilegeStore) Create(ctx context.Context, tx *sql.Tx, r *models.UserPrivilege) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO user_privileges
(id, user_id, privilege, granted_by, granted_at, reason)
- VALUES (?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6)`,
r.ID, r.UserID, r.Privilege, r.GrantedBy, r.GrantedAt, r.Reason)
return err
}
-func (s *mysqlUserPrivilegeStore) Delete(ctx context.Context, tx *sql.Tx, userID string, privilege models.PrivilegeKey) error {
+func (s *pgUserPrivilegeStore) Delete(ctx context.Context, tx *sql.Tx, userID string, privilege models.PrivilegeKey) error {
_, err := tx.ExecContext(ctx,
- `DELETE FROM user_privileges WHERE user_id = ? AND privilege = ?`,
+ `DELETE FROM user_privileges WHERE user_id = $1 AND privilege = $2`,
userID, privilege)
return err
}
diff --git a/internal/store/user_role_store.go b/internal/store/user_role_store.go
index 1ec5468a3..43e16843c 100644
--- a/internal/store/user_role_store.go
+++ b/internal/store/user_role_store.go
@@ -38,18 +38,18 @@ type UserRoleDetail struct {
const userRoleColumns = "user_id, role_id, granted_by, granted_at, reason"
-type mysqlUserRoleStore struct {
+type pgUserRoleStore struct {
db *sqlx.DB
}
func NewUserRoleStore(db *sqlx.DB) UserRoleStore {
- return &mysqlUserRoleStore{db: db}
+ return &pgUserRoleStore{db: db}
}
-func (s *mysqlUserRoleStore) Find(ctx context.Context, userID, roleID string) (*models.UserRole, error) {
+func (s *pgUserRoleStore) Find(ctx context.Context, userID, roleID string) (*models.UserRole, error) {
var r models.UserRole
err := s.db.GetContext(ctx, &r,
- `SELECT `+userRoleColumns+` FROM user_roles WHERE user_id = ? AND role_id = ?`,
+ `SELECT `+userRoleColumns+` FROM user_roles WHERE user_id = $1 AND role_id = $2`,
userID, roleID)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
@@ -60,9 +60,9 @@ func (s *mysqlUserRoleStore) Find(ctx context.Context, userID, roleID string) (*
return &r, nil
}
-func (s *mysqlUserRoleStore) FindForUpdate(ctx context.Context, tx *sql.Tx, userID, roleID string) (*models.UserRole, error) {
+func (s *pgUserRoleStore) FindForUpdate(ctx context.Context, tx *sql.Tx, userID, roleID string) (*models.UserRole, error) {
row := tx.QueryRowContext(ctx,
- `SELECT `+userRoleColumns+` FROM user_roles WHERE user_id = ? AND role_id = ? FOR UPDATE`,
+ `SELECT `+userRoleColumns+` FROM user_roles WHERE user_id = $1 AND role_id = $2 FOR UPDATE`,
userID, roleID)
var r models.UserRole
if err := row.Scan(&r.UserID, &r.RoleID, &r.GrantedBy, &r.GrantedAt, &r.Reason); err != nil {
@@ -74,10 +74,10 @@ func (s *mysqlUserRoleStore) FindForUpdate(ctx context.Context, tx *sql.Tx, user
return &r, nil
}
-func (s *mysqlUserRoleStore) ListByUser(ctx context.Context, userID string) ([]models.UserRole, error) {
+func (s *pgUserRoleStore) ListByUser(ctx context.Context, userID string) ([]models.UserRole, error) {
var rows []models.UserRole
err := s.db.SelectContext(ctx, &rows,
- `SELECT `+userRoleColumns+` FROM user_roles WHERE user_id = ? ORDER BY granted_at`,
+ `SELECT `+userRoleColumns+` FROM user_roles WHERE user_id = $1 ORDER BY granted_at`,
userID)
if err != nil {
return nil, err
@@ -85,10 +85,10 @@ func (s *mysqlUserRoleStore) ListByUser(ctx context.Context, userID string) ([]m
return rows, nil
}
-func (s *mysqlUserRoleStore) ListByRole(ctx context.Context, roleID string) ([]models.UserRole, error) {
+func (s *pgUserRoleStore) ListByRole(ctx context.Context, roleID string) ([]models.UserRole, error) {
var rows []models.UserRole
err := s.db.SelectContext(ctx, &rows,
- `SELECT `+userRoleColumns+` FROM user_roles WHERE role_id = ? ORDER BY granted_at`,
+ `SELECT `+userRoleColumns+` FROM user_roles WHERE role_id = $1 ORDER BY granted_at`,
roleID)
if err != nil {
return nil, err
@@ -96,10 +96,10 @@ func (s *mysqlUserRoleStore) ListByRole(ctx context.Context, roleID string) ([]m
return rows, nil
}
-func (s *mysqlUserRoleStore) ListUserIDsByRole(ctx context.Context, roleID string) ([]string, error) {
+func (s *pgUserRoleStore) ListUserIDsByRole(ctx context.Context, roleID string) ([]string, error) {
var ids []string
err := s.db.SelectContext(ctx, &ids,
- `SELECT user_id FROM user_roles WHERE role_id = ?`,
+ `SELECT user_id FROM user_roles WHERE role_id = $1`,
roleID)
if err != nil {
return nil, err
@@ -107,24 +107,24 @@ func (s *mysqlUserRoleStore) ListUserIDsByRole(ctx context.Context, roleID strin
return ids, nil
}
-func (s *mysqlUserRoleStore) Create(ctx context.Context, tx *sql.Tx, r *models.UserRole) error {
+func (s *pgUserRoleStore) Create(ctx context.Context, tx *sql.Tx, r *models.UserRole) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO user_roles (user_id, role_id, granted_by, granted_at, reason)
- VALUES (?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5)`,
r.UserID, r.RoleID, r.GrantedBy, r.GrantedAt, r.Reason)
return err
}
-func (s *mysqlUserRoleStore) Delete(ctx context.Context, tx *sql.Tx, userID, roleID string) error {
+func (s *pgUserRoleStore) Delete(ctx context.Context, tx *sql.Tx, userID, roleID string) error {
_, err := tx.ExecContext(ctx,
- `DELETE FROM user_roles WHERE user_id = ? AND role_id = ?`,
+ `DELETE FROM user_roles WHERE user_id = $1 AND role_id = $2`,
userID, roleID)
return err
}
// ListDetailedByUser returns the user's roles with their privileges and
// grant dates in one query.
-func (s *mysqlUserRoleStore) ListDetailedByUser(ctx context.Context, userID string) ([]UserRoleDetail, error) {
+func (s *pgUserRoleStore) ListDetailedByUser(ctx context.Context, userID string) ([]UserRoleDetail, error) {
var rows []struct {
models.Role
UserGrantedAt time.Time `db:"user_granted_at"`
@@ -136,7 +136,7 @@ func (s *mysqlUserRoleStore) ListDetailedByUser(ctx context.Context, userID stri
FROM user_roles ur
JOIN roles r ON r.id = ur.role_id
LEFT JOIN role_privileges rp ON rp.role_id = r.id
- WHERE ur.user_id = ?
+ WHERE ur.user_id = $1
ORDER BY r.name, rp.privilege`, userID)
if err != nil {
return nil, err
@@ -163,13 +163,13 @@ func (s *mysqlUserRoleStore) ListDetailedByUser(ctx context.Context, userID stri
// PrivilegesForUser returns the union of privileges from every role the
// user holds.
-func (s *mysqlUserRoleStore) PrivilegesForUser(ctx context.Context, userID string) ([]models.PrivilegeKey, error) {
+func (s *pgUserRoleStore) PrivilegesForUser(ctx context.Context, userID string) ([]models.PrivilegeKey, error) {
var keys []models.PrivilegeKey
err := s.db.SelectContext(ctx, &keys,
`SELECT DISTINCT rp.privilege
FROM user_roles ur
JOIN role_privileges rp ON rp.role_id = ur.role_id
- WHERE ur.user_id = ?`, userID)
+ WHERE ur.user_id = $1`, userID)
if err != nil {
return nil, err
}
@@ -178,13 +178,13 @@ func (s *mysqlUserRoleStore) PrivilegesForUser(ctx context.Context, userID strin
// UsersHoldingPrivilege returns user IDs that get this privilege via any
// role.
-func (s *mysqlUserRoleStore) UsersHoldingPrivilege(ctx context.Context, privilege models.PrivilegeKey) ([]string, error) {
+func (s *pgUserRoleStore) UsersHoldingPrivilege(ctx context.Context, privilege models.PrivilegeKey) ([]string, error) {
var ids []string
err := s.db.SelectContext(ctx, &ids,
`SELECT DISTINCT ur.user_id
FROM user_roles ur
JOIN role_privileges rp ON rp.role_id = ur.role_id
- WHERE rp.privilege = ?`, privilege)
+ WHERE rp.privilege = $1`, privilege)
if err != nil {
return nil, err
}
diff --git a/internal/store/user_store.go b/internal/store/user_store.go
index 1e7695286..6d4710b42 100644
--- a/internal/store/user_store.go
+++ b/internal/store/user_store.go
@@ -28,21 +28,21 @@ import (
"github.com/apache/airavata-custos/pkg/models"
)
-type mysqlUserStore struct {
+type pgUserStore struct {
db *sqlx.DB
}
-// NewUserStore returns a MySQL-backed UserStore.
+// NewUserStore returns a PostgreSQL-backed UserStore.
func NewUserStore(db *sqlx.DB) UserStore {
- return &mysqlUserStore{db: db}
+ return &pgUserStore{db: db}
}
const userColumns = `id, organization_id, first_name, last_name, middle_name, email, status, type`
-func (s *mysqlUserStore) FindByID(ctx context.Context, id string) (*models.User, error) {
+func (s *pgUserStore) FindByID(ctx context.Context, id string) (*models.User, error) {
var u models.User
err := s.db.GetContext(ctx, &u,
- `SELECT `+userColumns+` FROM users WHERE id = ?`, id)
+ `SELECT `+userColumns+` FROM users WHERE id = $1`, id)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -52,7 +52,7 @@ func (s *mysqlUserStore) FindByID(ctx context.Context, id string) (*models.User,
return &u, nil
}
-func (s *mysqlUserStore) List(ctx context.Context, limit, offset int) ([]models.User, int, error) {
+func (s *pgUserStore) List(ctx context.Context, limit, offset int) ([]models.User, int, error) {
var total int
if err := s.db.GetContext(ctx, &total, `SELECT COUNT(*) FROM users`); err != nil {
return nil, 0, err
@@ -68,16 +68,16 @@ func (s *mysqlUserStore) List(ctx context.Context, limit, offset int) ([]models.
}
var rows []models.User
if err := s.db.SelectContext(ctx, &rows,
- `SELECT `+userColumns+` FROM users ORDER BY email LIMIT ? OFFSET ?`, limit, offset); err != nil {
+ `SELECT `+userColumns+` FROM users ORDER BY email LIMIT $1 OFFSET $2`, limit, offset); err != nil {
return nil, 0, err
}
return rows, total, nil
}
-func (s *mysqlUserStore) FindByEmail(ctx context.Context, email string) (*models.User, error) {
+func (s *pgUserStore) FindByEmail(ctx context.Context, email string) (*models.User, error) {
var u models.User
err := s.db.GetContext(ctx, &u,
- `SELECT `+userColumns+` FROM users WHERE email = ?`, email)
+ `SELECT `+userColumns+` FROM users WHERE email = $1`, email)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -90,7 +90,7 @@ func (s *mysqlUserStore) FindByEmail(ctx context.Context, email string) (*models
// GetUserByOIDCSub returns the user owning the user_identities row whose
// oidc_sub matches. Returns nil when the OIDC subject is empty or no row
// links it to a Custos user.
-func (s *mysqlUserStore) GetUserByOIDCSub(ctx context.Context, oidcSub string) (*models.User, error) {
+func (s *pgUserStore) GetUserByOIDCSub(ctx context.Context, oidcSub string) (*models.User, error) {
if oidcSub == "" {
return nil, nil
}
@@ -99,7 +99,7 @@ func (s *mysqlUserStore) GetUserByOIDCSub(ctx context.Context, oidcSub string) (
`SELECT `+prefixed("u", userColumns)+`
FROM users u
JOIN user_identities ui ON ui.user_id = u.id
- WHERE ui.oidc_sub = ?`, oidcSub)
+ WHERE ui.oidc_sub = $1`, oidcSub)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return nil, nil
@@ -119,40 +119,40 @@ func prefixed(alias, columns string) string {
return strings.Join(parts, ", ")
}
-func (s *mysqlUserStore) FindByOrganization(ctx context.Context, organizationID string) ([]models.User, error) {
+func (s *pgUserStore) FindByOrganization(ctx context.Context, organizationID string) ([]models.User, error) {
var users []models.User
err := s.db.SelectContext(ctx, &users,
- `SELECT `+userColumns+` FROM users WHERE organization_id = ?`, organizationID)
+ `SELECT `+userColumns+` FROM users WHERE organization_id = $1`, organizationID)
if err != nil {
return nil, err
}
return users, nil
}
-func (s *mysqlUserStore) Create(ctx context.Context, tx *sql.Tx, u *models.User) error {
+func (s *pgUserStore) Create(ctx context.Context, tx *sql.Tx, u *models.User) error {
_, err := tx.ExecContext(ctx,
`INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`,
u.ID, u.OrganizationID, u.FirstName, u.LastName, u.MiddleName, u.Email, u.Status, u.Type)
return err
}
-func (s *mysqlUserStore) Update(ctx context.Context, tx *sql.Tx, u *models.User) error {
+func (s *pgUserStore) Update(ctx context.Context, tx *sql.Tx, u *models.User) error {
_, err := tx.ExecContext(ctx,
- `UPDATE users SET organization_id = ?, first_name = ?, last_name = ?, middle_name = ?, email = ?, status = ?, type = ?
- WHERE id = ?`,
+ `UPDATE users SET organization_id = $1, first_name = $2, last_name = $3, middle_name = $4, email = $5, status = $6, type = $7
+ WHERE id = $8`,
u.OrganizationID, u.FirstName, u.LastName, u.MiddleName, u.Email, u.Status, u.Type, u.ID)
return err
}
-func (s *mysqlUserStore) UpdateStatus(ctx context.Context, tx *sql.Tx, id string, status models.UserStatus) error {
+func (s *pgUserStore) UpdateStatus(ctx context.Context, tx *sql.Tx, id string, status models.UserStatus) error {
_, err := tx.ExecContext(ctx,
- `UPDATE users SET status = ? WHERE id = ?`,
+ `UPDATE users SET status = $1 WHERE id = $2`,
status, id)
return err
}
-func (s *mysqlUserStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
- _, err := tx.ExecContext(ctx, `DELETE FROM users WHERE id = ?`, id)
+func (s *pgUserStore) Delete(ctx context.Context, tx *sql.Tx, id string) error {
+ _, err := tx.ExecContext(ctx, `DELETE FROM users WHERE id = $1`, id)
return err
}
diff --git a/pkg/README.md b/pkg/README.md
index 4b159554a..6a2b8f72d 100644
--- a/pkg/README.md
+++ b/pkg/README.md
@@ -41,7 +41,7 @@ module and are not importable from outside the repository.
## Requirements
- Go 1.24+
-- MySQL 8+ or MariaDB 10.5+
+- PostgreSQL 17+
---
@@ -135,8 +135,8 @@ users (FK → organizations.id)
projects (FK → users.id via project_pi_id)
```
-`parseTime=true` must be set in the DSN so MySQL `TIMESTAMP` columns scan into
-`time.Time` correctly.
+The DSN is a PostgreSQL URL, e.g.
+`postgres://user:pass@localhost:5432/custos?sslmode=disable`.
## Testing with a mock service
diff --git a/pkg/service/identity_resolver_integration_test.go b/pkg/service/identity_resolver_integration_test.go
index fa05243f2..377a8c01e 100644
--- a/pkg/service/identity_resolver_integration_test.go
+++ b/pkg/service/identity_resolver_integration_test.go
@@ -34,7 +34,7 @@ func seedPendingUserWithEmail(t *testing.T, database *sqlx.DB, email string) str
orgID := seedOrg(t, database)
userID := uuid.NewString()
if _, err := database.Exec(
- "INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+ "INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
userID, orgID, "Test", "Pending", "", email, string(models.UserPending), string(models.UserTypeClusterLocal),
); err != nil {
t.Fatalf("seed pending user %s: %v", email, err)
@@ -45,7 +45,7 @@ func seedPendingUserWithEmail(t *testing.T, database *sqlx.DB, email string) str
func seedAMIEIdentity(t *testing.T, database *sqlx.DB, userID, email string) {
t.Helper()
if _, err := database.Exec(
- "INSERT INTO user_identities (id, user_id, source, external_id, email) VALUES (?, ?, ?, ?, ?)",
+ "INSERT INTO user_identities (id, user_id, source, external_id, email) VALUES ($1, $2, $3, $4, $5)",
uuid.NewString(), userID, "amie", "amie-"+userID[:8], email,
); err != nil {
t.Fatalf("seed AMIE identity: %v", err)
@@ -55,7 +55,7 @@ func seedAMIEIdentity(t *testing.T, database *sqlx.DB, userID, email string) {
func seedOIDCIdentity(t *testing.T, database *sqlx.DB, userID, sub, email string) {
t.Helper()
if _, err := database.Exec(
- "INSERT INTO user_identities (id, user_id, source, external_id, email, oidc_sub) VALUES (?, ?, ?, ?, ?, ?)",
+ "INSERT INTO user_identities (id, user_id, source, external_id, email, oidc_sub) VALUES ($1, $2, $3, $4, $5, $6)",
uuid.NewString(), userID, "oidc", sub, email, sub,
); err != nil {
t.Fatalf("seed OIDC identity: %v", err)
@@ -104,7 +104,7 @@ func TestResolveCaller_EmailFallback_HappyPath_LinksAndActivates(t *testing.T) {
}
// User should now be ACTIVE.
var status string
- if err := database.Get(&status, "SELECT status FROM users WHERE id = ?", user); err != nil {
+ if err := database.Get(&status, "SELECT status FROM users WHERE id = $1", user); err != nil {
t.Fatalf("read status: %v", err)
}
if status != string(models.UserActive) {
@@ -117,7 +117,7 @@ func TestResolveCaller_EmailFallback_HappyPath_LinksAndActivates(t *testing.T) {
// oidc_sub binding now in place.
var sub string
if err := database.Get(&sub,
- "SELECT oidc_sub FROM user_identities WHERE user_id = ? AND source = 'oidc'", user,
+ "SELECT oidc_sub FROM user_identities WHERE user_id = $1 AND source = 'oidc'", user,
); err != nil {
t.Fatalf("read binding: %v", err)
}
@@ -207,7 +207,7 @@ func TestResolveCaller_EmailFallback_LinksAMIEProvisioned(t *testing.T) {
}
// Both identities now coexist for this user: amie + oidc.
var count int
- if err := database.Get(&count, "SELECT COUNT(*) FROM user_identities WHERE user_id = ?", user); err != nil {
+ if err := database.Get(&count, "SELECT COUNT(*) FROM user_identities WHERE user_id = $1", user); err != nil {
t.Fatalf("count identities: %v", err)
}
if count != 2 {
@@ -228,7 +228,7 @@ func TestBootstrapSuperAdmin_CreatesPendingUserWhenMissing(t *testing.T) {
userID, status, userType, orgID string
)
if err := database.QueryRow(
- "SELECT id, status, type, organization_id FROM users WHERE email = ?", email,
+ "SELECT id, status, type, organization_id FROM users WHERE email = $1", email,
).Scan(&userID, &status, &userType, &orgID); err != nil {
t.Fatalf("read user: %v", err)
}
diff --git a/pkg/service/integration_common_test.go b/pkg/service/integration_common_test.go
index 34abc53c3..75ea95e81 100644
--- a/pkg/service/integration_common_test.go
+++ b/pkg/service/integration_common_test.go
@@ -22,6 +22,7 @@ package service
import (
"context"
"os"
+ "strings"
"sync"
"testing"
@@ -87,16 +88,8 @@ func truncateAll(t *testing.T, database *sqlx.DB) {
"users",
"organizations",
}
- if _, err := database.Exec("SET FOREIGN_KEY_CHECKS = 0"); err != nil {
- t.Fatalf("disable FK: %v", err)
- }
- for _, tbl := range tables {
- if _, err := database.Exec("TRUNCATE TABLE " + tbl); err != nil {
- t.Fatalf("truncate %s: %v", tbl, err)
- }
- }
- if _, err := database.Exec("SET FOREIGN_KEY_CHECKS = 1"); err != nil {
- t.Fatalf("re-enable FK: %v", err)
+ if _, err := database.Exec("TRUNCATE TABLE " + strings.Join(tables, ", ") + " CASCADE"); err != nil {
+ t.Fatalf("truncate: %v", err)
}
}
@@ -108,7 +101,7 @@ func seedOrg(t *testing.T, database *sqlx.DB) string {
t.Helper()
orgID := uuid.NewString()
if _, err := database.Exec(
- "INSERT INTO organizations (id, originated_id, name) VALUES (?, ?, ?)",
+ "INSERT INTO organizations (id, originated_id, name) VALUES ($1, $2, $3)",
orgID, "TEST-ORG-"+orgID[:8], "Test Org",
); err != nil {
t.Fatalf("seed org: %v", err)
@@ -121,7 +114,7 @@ func seedUser(t *testing.T, database *sqlx.DB, email string) string {
orgID := seedOrg(t, database)
userID := uuid.NewString()
if _, err := database.Exec(
- "INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+ "INSERT INTO users (id, organization_id, first_name, last_name, middle_name, email, status, type) VALUES ($1, $2, $3, $4, $5, $6, $7, $8)",
userID, orgID, "Test", "User", "", email, string(models.UserActive), string(models.UserTypeClusterLocal),
); err != nil {
t.Fatalf("seed user %s: %v", email, err)
@@ -136,7 +129,7 @@ func seedPrivilegesGrant(t *testing.T, database *sqlx.DB, userID string) {
t.Helper()
if _, err := database.Exec(
`INSERT INTO user_privileges (id, user_id, privilege, granted_at, reason)
- VALUES (?, ?, ?, NOW(6), 'seed')`,
+ VALUES ($1, $2, $3, NOW(), 'seed')`,
uuid.NewString(), userID, string(models.PrivilegesGrant),
); err != nil {
t.Fatalf("seed privileges:grant for %s: %v", userID, err)
@@ -147,7 +140,7 @@ func countAuditEventsOfType(t *testing.T, database *sqlx.DB, eventType, entityID
t.Helper()
var n int
if err := database.Get(&n,
- "SELECT COUNT(*) FROM audit_events WHERE event_type = ? AND entity_id = ?",
+ "SELECT COUNT(*) FROM audit_events WHERE event_type = $1 AND entity_id = $2",
eventType, entityID,
); err != nil {
t.Fatalf("count audit_events: %v", err)
diff --git a/pkg/service/role_integration_test.go b/pkg/service/role_integration_test.go
index 28386bbdc..cbbd5dfd6 100644
--- a/pkg/service/role_integration_test.go
+++ b/pkg/service/role_integration_test.go
@@ -188,7 +188,7 @@ func seedPrivilege(t *testing.T, database *sqlx.DB, userID string, p models.Priv
t.Helper()
if _, err := database.Exec(
`INSERT INTO user_privileges (id, user_id, privilege, granted_at, reason)
- VALUES (?, ?, ?, NOW(6), 'seed')`,
+ VALUES ($1, $2, $3, NOW(), 'seed')`,
uuid.NewString(), userID, string(p),
); err != nil {
t.Fatalf("seed privilege %s for %s: %v", p, userID, err)
diff --git a/pkg/service/service.go b/pkg/service/service.go
index 2c8b42996..b4ccdef65 100644
--- a/pkg/service/service.go
+++ b/pkg/service/service.go
@@ -62,7 +62,7 @@ type Service struct {
}
// New constructs a Service backed by the supplied database handle.
-// Stores are instantiated internally using the default MySQL implementations.
+// Stores are instantiated internally using the default PostgreSQL implementations.
func New(database *sqlx.DB, eventBus *events.Bus) *Service {
return &Service{
db: database,
diff --git a/pkg/service/user_privilege_integration_test.go b/pkg/service/user_privilege_integration_test.go
index 21556e262..415390a98 100644
--- a/pkg/service/user_privilege_integration_test.go
+++ b/pkg/service/user_privilege_integration_test.go
@@ -266,13 +266,13 @@ func TestSchema_RejectsTwoActivePrivilegesOfSameKey(t *testing.T) {
database := setupTestDB(t)
user := seedUser(t, database, "u@example.edu")
if _, err := database.Exec(
- `INSERT INTO user_privileges (id, user_id, privilege, granted_at) VALUES (?, ?, ?, NOW(6))`,
+ `INSERT INTO user_privileges (id, user_id, privilege, granted_at) VALUES ($1, $2, $3, NOW())`,
uuid.NewString(), user, string(models.ClustersRead),
); err != nil {
t.Fatalf("first insert: %v", err)
}
_, err := database.Exec(
- `INSERT INTO user_privileges (id, user_id, privilege, granted_at) VALUES (?, ?, ?, NOW(6))`,
+ `INSERT INTO user_privileges (id, user_id, privilege, granted_at) VALUES ($1, $2, $3, NOW())`,
uuid.NewString(), user, string(models.ClustersRead),
)
if err == nil {
diff --git a/pkg/service/user_role.go b/pkg/service/user_role.go
index 8feccbd8a..b5233a99c 100644
--- a/pkg/service/user_role.go
+++ b/pkg/service/user_role.go
@@ -343,11 +343,11 @@ func (s *Service) countUsersHoldingPrivilegeTx(ctx context.Context, tx *sql.Tx,
var n int
err := tx.QueryRowContext(ctx,
`SELECT COUNT(DISTINCT user_id) FROM (
- SELECT user_id FROM user_privileges WHERE privilege = ?
+ SELECT user_id FROM user_privileges WHERE privilege = $1
UNION
SELECT ur.user_id FROM user_roles ur
JOIN role_privileges rp ON rp.role_id = ur.role_id
- WHERE rp.privilege = ?
+ WHERE rp.privilege = $2
) AS holders`, key, key).Scan(&n)
return n, err
}
@@ -358,11 +358,11 @@ func (s *Service) userHasPrivilegeOutsideTx(ctx context.Context, tx *sql.Tx, use
var n int
err := tx.QueryRowContext(ctx,
`SELECT COUNT(*) FROM (
- SELECT 1 FROM user_privileges WHERE user_id = ? AND privilege = ?
+ SELECT 1 FROM user_privileges WHERE user_id = $1 AND privilege = $2
UNION ALL
SELECT 1 FROM user_roles ur
JOIN role_privileges rp ON rp.role_id = ur.role_id
- WHERE ur.user_id = ? AND ur.role_id <> ? AND rp.privilege = ?
+ WHERE ur.user_id = $3 AND ur.role_id <> $4 AND rp.privilege = $5
) AS sources`, userID, key, userID, excludeRoleID, key).Scan(&n)
if err != nil {
return false, err
diff --git a/scripts/run-amie-integration-tests.sh b/scripts/run-amie-integration-tests.sh
index fef437c2b..d6da369f2 100755
--- a/scripts/run-amie-integration-tests.sh
+++ b/scripts/run-amie-integration-tests.sh
@@ -30,7 +30,7 @@ trap cleanup EXIT
make -s -C dev-ops/local-amie down >/dev/null 2>&1 || true
make -s -C dev-ops/local-amie up
-export DATABASE_DSN="admin:admin@tcp(localhost:3307)/custos?parseTime=true&charset=utf8mb4&multiStatements=true"
+export DATABASE_DSN="postgres://admin:admin@localhost:5433/custos?sslmode=disable"
export AMIE_BASE_URL="http://localhost:8181"
export AMIE_SITE_CODE="TESTSITE"
export AMIE_API_KEY="dev"
(airavata-custos) 02/02: Migrate SSH certificate signer to PostgreSQL
Posted by la...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a commit to branch postgresql-migration
in repository https://gitbox.apache.org/repos/asf/airavata-custos.git
commit 32c7ac32c8d9686f87ed3744dd078ea7a44763ad
Author: lahiruj <la...@gmail.com>
AuthorDate: Fri Aug 21 16:33:19 2026 -0400
Migrate SSH certificate signer to PostgreSQL
---
extensions/SSH-Certificate-Signer/README.md | 8 +--
.../SSH-Certificate-Signer/config.example.yaml | 2 +-
extensions/SSH-Certificate-Signer/go.mod | 7 +-
extensions/SSH-Certificate-Signer/go.sum | 16 +++--
.../internal/config/config.go | 2 +-
.../internal/handler/sign.go | 9 ++-
.../internal/handler/sign_test.go | 11 +--
.../internal/store/certificate_query.go | 8 +--
.../internal/store/client_config.go | 2 +-
.../SSH-Certificate-Signer/internal/store/db.go | 8 +--
.../internal/store/issuance_log.go | 2 +-
.../internal/store/revocation.go | 2 +-
extensions/SSH-Certificate-Signer/main.go | 6 +-
.../migrations/001_initial_schema.up.sql | 84 +++++++++++-----------
14 files changed, 92 insertions(+), 75 deletions(-)
diff --git a/extensions/SSH-Certificate-Signer/README.md b/extensions/SSH-Certificate-Signer/README.md
index f81370048..39d10f899 100644
--- a/extensions/SSH-Certificate-Signer/README.md
+++ b/extensions/SSH-Certificate-Signer/README.md
@@ -28,7 +28,7 @@ Part of [Apache Airavata Custos](https://airavata.apache.org/custos/), a securit
## Prerequisites
- Go 1.22+
-- MariaDB 10.6+
+- PostgreSQL 17+
- Vault / OpenBao with KV v2 secrets engine enabled
---
@@ -86,7 +86,7 @@ Environment variables take precedence over YAML values.
### Create the database
```sql
-CREATE DATABASE custos_signer CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+CREATE DATABASE custos_signer;
```
If using the project's Docker Compose, the `custos_signer` database is created automatically by `compose/dbinit/init-db.sh`.
@@ -104,7 +104,7 @@ This applies any pending migrations and exits. It's idempotent — safe to run r
You can also apply SQL directly:
```bash
-mysql -u admin -p custos_signer < migrations/001_initial_schema.up.sql
+psql -U admin -d custos_signer < migrations/001_initial_schema.up.sql
```
The initial migration creates three tables:
@@ -280,7 +280,7 @@ curl -s -X POST http://localhost:8084/api/v1/admin/rotate-ca \
| Error | Cause | Fix |
|-------|-------|-----|
-| "failed to connect to database" | MariaDB unreachable or wrong credentials | Check host/port, verify DB user permissions, ensure database exists |
+| "failed to connect to database" | PostgreSQL unreachable or wrong credentials | Check host/port, verify DB user permissions, ensure database exists |
| "failed to create vault client" | Vault unreachable or bad token | Check `VAULT_ADDRESS`, ensure token is valid, verify KV v2 enabled |
| HTTP 401 on authenticated requests | Bad client credentials | Verify `X-Client-Id` is `tenant_id:client_id` format, check bcrypt hash matches |
| HTTP 403 "Client is disabled" | Client row has `enabled=false` | Set `enabled=true` in `client_ssh_configs` |
diff --git a/extensions/SSH-Certificate-Signer/config.example.yaml b/extensions/SSH-Certificate-Signer/config.example.yaml
index 0eaa603df..6814ef85c 100644
--- a/extensions/SSH-Certificate-Signer/config.example.yaml
+++ b/extensions/SSH-Certificate-Signer/config.example.yaml
@@ -23,7 +23,7 @@ server:
database:
host: localhost # env: DB_HOST
- port: 3306 # env: DB_PORT
+ port: 5432 # env: DB_PORT
name: custos_signer # env: DB_NAME
username: admin # env: DB_USERNAME
password: admin # env: DB_PASSWORD
diff --git a/extensions/SSH-Certificate-Signer/go.mod b/extensions/SSH-Certificate-Signer/go.mod
index 0f21c145a..72434e0ac 100644
--- a/extensions/SSH-Certificate-Signer/go.mod
+++ b/extensions/SSH-Certificate-Signer/go.mod
@@ -5,8 +5,8 @@ go 1.25.0
require (
github.com/go-chi/chi/v5 v5.2.4
github.com/go-ldap/ldap/v3 v3.4.13
- github.com/go-sql-driver/mysql v1.8.1
github.com/golang-migrate/migrate/v4 v4.19.1
+ github.com/jackc/pgx/v5 v5.10.0
github.com/lestrrat-go/jwx/v2 v2.1.3
github.com/openbao/openbao/api/v2 v2.5.1
github.com/prometheus/client_golang v1.20.5
@@ -16,7 +16,6 @@ require (
)
require (
- filippo.io/edwards25519 v1.1.1 // indirect
github.com/Azure/go-ntlmssp v0.1.1 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
@@ -35,6 +34,10 @@ require (
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
+ github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect
+ github.com/jackc/pgpassfile v1.0.0 // indirect
+ github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
+ github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/klauspost/compress v1.17.9 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
diff --git a/extensions/SSH-Certificate-Signer/go.sum b/extensions/SSH-Certificate-Signer/go.sum
index b3d067bfd..529897c5d 100644
--- a/extensions/SSH-Certificate-Signer/go.sum
+++ b/extensions/SSH-Certificate-Signer/go.sum
@@ -1,5 +1,3 @@
-filippo.io/edwards25519 v1.1.1 h1:YpjwWWlNmGIDyXOn8zLzqiD+9TyIlPhGFG96P39uBpw=
-filippo.io/edwards25519 v1.1.1/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Azure/go-ntlmssp v0.1.1 h1:l+FM/EEMb0U9QZE7mKNEDw5Mu3mFiaa2GKOoTSsNDPw=
@@ -51,8 +49,6 @@ github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
-github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
-github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
github.com/go-test/deep v1.1.1 h1:0r/53hagsehfO4bzD2Pgr/+RgHqhmf+k1Bpse2cTu1U=
github.com/go-test/deep v1.1.1/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE=
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
@@ -88,6 +84,16 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y6xGI0I=
github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM=
+github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw=
+github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
+github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
+github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
+github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
+github.com/jackc/pgx/v5 v5.10.0 h1:VhSvgU2jSli8o3AqIEOTJr7rZwAEUVo4E4XhR94Zfr0=
+github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4=
+github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
+github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jcmturner/aescts/v2 v2.0.0 h1:9YKLH6ey7H4eDBXW8khjYslgyqG2xZikXP0EQFKrle8=
github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs=
github.com/jcmturner/dnsutils/v2 v2.0.0 h1:lltnkeZGL0wILNvrNiVCR6Ro5PGU/SeBvVO/8c/iPbo=
@@ -162,7 +168,9 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH
github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
diff --git a/extensions/SSH-Certificate-Signer/internal/config/config.go b/extensions/SSH-Certificate-Signer/internal/config/config.go
index 29a1c64e9..3bc329b11 100644
--- a/extensions/SSH-Certificate-Signer/internal/config/config.go
+++ b/extensions/SSH-Certificate-Signer/internal/config/config.go
@@ -135,7 +135,7 @@ func DefaultConfig() *Config {
},
Database: DatabaseConfig{
Host: "localhost",
- Port: 3306,
+ Port: 5432,
Name: "custos_signer",
Username: "admin",
Password: "admin",
diff --git a/extensions/SSH-Certificate-Signer/internal/handler/sign.go b/extensions/SSH-Certificate-Signer/internal/handler/sign.go
index 885fbcab4..c34bec1c1 100644
--- a/extensions/SSH-Certificate-Signer/internal/handler/sign.go
+++ b/extensions/SSH-Certificate-Signer/internal/handler/sign.go
@@ -18,12 +18,14 @@ package handler
import (
"encoding/base64"
"encoding/json"
+ "errors"
"log/slog"
"net/http"
"regexp"
- "strings"
"time"
+ "github.com/jackc/pgx/v5/pgconn"
+
"github.com/apache/airavata-custos/signer/internal/audit"
"github.com/apache/airavata-custos/signer/internal/auth"
"github.com/apache/airavata-custos/signer/internal/cert"
@@ -311,9 +313,10 @@ func (h *SignHandler) Handle(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(resp)
}
-// isDuplicateKeyError detects MySQL error 1062 (duplicate key constraint).
+// isDuplicateKeyError detects a unique-constraint violation (SQLSTATE 23505).
func isDuplicateKeyError(err error) bool {
- return err != nil && strings.Contains(err.Error(), "Duplicate entry")
+ var pgErr *pgconn.PgError
+ return errors.As(err, &pgErr) && pgErr.Code == "23505"
}
func stringPtrIfNonEmpty(s string) *string {
diff --git a/extensions/SSH-Certificate-Signer/internal/handler/sign_test.go b/extensions/SSH-Certificate-Signer/internal/handler/sign_test.go
index 14b094262..c9f944ac1 100644
--- a/extensions/SSH-Certificate-Signer/internal/handler/sign_test.go
+++ b/extensions/SSH-Certificate-Signer/internal/handler/sign_test.go
@@ -17,8 +17,11 @@ package handler
import (
"errors"
+ "fmt"
"regexp"
"testing"
+
+ "github.com/jackc/pgx/v5/pgconn"
)
func TestPrincipalRegex(t *testing.T) {
@@ -68,10 +71,10 @@ func TestIsDuplicateKeyError(t *testing.T) {
}{
{"nil error", nil, false},
{"generic error", errors.New("connection refused"), false},
- {"mysql duplicate entry", errors.New("Error 1062 (23000): Duplicate entry '42' for key 'serial_number'"), true},
- {"wrapped duplicate entry", errors.New("failed to insert: Duplicate entry '99' for key 'PRIMARY'"), true},
- {"partial match", errors.New("Duplicate entry"), true},
- {"case sensitive no match", errors.New("duplicate entry"), false},
+ {"unique violation", &pgconn.PgError{Code: "23505", Message: "duplicate key value violates unique constraint \"serial_number\""}, true},
+ {"wrapped unique violation", fmt.Errorf("failed to insert: %w", &pgconn.PgError{Code: "23505"}), true},
+ {"other pg error", &pgconn.PgError{Code: "23503", Message: "foreign key violation"}, false},
+ {"plain message no match", errors.New("duplicate key value violates unique constraint"), false},
}
for _, tt := range tests {
diff --git a/extensions/SSH-Certificate-Signer/internal/store/certificate_query.go b/extensions/SSH-Certificate-Signer/internal/store/certificate_query.go
index 5ceb864c7..e64cb7d49 100644
--- a/extensions/SSH-Certificate-Signer/internal/store/certificate_query.go
+++ b/extensions/SSH-Certificate-Signer/internal/store/certificate_query.go
@@ -65,7 +65,7 @@ func (d *DB) ListCertificatesByEmail(ctx context.Context, email string, limit, o
// Count total matching certificates
var total int
err := d.QueryRowContext(ctx,
- `SELECT COUNT(*) FROM certificate_issuance_logs WHERE user_email = ?`,
+ `SELECT COUNT(*) FROM certificate_issuance_logs WHERE user_email = $1`,
email,
).Scan(&total)
if err != nil {
@@ -84,9 +84,9 @@ func (d *DB) ListCertificatesByEmail(ctx context.Context, email string, limit, o
COALESCE(r.reason, '') AS revocation_reason
FROM certificate_issuance_logs c
LEFT JOIN revocation_events r ON r.serial_number = c.serial_number
- WHERE c.user_email = ?
+ WHERE c.user_email = $1
ORDER BY c.issued_at DESC
- LIMIT ? OFFSET ?`,
+ LIMIT $2 OFFSET $3`,
email, limit, offset,
)
if err != nil {
@@ -150,7 +150,7 @@ func (d *DB) GetCertificateBySerial(ctx context.Context, serial int64) (*Certifi
COALESCE(r.reason, '') AS revocation_reason
FROM certificate_issuance_logs c
LEFT JOIN revocation_events r ON r.serial_number = c.serial_number
- WHERE c.serial_number = ?`,
+ WHERE c.serial_number = $1`,
serial,
).Scan(
&cert.ID, &cert.TenantID, &cert.ClientID, &cert.SerialNumber, &cert.KeyID,
diff --git a/extensions/SSH-Certificate-Signer/internal/store/client_config.go b/extensions/SSH-Certificate-Signer/internal/store/client_config.go
index 0b29d0380..518770c44 100644
--- a/extensions/SSH-Certificate-Signer/internal/store/client_config.go
+++ b/extensions/SSH-Certificate-Signer/internal/store/client_config.go
@@ -49,7 +49,7 @@ func (d *DB) GetClientConfig(ctx context.Context, tenantID, clientID string) (*C
max_ttl_seconds, allowed_key_types, source_address_restriction,
denied_extensions, principal_source, enabled
FROM client_ssh_configs
- WHERE tenant_id = ? AND client_id = ?`,
+ WHERE tenant_id = $1 AND client_id = $2`,
tenantID, clientID,
).Scan(
&cc.TenantID, &cc.ClientID, &cc.ClientSecret, &cc.TargetHost, &cc.TargetPort,
diff --git a/extensions/SSH-Certificate-Signer/internal/store/db.go b/extensions/SSH-Certificate-Signer/internal/store/db.go
index ec3b656b9..a635ca5f7 100644
--- a/extensions/SSH-Certificate-Signer/internal/store/db.go
+++ b/extensions/SSH-Certificate-Signer/internal/store/db.go
@@ -21,7 +21,7 @@ import (
"fmt"
"time"
- _ "github.com/go-sql-driver/mysql"
+ _ "github.com/jackc/pgx/v5/stdlib"
"github.com/apache/airavata-custos/signer/internal/config"
)
@@ -31,10 +31,10 @@ type DB struct {
}
func NewDB(cfg config.DatabaseConfig) (*DB, error) {
- dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&loc=UTC&charset=utf8mb4",
- cfg.Username, cfg.Password, cfg.Host, cfg.Port, cfg.Name)
+ dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
+ cfg.Host, cfg.Port, cfg.Username, cfg.Password, cfg.Name)
- db, err := sql.Open("mysql", dsn)
+ db, err := sql.Open("pgx", dsn)
if err != nil {
return nil, fmt.Errorf("opening database: %w", err)
}
diff --git a/extensions/SSH-Certificate-Signer/internal/store/issuance_log.go b/extensions/SSH-Certificate-Signer/internal/store/issuance_log.go
index f9ef4ef51..2a0183fb3 100644
--- a/extensions/SSH-Certificate-Signer/internal/store/issuance_log.go
+++ b/extensions/SSH-Certificate-Signer/internal/store/issuance_log.go
@@ -64,7 +64,7 @@ func (d *DB) InsertIssuanceLog(ctx context.Context, log *IssuanceLog) error {
(tenant_id, client_id, serial_number, key_id, principal, user_email, public_key_fingerprint,
ca_fingerprint, valid_after, valid_before, source_ip, granted_extensions, force_command,
user_access_token_hash, request_metadata)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)`,
log.TenantID, log.ClientID, log.SerialNumber, log.KeyID, log.Principal,
log.UserEmail, log.PublicKeyFingerprint, log.CAFingerprint, log.ValidAfter, log.ValidBefore,
log.SourceIP, grantedExtensionsJSON, log.ForceCommand, log.UserAccessTokenHash, metadataJSON,
diff --git a/extensions/SSH-Certificate-Signer/internal/store/revocation.go b/extensions/SSH-Certificate-Signer/internal/store/revocation.go
index f625e3547..53f548915 100644
--- a/extensions/SSH-Certificate-Signer/internal/store/revocation.go
+++ b/extensions/SSH-Certificate-Signer/internal/store/revocation.go
@@ -48,7 +48,7 @@ func (d *DB) InsertRevocationEvent(ctx context.Context, ev *RevocationEvent) err
_, err := d.ExecContext(ctx,
`INSERT INTO revocation_events
(tenant_id, client_id, serial_number, key_id, ca_fingerprint, reason, revoked_by)
- VALUES (?, ?, ?, ?, ?, ?, ?)`,
+ VALUES ($1, $2, $3, $4, $5, $6, $7)`,
ev.TenantID, ev.ClientID, serialNumber, keyID, caFingerprint, ev.Reason, ev.RevokedBy,
)
if err != nil {
diff --git a/extensions/SSH-Certificate-Signer/main.go b/extensions/SSH-Certificate-Signer/main.go
index 1e8fd6e82..9abb86554 100644
--- a/extensions/SSH-Certificate-Signer/main.go
+++ b/extensions/SSH-Certificate-Signer/main.go
@@ -33,7 +33,7 @@ import (
"github.com/apache/airavata-custos/signer/internal/validation"
"github.com/apache/airavata-custos/signer/internal/vault"
"github.com/golang-migrate/migrate/v4"
- _ "github.com/golang-migrate/migrate/v4/database/mysql"
+ _ "github.com/golang-migrate/migrate/v4/database/pgx/v5"
_ "github.com/golang-migrate/migrate/v4/source/file"
)
@@ -107,10 +107,10 @@ func main() {
// Applies pending migrations and exits.
// Invoked by the "migrate" subcommand and also reused by --auto-migrate in serve mode.
func runMigrate(cfg *config.Config, logger *slog.Logger) {
- dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s",
+ dsn := fmt.Sprintf("pgx5://%s:%s@%s:%d/%s?sslmode=disable",
cfg.Database.Username, cfg.Database.Password,
cfg.Database.Host, cfg.Database.Port, cfg.Database.Name)
- m, err := migrate.New("file://migrations", "mysql://"+dsn)
+ m, err := migrate.New("file://migrations", dsn)
if err != nil {
logger.Error("failed to create migrate instance", "error", err)
os.Exit(1)
diff --git a/extensions/SSH-Certificate-Signer/migrations/001_initial_schema.up.sql b/extensions/SSH-Certificate-Signer/migrations/001_initial_schema.up.sql
index 649e3a4d0..9c0bfc5ed 100644
--- a/extensions/SSH-Certificate-Signer/migrations/001_initial_schema.up.sql
+++ b/extensions/SSH-Certificate-Signer/migrations/001_initial_schema.up.sql
@@ -15,9 +15,6 @@
-- specific language governing permissions and limitations
-- under the License.
-SET NAMES utf8mb4;
-SET time_zone = '+00:00';
-
CREATE TABLE IF NOT EXISTS client_ssh_configs
(
tenant_id VARCHAR(255) NOT NULL,
@@ -26,25 +23,22 @@ CREATE TABLE IF NOT EXISTS client_ssh_configs
target_host VARCHAR(255) NOT NULL,
target_port INT NOT NULL DEFAULT 22,
max_ttl_seconds INT NOT NULL DEFAULT 86400,
- allowed_key_types JSON NOT NULL,
+ allowed_key_types JSONB NOT NULL,
source_address_restriction VARCHAR(255) NULL,
- denied_extensions JSON NULL,
+ denied_extensions JSONB NULL,
principal_source VARCHAR(20) NOT NULL DEFAULT 'noop',
enabled BOOLEAN NOT NULL DEFAULT TRUE,
- created_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- updated_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
- ON UPDATE CURRENT_TIMESTAMP(6),
+ created_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ updated_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
- PRIMARY KEY (tenant_id, client_id),
- INDEX idx_client_configs_enabled (enabled),
- INDEX idx_client_configs_target_host (target_host)
-) ENGINE = InnoDB
- DEFAULT CHARSET = utf8mb4
- COLLATE = utf8mb4_unicode_ci;
+ PRIMARY KEY (tenant_id, client_id)
+);
+CREATE INDEX IF NOT EXISTS idx_client_configs_enabled ON client_ssh_configs (enabled);
+CREATE INDEX IF NOT EXISTS idx_client_configs_target_host ON client_ssh_configs (target_host);
CREATE TABLE IF NOT EXISTS certificate_issuance_logs
(
- id BIGINT NOT NULL AUTO_INCREMENT,
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY,
tenant_id VARCHAR(255) NOT NULL,
client_id VARCHAR(255) NOT NULL,
serial_number BIGINT NOT NULL,
@@ -53,46 +47,52 @@ CREATE TABLE IF NOT EXISTS certificate_issuance_logs
user_email VARCHAR(255) NULL,
public_key_fingerprint VARCHAR(255) NOT NULL,
ca_fingerprint VARCHAR(255) NOT NULL,
- valid_after TIMESTAMP(6) NOT NULL,
- valid_before TIMESTAMP(6) NOT NULL,
- issued_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ valid_after TIMESTAMPTZ(6) NOT NULL,
+ valid_before TIMESTAMPTZ(6) NOT NULL,
+ issued_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
source_ip VARCHAR(45) NULL,
- granted_extensions JSON NOT NULL,
+ granted_extensions JSONB NOT NULL,
force_command TEXT NULL,
user_access_token_hash VARCHAR(255) NULL,
- request_metadata JSON NULL,
+ request_metadata JSONB NULL,
PRIMARY KEY (id),
- UNIQUE KEY uq_issuance_serial (serial_number),
- INDEX idx_issuance_tenant_client (tenant_id, client_id),
- INDEX idx_issuance_keyid (key_id),
- INDEX idx_issuance_principal (principal),
- INDEX idx_issuance_user_email (user_email),
- INDEX idx_issuance_ca_fingerprint (ca_fingerprint),
- INDEX idx_issuance_issued_at (issued_at),
- INDEX idx_issuance_valid_range (valid_after, valid_before)
-) ENGINE = InnoDB
- DEFAULT CHARSET = utf8mb4
- COLLATE = utf8mb4_unicode_ci;
+ CONSTRAINT uq_issuance_serial UNIQUE (serial_number)
+);
+CREATE INDEX IF NOT EXISTS idx_issuance_tenant_client ON certificate_issuance_logs (tenant_id, client_id);
+CREATE INDEX IF NOT EXISTS idx_issuance_keyid ON certificate_issuance_logs (key_id);
+CREATE INDEX IF NOT EXISTS idx_issuance_principal ON certificate_issuance_logs (principal);
+CREATE INDEX IF NOT EXISTS idx_issuance_user_email ON certificate_issuance_logs (user_email);
+CREATE INDEX IF NOT EXISTS idx_issuance_ca_fingerprint ON certificate_issuance_logs (ca_fingerprint);
+CREATE INDEX IF NOT EXISTS idx_issuance_issued_at ON certificate_issuance_logs (issued_at);
+CREATE INDEX IF NOT EXISTS idx_issuance_valid_range ON certificate_issuance_logs (valid_after, valid_before);
CREATE TABLE IF NOT EXISTS revocation_events
(
- id BIGINT NOT NULL AUTO_INCREMENT,
+ id BIGINT GENERATED BY DEFAULT AS IDENTITY,
tenant_id VARCHAR(255) NOT NULL,
client_id VARCHAR(255) NOT NULL,
serial_number BIGINT NULL,
key_id VARCHAR(255) NULL,
ca_fingerprint VARCHAR(255) NULL,
- revoked_at TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
+ revoked_at TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
reason VARCHAR(255) NOT NULL,
revoked_by VARCHAR(255) NOT NULL,
- PRIMARY KEY (id),
- INDEX idx_revocation_tenant_client (tenant_id, client_id),
- INDEX idx_revocation_serial (serial_number),
- INDEX idx_revocation_keyid (key_id),
- INDEX idx_revocation_ca_fingerprint (ca_fingerprint),
- INDEX idx_revocation_revoked_at (revoked_at)
-) ENGINE = InnoDB
- DEFAULT CHARSET = utf8mb4
- COLLATE = utf8mb4_unicode_ci;
+ PRIMARY KEY (id)
+);
+CREATE INDEX IF NOT EXISTS idx_revocation_tenant_client ON revocation_events (tenant_id, client_id);
+CREATE INDEX IF NOT EXISTS idx_revocation_serial ON revocation_events (serial_number);
+CREATE INDEX IF NOT EXISTS idx_revocation_keyid ON revocation_events (key_id);
+CREATE INDEX IF NOT EXISTS idx_revocation_ca_fingerprint ON revocation_events (ca_fingerprint);
+CREATE INDEX IF NOT EXISTS idx_revocation_revoked_at ON revocation_events (revoked_at);
+
+CREATE OR REPLACE FUNCTION set_updated_at() RETURNS TRIGGER AS $$
+BEGIN
+ NEW.updated_at = NOW();
+ RETURN NEW;
+END $$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE TRIGGER trg_client_ssh_configs_updated_at
+ BEFORE UPDATE ON client_ssh_configs
+ FOR EACH ROW EXECUTE FUNCTION set_updated_at();