Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion internal/migrations/001-common-actions.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1319,4 +1319,4 @@ CREATE OR REPLACE ACTION list_streams(
CASE WHEN $order_by = 'stream_type ASC' THEN stream_type END ASC,
CASE WHEN $order_by = 'stream_type DESC' THEN stream_type END DESC
LIMIT $limit OFFSET $offset;
};
};
80 changes: 80 additions & 0 deletions internal/migrations/021-metadata-actions.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* list_metadata_by_height: Queries metadata within a specific block height range.
* Supports pagination.
* Supports filtering by key and ref.
*
* Parameters:
* $key: filter by metadata key.
*. $ref: filter by value reference.
* $from_height: Start height (inclusive). If NULL, uses earliest available.
* $to_height: End height (inclusive). If NULL, uses current height.
* $limit: Maximum number of results to return.
* $offset: Number of results to skip for pagination.
*
* Returns:
* Table with metadata entries matching the criteria.
*/
CREATE OR REPLACE ACTION list_metadata_by_height(
$key TEXT,
$ref TEXT,
$from_height INT8,
$to_height INT8,
$limit INT,
$offset INT
) PUBLIC view returns table(
stream_ref INT,
row_id uuid,
value_i INT,
value_f NUMERIC(36,18),
value_b bool,
value_s TEXT,
value_ref TEXT,
created_at INT8
) {
-- Set defaults for pagination and validate values
if $limit IS NULL {
$limit := 1000;
}
if $offset IS NULL {
$offset := 0;
}

-- Ensure non-negative values for PostgreSQL compatibility
if $limit < 0 {
$limit := 0;
}
if $offset < 0 {
$offset := 0;
}

-- Get current block height for default behavior
$current_block INT8 := @height;

-- Determine effective height range, if none given, get all metadata
$effective_from INT8 := COALESCE($from_height, 0);
$effective_to INT8 := COALESCE($to_height, $current_block);

-- Validate height range
if $effective_from > $effective_to {
ERROR('Invalid height range: from_height (' || $effective_from::TEXT || ') > to_height (' || $effective_to::TEXT || ')');
}

RETURN SELECT
stream_ref,
row_id,
value_i,
value_f,
value_b,
value_s,
value_ref,
created_at
FROM metadata
WHERE metadata_key = $key
AND disabled_at IS NULL
-- do not use LOWER on value_ref, or it will break the index lookup
AND ($ref IS NULL OR value_ref = LOWER($ref))
AND created_at >= $effective_from
AND created_at <= $effective_to
ORDER BY created_at ASC
LIMIT $limit OFFSET $offset;
};
276 changes: 274 additions & 2 deletions tests/streams/query/metadata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tests
import (
"context"
"fmt"
"strings"
"testing"

"github.com/pkg/errors"
Expand All @@ -14,6 +15,7 @@ import (
testutils "github.com/trufnetwork/node/tests/streams/utils"
"github.com/trufnetwork/node/tests/streams/utils/procedure"
"github.com/trufnetwork/node/tests/streams/utils/setup"
"github.com/trufnetwork/node/tests/streams/utils/table"
"github.com/trufnetwork/sdk-go/core/types"
"github.com/trufnetwork/sdk-go/core/util"
)
Expand All @@ -29,14 +31,19 @@ var (
}
)

