-- = Overview
-- For each "kind of UTxO" for the dens protocol, we create a table for it e.g.
-- we have tables
-- - `+dens_set_utxos+`
-- - `+dens_elem_ids+`
-- - `+dens_protocol_utxos+`
-- We call such a table a _dens table_.
-- Each of these dens tables has a foreign key to `+tx_out_refs+` s.t. when a
-- UTxO gets spent, we may simply delete the corresponding transaction output
-- `+tx_out_refs+` where the deletion will cascade down to the dens table.
-- Note that the `+tx_out_refs+` has foreign keys to the `+blocks+` table.
--
-- To handle rollbacks (recall a rollback is when the blockchain "goes back" to
-- a previous block state), we have the `+undo_log+` table which associates
-- blocks with the inverse operation of the SQL statements that changed dens
-- tables (or the `+tx_out_refs+`/`+blocks+` table).
-- Thus, rolling back amounts to executing each of the SQL statements stored in
-- `+undo_log+` until we reach the block that we must roll back to.
-- Finally, to maintain the `+undo_log+`, we essentially create a "higher order
-- function" which creates a trigger for each of the dens tables (and the
-- `+tx_out_refs+`/`+blocks+` table) which records the inverse SQL operation in
-- the `+undo_log+` table.
-- One wrinkle with the triggers is that cascaded SQL operations don't execute
-- the triggers in the "right order" e.g. if we have table A and table B where
-- B has a foreign key to A, and we delete something in A, the trigger for A
-- will run, then the trigger for B will run -- so undoing will temporarily
-- violate the foreign key constraint. Hence, why we always have `+DEFERRABLE+`
-- set for foreign keys.
--
-- Finally, this schema assumes that we are only following a single DeNS
-- protocol. Thus, it assumes that there is a unique protocol NFT we are
-- interested in following -- see the table `+dens_protocol_nft+` for details.
--
--
-- = References
--
-- * [#ogmios] https://ogmios.dev/api/
-----------------------------------------------------------------------------
-- = Types
-----------------------------------------------------------------------------
DO LANGUAGE plpgsql
$body$
BEGIN
CREATE TYPE asset_class_type AS (
currency_symbol bytea,
token_name bytea
);
CREATE DOMAIN asset_class AS asset_class_type
CONSTRAINT currency_symbol_not_null CHECK (((VALUE).currency_symbol IS NOT NULL))
CONSTRAINT token_name_not_null CHECK (((VALUE).token_name IS NOT NULL))
-- https://github.com/IntersectMBO/plutus/blob/1.16.0.0/plutus-ledger-api/src/PlutusLedgerApi/V1/Value.hs#L75-L92
CONSTRAINT currency_symbol_length CHECK ((octet_length((VALUE).currency_symbol) = 0) OR (octet_length((VALUE).currency_symbol) = 28))
-- https://github.com/IntersectMBO/plutus/blob/1.16.0.0/plutus-ledger-api/src/PlutusLedgerApi/V1/Value.hs#L99-L112
CONSTRAINT token_name_length CHECK (octet_length((VALUE).token_name) <= 32);
EXCEPTION
WHEN duplicate_object THEN null;
END
$body$;
-----------------------------------------------------------------------------
-- = Tables for general information about the blockchain
-----------------------------------------------------------------------------
-- All blocks in the blockchain.
CREATE TABLE IF NOT EXISTS blocks (
block_slot bigint NOT NULL,
block_id bytea NOT NULL,
PRIMARY KEY (block_id, block_slot)
);
-- Transaction outputs relevant to the dens tables
CREATE TABLE IF NOT EXISTS tx_out_refs (
tx_out_ref_id bytea NOT NULL,
tx_out_ref_idx bigint NOT NULL,
block_slot bigint NOT NULL,
block_id bytea NOT NULL,
FOREIGN KEY (block_id, block_slot) REFERENCES blocks (block_id, block_slot)
ON DELETE CASCADE DEFERRABLE,
-- https://github.com/IntersectMBO/plutus/blob/1.16.0.0/plutus-ledger-api/src/PlutusLedgerApi/V1/Tx.hs#L51-L65
CONSTRAINT tx_id_length_is_32 CHECK (octet_length(tx_out_ref_id) = 32),
PRIMARY KEY (tx_out_ref_id, tx_out_ref_idx)
);
-----------------------------------------------------------------------------
-- = Tables for the protocol
-----------------------------------------------------------------------------
-----------------------------------------------------------------------------
-- == Table for the Linked list for associating domain names to RRs
-----------------------------------------------------------------------------
-- Linked list set data structure
CREATE TABLE IF NOT EXISTS dens_set_utxos (
-- Unique identifier for the names
id bigserial UNIQUE,
-- name for the DNS record that is owned
name bytea UNIQUE,
-- Token which associates this `+name+` with a validator address which
-- actually holds (a reference) to the RRs.
pointer asset_class NOT NULL,
tx_out_ref_id bytea NOT NULL,
tx_out_ref_idx bigint NOT NULL,
PRIMARY KEY (tx_out_ref_id, tx_out_ref_idx),
FOREIGN KEY (tx_out_ref_id, tx_out_ref_idx) REFERENCES tx_out_refs (tx_out_ref_id, tx_out_ref_idx)
ON DELETE CASCADE DEFERRABLE
);
-- Index s.t. one can efficiently query if change has happened in the
-- dens_set_utxos
CREATE INDEX IF NOT EXISTS dens_set_utxos_asset_class ON dens_set_utxos (pointer);
-- Index s.t. one can efficiently query which UTxO to spend
CREATE INDEX IF NOT EXISTS dens_set_utxos_name ON dens_set_utxos (name);
-----------------------------------------------------------------------------
-- == Table for representing the UTxOs which contain RRs
-----------------------------------------------------------------------------
-- `+TxOutRef+`s which contain the dens_set_utxos(pointer)
-- i.e., these are the UTxOs which identify the transactions which contain
-- transaction outputs that contain RRs as datum.
CREATE TABLE IF NOT EXISTS dens_elem_ids (
id bigserial UNIQUE,
tx_out_ref_id bytea NOT NULL,
tx_out_ref_idx bigint NOT NULL,
asset_class asset_class NOT NULL,
PRIMARY KEY(id),
UNIQUE (asset_class),
UNIQUE (tx_out_ref_id, tx_out_ref_idx, asset_class),
FOREIGN KEY (tx_out_ref_id, tx_out_ref_idx) REFERENCES tx_out_refs (tx_out_ref_id, tx_out_ref_idx)
ON DELETE CASCADE DEFERRABLE
);
CREATE INDEX IF NOT EXISTS dens_elem_ids_asset_classes ON dens_elem_ids(asset_class);
-- The list of RRs at `+DensValidator+`s addresses i.e., this forms a
-- . M:1 relationship of `+dens_rrs+` to `+dens_elem_ids+`
--
-- NOTE:(jaredponn): so unlike the rest of the tables, the UTxOs we are
-- interested in are controlled by how an asset class at `+dens_elem_ids+`
-- is traded i.e.,
--
-- . If the asset class at `+dens_elem_ids+` is traded (i.e., if this
-- UTxO is consumed), then the RRs are deleted
--
-- Contrast this to how all other tables have FKs to the `+tx_out_refs+` table
--
-- NOTE(jaredponn): this loosely follows the records table in
-- <https://github.com/PowerDNS/pdns/blob/0b6eb67e14ce894e8286c0993e393b1191411c96/modules/gpgsqlbackend/schema.pgsql.sql>
-- NOTE(jaredponn): the only DNS backend we support is PowerDNS. The following
-- are useful docs:
-- . <https://github.com/PowerDNS/pdns/blob/0b6eb67e14ce894e8286c0993e393b1191411c96/modules/gpgsqlbackend/schema.pgsql.sql>
-- for the schema
-- . <https://github.com/PowerDNS/pdns/blob/0b6eb67e14ce894e8286c0993e393b1191411c96/modules/gpgsqlbackend/gpgsqlbackend.cc>
-- . In the future, it'll probably be a reasonable idea to write up our own Cardano backend.
-- See over here: <https://doc.powerdns.com/authoritative/appendices/backend-writers-guide.html> for details
CREATE TABLE IF NOT EXISTS dens_rrs (
id bigserial,
-- The type of the RR e.g. `+A+`, `+AAAA+`, etc.
type varchar(10) NOT NULL,
ttl int NOT NULL,
content varchar(65535) NOT NULL,
dens_elem_id bigserial,
PRIMARY KEY(id),
FOREIGN KEY (dens_elem_id) REFERENCES dens_elem_ids(id)
ON DELETE CASCADE DEFERRABLE
);
-----------------------------------------------------------------------------
-- == Table for the protocol UTxO
-----------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dens_protocol_utxos (
element_id_minting_policy bytea NOT NULL,
set_elem_minting_policy bytea NOT NULL,
set_validator bytea NOT NULL,
records_validator bytea NOT NULL,
tx_out_ref_id bytea NOT NULL,
tx_out_ref_idx bigint NOT NULL,
PRIMARY KEY (tx_out_ref_id, tx_out_ref_idx),
FOREIGN KEY (tx_out_ref_id, tx_out_ref_idx) REFERENCES tx_out_refs (tx_out_ref_id, tx_out_ref_idx)
ON DELETE CASCADE DEFERRABLE
);
-----------------------------------------------------------------------------
-- == Table for the protocol NFT
-----------------------------------------------------------------------------
-- Note that we assume that we are only following a single instance of the DeNS
-- protocol in this schema, so really _all_ DeNS tables depend on this table;
-- and hence have a foreign key to this table.
-- But, to simplify the schema, we only allow at most one dens_protocol_nft to
-- exist, and hence we don't write this foreign key dependency explicitly in
-- the tables.
-- See `+dens_set_protocol_nft+` for details.
CREATE TABLE IF NOT EXISTS dens_protocol_nft(
at_most_one boolean PRIMARY KEY DEFAULT TRUE,
asset_class asset_class NOT NULL,
CONSTRAINT at_most_one CHECK (at_most_one)
);
-----------------------------------------------------------------------------
-- = Tables for the undo log
-----------------------------------------------------------------------------
-- Associates a block (the block id and block slot) with an SQL statement to
-- undo something.
-- NOTE: future versions can shrink the length of undo_log and make it
-- based on the maximum length of the rollback to save some memory.
-- See https://cips.cardano.org/cip/CIP-9/ for details.
CREATE TABLE IF NOT EXISTS undo_log (
seq bigserial,
block_slot bigint NOT NULL,
block_id bytea NOT NULL,
undo_statement text NOT NULL,
FOREIGN KEY (block_id, block_slot) REFERENCES blocks (block_id, block_slot)
ON DELETE CASCADE DEFERRABLE,
PRIMARY KEY (seq)
);
CREATE INDEX IF NOT EXISTS undo_log_block_id_and_block_slot ON undo_log (block_slot, block_id);
-- Gets the most recent block
CREATE OR REPLACE FUNCTION get_most_recent_block()
RETURNS blocks AS
$body$
DECLARE most_recent_block blocks;
BEGIN
SELECT blocks.block_slot, blocks.block_id INTO most_recent_block
FROM blocks
WHERE blocks.block_slot = (SELECT max(block_slot) FROM blocks);
RETURN most_recent_block;
END
$body$
LANGUAGE plpgsql;
-- Given a `+table_name+`, returns `+<table_name>_undo_insert+`. This exists to
-- ensure that we have a consistent way of generating the trigger / function
-- name associated with a table.
CREATE OR REPLACE FUNCTION create_undo_insert_name(table_name text)
RETURNS text AS
$body$
BEGIN
RETURN table_name || '_undo_insert';
END
$body$
LANGUAGE plpgsql;
-- Creates a function and trigger with the name `+table_name_undo_insert+`
-- which on insertion to `+table_name+`, assuming that `+undo_log.freeze_log+`
-- is not true, appends an SQL statement of the form
-- ---
-- format
-- ( $$ DELETE FROM table_name WHERE primary_key1 = %L AND ... primary_keyN = %L $$
-- , NEW.primary_key1
-- , ...
-- , NEW.primary_keyN
-- )
-- ---
-- to `+undo_log+` associated with the most recently added block (if it exists,
-- otherwise we do nothing).
CREATE OR REPLACE FUNCTION create_table_undo_insert(table_name text)
RETURNS void AS
$body$
DECLARE
name text := create_undo_insert_name(table_name);
sql_is_primary_keys text;
sql_new_primary_keys text;
BEGIN
CREATE TEMP TABLE table_primary_keys(
primary_key text
) ON COMMIT DROP;
-- See <https://wiki.postgresql.org/wiki/Retrieve_primary_key_columns>
-- for details
INSERT INTO table_primary_keys
SELECT a.attname
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid
AND a.attnum = ANY(i.indkey)
WHERE i.indrelid = CAST (table_name AS regclass)
AND i.indisprimary;
-- Create a string of the form
-- ---
-- primary_key1 = %L AND primary_key2 = %L ... AND primary_keyN = %L
-- ---
SELECT string_agg(format('%I = %%L', primary_key), ' AND ' ORDER BY primary_key ASC) INTO STRICT sql_is_primary_keys
FROM table_primary_keys;
-- Create a string of the form
-- ---
-- NEW.primary_key1, NEW.primary_key2, ..., NEW.primary_keyN
-- ---
SELECT string_agg(format('NEW.%I', primary_key), ',' ORDER BY primary_key ASC) INTO STRICT sql_new_primary_keys
FROM table_primary_keys;
EXECUTE
format(
$undo_function$
CREATE OR REPLACE FUNCTION %I()
RETURNS trigger AS
$$
DECLARE
most_recent_block record := get_most_recent_block();
BEGIN
IF current_setting('undo_log.freeze_log', TRUE) = CAST(TRUE AS TEXT) THEN
RETURN NEW;
END IF;
IF most_recent_block IS NOT NULL THEN -- if there is no block, then we can't associate the undo log with anything
INSERT INTO undo_log (seq, block_slot, block_id, undo_statement)
VALUES (DEFAULT, most_recent_block.block_slot, most_recent_block.block_id, format(%L, %s));
END IF;
RETURN NEW;
END
$$
LANGUAGE plpgsql;
$undo_function$,
name,
format('DELETE FROM %I WHERE %s', table_name, sql_is_primary_keys),
sql_new_primary_keys
);
EXECUTE
format(
$undo_trigger$
CREATE OR REPLACE TRIGGER %I AFTER INSERT ON %I
FOR EACH ROW
EXECUTE FUNCTION %I();
$undo_trigger$,
name,
table_name,
name
);
DROP TABLE IF EXISTS table_primary_keys;
END
$body$
LANGUAGE plpgsql;
-- Given a `+table_name+`, returns `+<table_name>_undo_delete+`. This exists to
-- ensure that we have a consistent way of generating the trigger / function
-- name associated with a table.
CREATE OR REPLACE FUNCTION create_undo_delete_name(table_name text)
RETURNS text AS
$body$
BEGIN
RETURN table_name || '_undo_delete';
END
$body$
LANGUAGE plpgsql;
-- Creates a function and trigger with the name `+table_name_undo_delete+`
-- which on deletion to `+table_name+`, assuming that `+undo_log.freeze_log+`
-- is not true, append an SQL statement of the form
-- ---
-- format
-- ( $$ INSERT INTO table_name VALUES ((CAST (%L AS table_name)).*) $$
-- , NEW
-- )
-- ---
-- to `+undo_log+` associated with the most recently added block (if it exists,
-- otherwise we do nothing).
CREATE OR REPLACE FUNCTION create_table_undo_delete(table_name text)
RETURNS void AS
$body$
DECLARE
name text := create_undo_delete_name(table_name);
BEGIN
EXECUTE
format(
$undo_function$
CREATE OR REPLACE FUNCTION %I()
RETURNS trigger AS
$$
DECLARE
most_recent_block record := get_most_recent_block();
BEGIN
IF current_setting('undo_log.freeze_log', TRUE) = CAST(TRUE as TEXT) THEN
RETURN OLD;
END IF;
IF most_recent_block IS NOT NULL
THEN -- if there is no block, then we can't associate the undo log with anything
INSERT INTO undo_log (seq, block_slot, block_id, undo_statement)
VALUES (DEFAULT, most_recent_block.block_slot, most_recent_block.block_id, format(%L, OLD));
END IF;
RETURN OLD;
END
$$
LANGUAGE plpgsql;
$undo_function$,
name,
format('INSERT INTO %I VALUES ((CAST (%%L AS %I)).*)', table_name, table_name)
);
EXECUTE
format(
$undo_trigger$
CREATE OR REPLACE TRIGGER %I AFTER DELETE ON %I
FOR EACH ROW
EXECUTE FUNCTION %I();
$undo_trigger$,
name,
table_name,
name
);
END
$body$
LANGUAGE plpgsql;
-- Given a `+table_name+`, returns `+<table_name>_undo_update+`. This exists to
-- ensure that we have a consistent way of generating the trigger / function
-- name associated with a table.
CREATE OR REPLACE FUNCTION create_undo_update_name(table_name text)
RETURNS text AS
$body$
BEGIN
RETURN table_name || '_undo_update';
END
$body$
LANGUAGE plpgsql;
-- Creates a function and trigger with the name `+table_name_undo_update+`
-- which on update to `+table_name+`, assuming that `+undo_log.freeze_log+`
-- is not true, appends an SQL statement of the form
-- ---
-- format
-- ( $$ UPDATE table_name SET column_name1 = %L, ..., column_nameN = %L WHERE primary_key1 = %L AND ... AND primary_keyM = %L;
-- , OLD.column_name1
-- , ...
-- , OLD.column_nameN
-- , NEW.primary_key1
-- , ...
-- , NEW.primary_keyM
-- )
-- ---
-- to `+undo_log+` associated with the most recently added block (if it exists,
-- otherwise we do nothing).
CREATE OR REPLACE FUNCTION create_table_undo_update(table_name text)
RETURNS void AS
$body$
DECLARE
name text := create_undo_update_name(table_name);
sql_is_primary_keys text;
sql_new_primary_keys text;
sql_set_columns text;
sql_old_columns text;
BEGIN
-- = SQL strings relating to the primary keys
CREATE TEMP TABLE table_primary_keys(
primary_key text
) ON COMMIT DROP;
-- See <https://wiki.postgresql.org/wiki/Retrieve_primary_key_columns>
-- for details
INSERT INTO table_primary_keys
SELECT a.attname
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid
AND a.attnum = ANY(i.indkey)
WHERE i.indrelid = CAST (table_name AS regclass)
AND i.indisprimary;
-- Create a string of the form
-- ---
-- primary_key1 = %L AND primary_key2 = %L ... AND primary_keyN = %L
-- ---
SELECT string_agg(format('%I = %%L', primary_key), ' AND ' ORDER BY primary_key ASC) INTO STRICT sql_is_primary_keys
FROM table_primary_keys;
-- Create a string of the form
-- ---
-- NEW.primary_key1, NEW.primary_key2, ..., NEW.primary_keyN
-- ---
SELECT string_agg(format('NEW.%I', primary_key), ',' ORDER BY primary_key ASC) INTO STRICT sql_new_primary_keys
FROM table_primary_keys;
-- = SQL strings relating to all columns
CREATE TEMP TABLE table_column_names(
column_name text
) ON COMMIT DROP;
INSERT INTO table_column_names
SELECT i.attname
FROM pg_attribute i
WHERE i.attrelid = CAST (table_name AS regclass) AND i.attnum > 0 AND NOT i.attisdropped;
-- Create a string of the form
-- ---
-- column_name1 = %L, column_name2 = %L, ..., column_nameN = %L
-- ---
SELECT string_agg(format('%I = %%L', column_name), ',' ORDER BY column_name ASC) INTO STRICT sql_set_columns
FROM table_column_names;
-- Create a string of the form
-- ---
-- OLD.column_name1, column_name2, ..., OLD.column_nameN
-- ---
SELECT string_agg(format('OLD.%I', column_name), ',' ORDER BY column_name ASC) INTO STRICT sql_old_columns
FROM table_column_names;
EXECUTE
format(
$undo_function$
CREATE OR REPLACE FUNCTION %I()
RETURNS trigger AS
$$
DECLARE
most_recent_block record := get_most_recent_block();
BEGIN
IF current_setting('undo_log.freeze_log', TRUE) = CAST(TRUE AS TEXT) THEN
RETURN NEW;
END IF;
IF most_recent_block IS NOT NULL THEN -- if there is no block, then we can't associate the undo log with anything
INSERT INTO undo_log (seq, block_slot, block_id, undo_statement)
VALUES (DEFAULT, most_recent_block.block_slot, most_recent_block.block_id, format(%L, %s, %s));
END IF;
RETURN NEW;
END
$$
LANGUAGE plpgsql;
$undo_function$,
name,
format('UPDATE %I SET %s WHERE %s', table_name, sql_set_columns, sql_is_primary_keys),
sql_old_columns,
sql_new_primary_keys
);
EXECUTE
format(
$undo_trigger$
CREATE OR REPLACE TRIGGER %I AFTER UPDATE ON %I
FOR EACH ROW
EXECUTE FUNCTION %I();
$undo_trigger$,
name,
table_name,
name
);
DROP TABLE IF EXISTS table_primary_keys;
DROP TABLE IF EXISTS table_column_names;
END
$body$
LANGUAGE plpgsql;
-- Freezes the `+undo_log+` i.e., stops triggers from automatically adding
-- things to the `+undo_log+` in the current transaction
CREATE OR REPLACE FUNCTION freeze_undo_log()
RETURNS void as
$body$
BEGIN
SET LOCAL undo_log.freeze_log = TRUE;
END
$body$
LANGUAGE plpgsql;
-- Unfreezes the `+undo_log+` i.e., allows things to be added to the
-- `+undo_log+` again in the current transaction
CREATE OR REPLACE FUNCTION unfreeze_undo_log()
RETURNS void as
$body$
BEGIN
SET LOCAL undo_log.freeze_log = FALSE;
END
$body$
LANGUAGE plpgsql;
-- Roll backs the database to the given block i.e.,
-- Using the `+undo_log+`, execute all `+undo_statement+` _strictly after_ the
-- provided block, and delete such rows from the `+undo_log+`.
CREATE OR REPLACE FUNCTION undo_log_rollback_to(block_slot bigint, block_id bytea)
RETURNS void AS
$body$
DECLARE
to_undo record;
BEGIN
SET CONSTRAINTS ALL DEFERRED;
PERFORM freeze_undo_log();
FOR to_undo IN
WITH deleted AS(
DELETE FROM undo_log
WHERE undo_log.block_slot > undo_log_rollback_to.block_slot
RETURNING *
)
SELECT *
FROM deleted
ORDER BY seq DESC
LOOP
IF to_undo.undo_statement IS NOT NULL
THEN EXECUTE to_undo.undo_statement;
END IF;
END LOOP;
PERFORM unfreeze_undo_log();
END
$body$
LANGUAGE plpgsql;
-----------------------------------------------------------------------------
-- = Undo log triggers
-----------------------------------------------------------------------------
SELECT create_table_undo_insert('blocks');
SELECT create_table_undo_delete('blocks');
SELECT create_table_undo_insert('tx_out_refs');
SELECT create_table_undo_delete('tx_out_refs');
SELECT create_table_undo_update('tx_out_refs');
SELECT create_table_undo_insert('dens_set_utxos');
SELECT create_table_undo_delete('dens_set_utxos');
SELECT create_table_undo_update('dens_set_utxos');
SELECT create_table_undo_insert('dens_elem_ids');
SELECT create_table_undo_delete('dens_elem_ids');
SELECT create_table_undo_update('dens_elem_ids');
SELECT create_table_undo_insert('dens_rrs');
SELECT create_table_undo_delete('dens_rrs');
SELECT create_table_undo_update('dens_rrs');
SELECT create_table_undo_insert('dens_protocol_utxos');
SELECT create_table_undo_delete('dens_protocol_utxos');
SELECT create_table_undo_update('dens_protocol_utxos');
-----------------------------------------------------------------------------
-- = Helper functions
-----------------------------------------------------------------------------
-- If the provided asset class (currency symbol / token name) matches the
-- existing asset class in the `+dens_protocol_nft+` table, do nothing.
-- Otherwise, overwrite the existing `+dens_protocol_nft+`
CREATE OR REPLACE FUNCTION dens_set_protocol_nft(currency_symbol bytea, token_name bytea)
RETURNS dens_protocol_nft AS
$body$
DECLARE
old_protocol_nft dens_protocol_nft;
new_protocol_nft dens_protocol_nft;
BEGIN
SELECT * INTO old_protocol_nft FROM dens_protocol_nft;
INSERT INTO dens_protocol_nft(asset_class)
VALUES(CAST(ROW(dens_set_protocol_nft.currency_symbol, dens_set_protocol_nft.token_name) AS asset_class))
ON CONFLICT (at_most_one) DO UPDATE
SET asset_class = (EXCLUDED).asset_class;
SELECT * INTO STRICT new_protocol_nft FROM dens_protocol_nft;
IF old_protocol_nft IS NULL THEN
RETURN new_protocol_nft;
END IF;
IF (old_protocol_nft).asset_class = (new_protocol_nft).asset_class THEN
RETURN new_protocol_nft;
END IF;
RETURN new_protocol_nft;
END
$body$
LANGUAGE plpgsql;
-- Resets the database if the current protocol NFT stored in the database
-- differs from the provided NFT, and returns the current protocol NFT stored
-- in the database
CREATE OR REPLACE FUNCTION dens_sync_protocol_nft(currency_symbol bytea, token_name bytea)
RETURNS dens_protocol_nft AS
$body$
DECLARE
current_protocol_nft dens_protocol_nft;
BEGIN
SELECT * INTO current_protocol_nft FROM dens_protocol_nft;
IF current_protocol_nft IS NULL THEN
-- Clear all tables if there is no current protocol NFT
TRUNCATE blocks * RESTART IDENTITY CASCADE;
RETURN ROW(true, currency_symbol,token_name);
END IF;
-- If the current protocol NFT matches the provided NFT, we're good, so do nothing and return
IF (current_protocol_nft).asset_class = ROW(currency_symbol, token_name) THEN
RETURN current_protocol_nft;
END IF;
TRUNCATE blocks * RESTART IDENTITY CASCADE;
RETURN current_protocol_nft;
END
$body$
LANGUAGE plpgsql;
-- Gets a collection of the most recent points suitable for resynchronizing
-- with the blockchain after shutting down.
-- NOTE: future versions may use binary search to find the first common point.
-- This requires a somewhat tricky interaction between ogmios / postgres; and
-- it's unclear if this would actually be better at all.
CREATE OR REPLACE FUNCTION dens_recent_points()
RETURNS SETOF blocks AS
$body$
BEGIN
RETURN QUERY
SELECT block_slot, block_id
FROM blocks
ORDER BY blocks.block_slot DESC
LIMIT 64;
END
$body$
LANGUAGE plpgsql;
-- Tests if the provided name is valid. See Section 3.5 of
-- <https://datatracker.ietf.org/doc/html/rfc1034>.
-- Moreover, differing from the specification, we only allow names to be lower
-- case.
--
-- For compatibility with DNS backends like PowerDNS, we must ensure:
-- - names are NEVER terminated with a trailing `.`,
-- - with the exception of the root zone, which must have the name of `.`
-- See <https://doc.powerdns.com/authoritative/backends/generic-sql.html#:~:text=The%20generic%20SQL%20backends%20(like,needed%20to%20cover%20all%20needs.>
CREATE OR REPLACE FUNCTION dens_is_valid_name(name bytea)
RETURNS boolean AS
$body$
BEGIN
RETURN encode(name, 'escape') SIMILAR TO '.|(([a-z]([-a-z0-9]*[a-z0-9])?)(.([a-z]([-a-z0-9]*[a-z0-9])?))*)';
END
$body$
LANGUAGE plpgsql;
Database Schema
|
Note
|
Readers may skip this section. |
DeNS query uses PostgreSQL with many PostgreSQL specific extensions. In particular, the PostgreSQL specific extensions were used to respond to blockchain rollbacks where the blockchain will rollback to a particular block requiring the protocol to undo all the changes from the present to the block to rollback to.
Database schema source code