Skip to content

Commit

Permalink
Create experimental ability to provision Postgres helper functions
Browse files Browse the repository at this point in the history
Towards NSL-5018
  • Loading branch information
n-g committed Jan 10, 2025
1 parent 31ecdf7 commit 44bb73b
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 10 deletions.
49 changes: 49 additions & 0 deletions library/oss/postgres/prepare/database/functions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
-- Collection of optional helper functions
-- To provision these functions add
-- provision_helper_functions: true
-- to the database intent.

-- `CREATE TABLE IF NOT EXISTS` and `ALTER TABLE … ADD COLUMN IF NOT EXISTS`
-- both require exclusive locks with Postgres, even if the table/column already exists.
-- The functions below provide ensure semantics while only acquiring exclusive locks on mutations.

-- fn_ensure_table is a lock-friendly replacement for `CREATE TABLE IF NOT EXISTS`.
--
-- Example usage:
--
-- SELECT fn_ensure_table('testtable', $$
-- UserID TEXT NOT NULL,
-- PRIMARY KEY(UserID)
-- $$);
CREATE OR REPLACE FUNCTION fn_ensure_table(tname TEXT, def TEXT)
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_tables
WHERE schemaname = 'public' AND tablename = LOWER(tname)
) THEN
EXECUTE 'CREATE TABLE IF NOT EXISTS ' || tname || ' (' || def || ');';
END IF;
END
$func$;

-- fn_ensure_column is a lock-friendly replacement for `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`.
--
-- Example usage:
--
-- SELECT fn_ensure_column('testtable', 'CreatedAt', 'TIMESTAMP DEFAULT CURRENT_TIMESTAMP');
CREATE OR REPLACE FUNCTION fn_ensure_column(tname TEXT, cname TEXT, def TEXT)
RETURNS void
LANGUAGE plpgsql AS
$func$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.columns
WHERE table_name = LOWER(tname) AND column_name = LOWER(cname)
) THEN
EXECUTE 'ALTER TABLE ' || tname || ' ADD COLUMN IF NOT EXISTS ' || cname || ' ' || def;
END IF;
END
$func$;
21 changes: 20 additions & 1 deletion library/oss/postgres/prepare/database/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ package main

import (
"context"
"embed"
"fmt"
"io/fs"
"log"
"math/rand"
"os"
Expand All @@ -27,7 +29,13 @@ const (
connIdleTimeout = 15 * time.Minute
connTimeout = 5 * time.Minute

caCertPath = "/tmp/ca.pem"
caCertPath = "/tmp/ca.pem"
helperFunctionsPath = "functions.sql"
)

var (
//go:embed *.sql
data embed.FS
)

func main() {
Expand Down Expand Up @@ -86,6 +94,17 @@ func run(ctx context.Context, p *provider.Provider[*postgres.DatabaseIntent]) er
}
}()

if p.Intent.ProvisionHelperFunctions {
content, err := fs.ReadFile(data, helperFunctionsPath)
if err != nil {
return fmt.Errorf("failed to read %s: %w", helperFunctionsPath, err)
}

if err := applyWithRetry(ctx, db, string(content)); err != nil {
return fmt.Errorf("unable to apply helper functions: %w", err)
}
}

for _, schema := range p.Intent.Schema {
if err := applyWithRetry(ctx, db, string(schema.Contents)); err != nil {
return fmt.Errorf("unable to apply schema %q: %w", schema.Path, err)
Expand Down
24 changes: 18 additions & 6 deletions library/oss/postgres/types.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions library/oss/postgres/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ message ClusterIntent {

message DatabaseIntent {
// The database name is applied as is (e.g. it is case-sensitive).
string name = 1;
repeated foundation.schema.FileContents schema = 2;
bool skip_schema_initialization_if_exists = 3;
string name = 1;
repeated foundation.schema.FileContents schema = 2;
bool skip_schema_initialization_if_exists = 3;
bool provision_helper_functions = 4;
}

0 comments on commit 44bb73b

Please sign in to comment.