// TestQUERY04MetadataInsertionAndRetrieval tests the insertion and retrieval of metadata
// TestQUERY04Metadata tests the insertion and retrieval of metadata
// for both primitive and composed streams. Also tests disabling metadata and attempting to retrieve it.
func TestQUERY04MetadataInsertionAndRetrieval(t *testing.T) {
func TestQUERY04Metadata(t *testing.T) {
kwilTesting.RunSchemaTest(t, kwilTesting.SchemaTest{
Name: "metadata_insertion_and_retrieval",
FunctionTests: []kwilTesting.TestFunc{
WithMetadataTestSetup(testMetadataInsertionAndRetrieval(t, primitiveContractInfo)),
WithMetadataTestSetup(testMetadataInsertionThenDisableAndRetrieval(t, primitiveContractInfo)),
WithMetadataTestSetup(testListMetadataByHeight(t, primitiveContractInfo)),
WithMetadataTestSetup(testListMetadataByHeightNoKey(t, primitiveContractInfo)),
WithMetadataTestSetup(testListMetadataByHeightPagination(t, primitiveContractInfo)),
WithMetadataTestSetup(testListMetadataByHeightInvalidRange(t, primitiveContractInfo)),
WithMetadataTestSetup(testListMetadataByHeightInvalidPagination(t, primitiveContractInfo)),
},
SeedScripts: migrations.GetSeedScriptPaths(),
}, testutils.GetTestOptions())
Expand Down Expand Up @@ -161,3 +168,268 @@ func testMetadataInsertionThenDisableAndRetrieval(t *testing.T, contractInfo set
return nil
}
}

func testListMetadataByHeight(t *testing.T, contractInfo setup.StreamInfo) kwilTesting.TestFunc {
return func(ctx context.Context, platform *kwilTesting.Platform) error {
metadataKey := "test_metadata"
metadataItems := []struct {
Key string
Value string
ValType string
}{
{metadataKey, "1", "int"},
{metadataKey, "0", "int"},
}

for _, item := range metadataItems {
err := procedure.InsertMetadata(ctx, procedure.InsertMetadataInput{
Platform: platform,
Locator: contractInfo.Locator,
Key: item.Key,
Value: item.Value,
ValType: item.ValType,
Height: 1,
})
if err != nil {
return errors.Wrapf(err, "error inserting metadata with key %s", item.Key)
}
}

result, err := procedure.ListMetadataByHeight(ctx, procedure.ListMetadataByHeightInput{
Platform: platform,
Key: metadataKey,
Height: 1,
})
if err != nil {
return errors.Wrapf(err, "error listing metadata")
}

expected := `
| stream_ref | value_i | value_f | value_b | value_s | value_ref | created_at |
|---------------|-----------|---------------------|-----------------|--------|------------|----------------|------------|------------|------------|
| 1 | 0 | <nil> | <nil> | <nil> | <nil> | 1 |
| 1 | 1 | <nil> | <nil> | <nil> | <nil> | 1 |`

table.AssertResultRowsEqualMarkdownTable(t, table.AssertResultRowsEqualMarkdownTableInput{
Actual: result,
Expected: expected,
ExcludedColumns: []int{1},
})

return nil
}
}

func testListMetadataByHeightNoKey(t *testing.T, contractInfo setup.StreamInfo) kwilTesting.TestFunc {
return func(ctx context.Context, platform *kwilTesting.Platform) error {
metadataKey := "test_metadata"
metadataItems := []struct {
Key string
Value string
ValType string
}{
{metadataKey, "1", "int"},
{metadataKey, "0", "int"},
}

for _, item := range metadataItems {
err := procedure.InsertMetadata(ctx, procedure.InsertMetadataInput{
Platform: platform,
Locator: contractInfo.Locator,
Key: item.Key,
Value: item.Value,
ValType: item.ValType,
Height: 1,
})
if err != nil {
return errors.Wrapf(err, "error inserting metadata with key %s", item.Key)
}
}

result, err := procedure.ListMetadataByHeight(ctx, procedure.ListMetadataByHeightInput{
Platform: platform,
Height: 1,
})
if err != nil {
return errors.Wrapf(err, "error listing metadata")
}

if (len(result) > 0) { // should return no rows
return errors.Wrapf(err, "expected empty results")
}

Comment thread
williamrusdyputra marked this conversation as resolved.
return nil
}
}

func testListMetadataByHeightPagination(t *testing.T, contractInfo setup.StreamInfo) kwilTesting.TestFunc {
return func(ctx context.Context, platform *kwilTesting.Platform) error {
metadataKey := "test_metadata"
metadataItems := []struct {
Key string
Value string
ValType string
}{
{metadataKey, "1", "int"},
{metadataKey, "0", "int"},
{metadataKey, "1", "int"},
}

for _, item := range metadataItems {
err := procedure.InsertMetadata(ctx, procedure.InsertMetadataInput{
Platform: platform,
Locator: contractInfo.Locator,
Key: item.Key,
Value: item.Value,
ValType: item.ValType,
Height: 1,
})
if err != nil {
return errors.Wrapf(err, "error inserting metadata with key %s", item.Key)
}
}

limit := 2
offset := 0
result, err := procedure.ListMetadataByHeight(ctx, procedure.ListMetadataByHeightInput{
Platform: platform,
Key: metadataKey,
Limit: &limit,
Offset: &offset,
Height: 1,
})
if err != nil {
return errors.Wrapf(err, "error listing metadata")
}

if len(result) != 2 {
return errors.Errorf("expected 2 results, got %d", len(result))
}

offset = 2
result, err = procedure.ListMetadataByHeight(ctx, procedure.ListMetadataByHeightInput{
Platform: platform,
Key: metadataKey,
Limit: &limit,
Offset: &offset,
Height: 1,
})
if err != nil {
return errors.Wrapf(err, "error listing metadata")
}

if len(result) != 1 {
return errors.Errorf("expected 1 result, got %d", len(result))
}

return nil
}
}

func testListMetadataByHeightInvalidRange(t *testing.T, contractInfo setup.StreamInfo) kwilTesting.TestFunc {
return func(ctx context.Context, platform *kwilTesting.Platform) error {
metadataKey := "test_metadata"
metadataItems := []struct {
Key string
Value string
ValType string
}{
{metadataKey, "1", "int"},
{metadataKey, "0", "int"},
{metadataKey, "1", "int"},
}

for _, item := range metadataItems {
err := procedure.InsertMetadata(ctx, procedure.InsertMetadataInput{
Platform: platform,
Locator: contractInfo.Locator,
Key: item.Key,
Value: item.Value,
ValType: item.ValType,
Height: 1,
})
if err != nil {
return errors.Wrapf(err, "error inserting metadata with key %s", item.Key)
}
}

fromHeight := int64(10)
toHeight := int64(5)
_, err := procedure.ListMetadataByHeight(ctx, procedure.ListMetadataByHeightInput{
Platform: platform,
Key: metadataKey,
FromHeight: &fromHeight,
ToHeight: &toHeight,
Height: 1,
})

if err == nil {
return errors.New("expected error for invalid height range (from_height > to_height), but got none")
}

expectedError := "Invalid height range"
if !strings.Contains(err.Error(), expectedError) {
return errors.Errorf("expected error message to contain '%s', got: %s", expectedError, err.Error())
}

return nil
}
}

func testListMetadataByHeightInvalidPagination(t *testing.T, contractInfo setup.StreamInfo) kwilTesting.TestFunc {
return func(ctx context.Context, platform *kwilTesting.Platform) error {
metadataKey := "test_metadata"
metadataItems := []struct {
Key string
Value string
ValType string
}{
{metadataKey, "1", "int"},
{metadataKey, "0", "int"},
{metadataKey, "1", "int"},
}

for _, item := range metadataItems {
err := procedure.InsertMetadata(ctx, procedure.InsertMetadataInput{
Platform: platform,
Locator: contractInfo.Locator,
Key: item.Key,
Value: item.Value,
ValType: item.ValType,
Height: 1,
})
if err != nil {
return errors.Wrapf(err, "error inserting metadata with key %s", item.Key)
}
}

// negative limit
limit := -10
result, err := procedure.ListMetadataByHeight(ctx, procedure.ListMetadataByHeightInput{
Platform: platform,
Key: metadataKey,
Limit: &limit,
Height: 1,
})
if err != nil {
return errors.Wrap(err, "unexpected error with negative limit")
}

if len(result) != 0 {
return errors.Errorf("expected empty results with negative limit (converted to 0), got %d results", len(result))
}

negativeOffset := -5
result, err = procedure.ListMetadataByHeight(ctx, procedure.ListMetadataByHeightInput{
Platform: platform,
Key: metadataKey,
Offset: &negativeOffset,
Height: 1,
})
if err != nil {
return errors.Wrap(err, "unexpected error with negative offset")
}

return nil
}
}
Loading
Loading