persistencetests

package
v1.36.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 23, 2026 License: MIT Imports: 52 Imported by: 0

Documentation

Index

Constants

View Source
const TimePrecision = 2 * time.Millisecond

TimePrecision is needed to account for database timestamp precision. Cassandra only provides milliseconds timestamp precision, so we need to use tolerance when doing comparison

Variables

This section is empty.

Functions

func GenerateRandomDBName

func GenerateRandomDBName(n int) string

GenerateRandomDBName helper Format: MMDDHHMMSS_abc

func NewTestClusterForCassandra added in v1.2.0

func NewTestClusterForCassandra(options *TestBaseOptions, logger log.Logger) *cassandra.TestCluster

Types

type ClusterMetadataManagerSuite added in v1.2.0

type ClusterMetadataManagerSuite struct {
	*TestBase
	// override suite.Suite.Assertions with require.Assertions; this means that s.NotNil(nil) will stop the test,
	// not merely log an error
	*require.Assertions
	// contains filtered or unexported fields
}

ClusterMetadataManagerSuite runs tests that cover the ClusterMetadata read/write scenarios

func (*ClusterMetadataManagerSuite) SetupSuite added in v1.2.0

func (s *ClusterMetadataManagerSuite) SetupSuite()

SetupSuite implementation

func (*ClusterMetadataManagerSuite) SetupTest added in v1.2.0

func (s *ClusterMetadataManagerSuite) SetupTest()

SetupTest implementation

func (*ClusterMetadataManagerSuite) TearDownSuite added in v1.2.0

func (s *ClusterMetadataManagerSuite) TearDownSuite()

TearDownSuite implementation

func (*ClusterMetadataManagerSuite) TearDownTest added in v1.2.0

func (s *ClusterMetadataManagerSuite) TearDownTest()

TearDownTest implementation

func (*ClusterMetadataManagerSuite) TestClusterMembershipEmptyInitially added in v1.2.0

func (s *ClusterMetadataManagerSuite) TestClusterMembershipEmptyInitially()

TestClusterMembershipEmptyInitially verifies the GetClusterMembers() works with an initial empty table

func (*ClusterMetadataManagerSuite) TestClusterMembershipReadFiltersCorrectly added in v1.2.0

func (s *ClusterMetadataManagerSuite) TestClusterMembershipReadFiltersCorrectly()

TestClusterMembershipReadFiltersCorrectly verifies that we can UpsertClusterMembership and read our result using filters

func (*ClusterMetadataManagerSuite) TestClusterMembershipUpsertCanPageRead added in v1.2.0

func (s *ClusterMetadataManagerSuite) TestClusterMembershipUpsertCanPageRead()

TestClusterMembershipUpsertCanPageRead verifies that we can UpsertClusterMembership and read our result

func (*ClusterMetadataManagerSuite) TestClusterMembershipUpsertCanReadAny added in v1.2.0

func (s *ClusterMetadataManagerSuite) TestClusterMembershipUpsertCanReadAny()

TestClusterMembershipUpsertCanReadAny verifies that we can UpsertClusterMembership and read our result

func (*ClusterMetadataManagerSuite) TestClusterMembershipUpsertExpiresCorrectly added in v1.2.0

func (s *ClusterMetadataManagerSuite) TestClusterMembershipUpsertExpiresCorrectly()

TestClusterMembershipUpsertExpiresCorrectly verifies RecordExpiry functions properly for ClusterMembership records

func (*ClusterMetadataManagerSuite) TestClusterMembershipUpsertInvalidExpiry added in v1.2.0

func (s *ClusterMetadataManagerSuite) TestClusterMembershipUpsertInvalidExpiry()

TestClusterMembershipUpsertInvalidExpiry verifies we cannot specify a non-positive RecordExpiry duration

func (*ClusterMetadataManagerSuite) TestInitImmutableMetadataReadWrite added in v1.2.0

func (s *ClusterMetadataManagerSuite) TestInitImmutableMetadataReadWrite()

TestInitImmutableMetadataReadWrite runs through the various cases of ClusterMetadata behavior Cases: 1 - Get, no data persisted 2 - Init, no data persisted 3 - Get, data persisted 4 - Init, data persisted 5 - Update, add version info and make sure it's persisted and can be retrieved. 6 - Delete, no data persisted

type HistoryV2PersistenceSuite added in v0.5.0

type HistoryV2PersistenceSuite struct {
	// suite.Suite
	*TestBase
	// override suite.Suite.Assertions with require.Assertions; this means that s.NotNil(nil) will stop the test,
	// not merely log an error
	*require.Assertions
	protorequire.ProtoAssertions
	// contains filtered or unexported fields
}

HistoryV2PersistenceSuite contains history persistence tests

func (*HistoryV2PersistenceSuite) SetupSuite added in v0.5.0

func (s *HistoryV2PersistenceSuite) SetupSuite()

SetupSuite implementation

func (*HistoryV2PersistenceSuite) SetupTest added in v0.5.0

func (s *HistoryV2PersistenceSuite) SetupTest()

SetupTest implementation

func (*HistoryV2PersistenceSuite) TearDownSuite added in v0.5.0

func (s *HistoryV2PersistenceSuite) TearDownSuite()

TearDownSuite implementation

func (*HistoryV2PersistenceSuite) TearDownTest added in v1.2.0

func (s *HistoryV2PersistenceSuite) TearDownTest()

TearDownTest implementation

func (*HistoryV2PersistenceSuite) TestConcurrentlyCreateAndAppendBranches added in v0.5.0

func (s *HistoryV2PersistenceSuite) TestConcurrentlyCreateAndAppendBranches()

TestConcurrentlyCreateAndAppendBranches test

func (*HistoryV2PersistenceSuite) TestConcurrentlyForkAndAppendBranches added in v0.5.0

func (s *HistoryV2PersistenceSuite) TestConcurrentlyForkAndAppendBranches()

TestConcurrentlyForkAndAppendBranches test

func (*HistoryV2PersistenceSuite) TestGenUUIDs added in v0.5.0

func (s *HistoryV2PersistenceSuite) TestGenUUIDs()

TestGenUUIDs testing uuid.NewString() can generate unique UUID

func (*HistoryV2PersistenceSuite) TestReadBranchByPagination added in v0.5.7

func (s *HistoryV2PersistenceSuite) TestReadBranchByPagination()

TestReadBranchByPagination test

func (*HistoryV2PersistenceSuite) TestScanAllTrees added in v1.2.0

func (s *HistoryV2PersistenceSuite) TestScanAllTrees()

TestScanAllTrees test

type MetadataPersistenceSuiteV2

type MetadataPersistenceSuiteV2 struct {
	*TestBase
	// override suite.Suite.Assertions with require.Assertions; this means that s.NotNil(nil) will stop the test,
	// not merely log an error
	*require.Assertions
	protorequire.ProtoAssertions
	// contains filtered or unexported fields
}

MetadataPersistenceSuiteV2 is test of the V2 version of metadata persistence

func (*MetadataPersistenceSuiteV2) CreateNamespace added in v1.2.0

func (m *MetadataPersistenceSuiteV2) CreateNamespace(info *persistencespb.NamespaceInfo, config *persistencespb.NamespaceConfig,
	replicationConfig *persistencespb.NamespaceReplicationConfig, isGlobalnamespace bool, configVersion int64, failoverVersion int64) (*p.CreateNamespaceResponse, error)

CreateNamespace helper method

func (*MetadataPersistenceSuiteV2) DeleteNamespace added in v1.2.0

func (m *MetadataPersistenceSuiteV2) DeleteNamespace(id string, name string) error

DeleteNamespace helper method

func (*MetadataPersistenceSuiteV2) GetNamespace added in v1.2.0

func (m *MetadataPersistenceSuiteV2) GetNamespace(id string, name string) (*p.GetNamespaceResponse, error)

GetNamespace helper method

func (*MetadataPersistenceSuiteV2) ListNamespaces added in v1.2.0

func (m *MetadataPersistenceSuiteV2) ListNamespaces(pageSize int, pageToken []byte) (*p.ListNamespacesResponse, error)

ListNamespaces helper method

func (*MetadataPersistenceSuiteV2) SetupSuite

func (m *MetadataPersistenceSuiteV2) SetupSuite()

SetupSuite implementation

func (*MetadataPersistenceSuiteV2) SetupTest

func (m *MetadataPersistenceSuiteV2) SetupTest()

SetupTest implementation

func (*MetadataPersistenceSuiteV2) TearDownSuite

func (m *MetadataPersistenceSuiteV2) TearDownSuite()

TearDownSuite implementation

func (*MetadataPersistenceSuiteV2) TearDownTest

func (m *MetadataPersistenceSuiteV2) TearDownTest()

TearDownTest implementation

func (*MetadataPersistenceSuiteV2) TestCASFailureUpdateNamespace added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestCASFailureUpdateNamespace()

TestCASFailureUpdateNamespace tests CAS failure when trying to update a namespace

func (*MetadataPersistenceSuiteV2) TestConcurrentCreateNamespace added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestConcurrentCreateNamespace()

TestConcurrentCreateNamespace test

func (*MetadataPersistenceSuiteV2) TestConcurrentUpdateNamespace added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestConcurrentUpdateNamespace()

TestConcurrentUpdateNamespace test

func (*MetadataPersistenceSuiteV2) TestCreateNamespace added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestCreateNamespace()

TestCreateNamespace test

func (*MetadataPersistenceSuiteV2) TestCreateNamespaceWithDuplicateID added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestCreateNamespaceWithDuplicateID()

TestCreateNamespaceWithDuplicateID tests creating a namespace with an ID that already exists

func (*MetadataPersistenceSuiteV2) TestCreateNamespaceWithDuplicateName added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestCreateNamespaceWithDuplicateName()

TestCreateNamespaceWithDuplicateName tests creating a namespace with a name that already exists

func (*MetadataPersistenceSuiteV2) TestCreateWithPartialNamespaceDifferentNameSameID added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestCreateWithPartialNamespaceDifferentNameSameID()

func (*MetadataPersistenceSuiteV2) TestCreateWithPartialNamespaceSameNameDifferentID added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestCreateWithPartialNamespaceSameNameDifferentID()

func (*MetadataPersistenceSuiteV2) TestCreateWithPartialNamespaceSameNameSameID added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestCreateWithPartialNamespaceSameNameSameID()

func (*MetadataPersistenceSuiteV2) TestDeleteNamespace added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestDeleteNamespace()

TestDeleteNamespace test

func (*MetadataPersistenceSuiteV2) TestDeleteNamespaceIdempotency added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestDeleteNamespaceIdempotency()

TestDeleteNamespaceIdempotency tests that delete operations are idempotent

func (*MetadataPersistenceSuiteV2) TestGetMetadataVersionIncrement added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestGetMetadataVersionIncrement()

TestGetMetadataVersionIncrement tests that GetMetadata correctly increments the version after a namespace is created

func (*MetadataPersistenceSuiteV2) TestGetNamespace added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestGetNamespace()

TestGetNamespace test

func (*MetadataPersistenceSuiteV2) TestInitializeSystemNamespaces added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestInitializeSystemNamespaces()

TestInitializeSystemNamespaces tests the initialization of system namespaces

func (*MetadataPersistenceSuiteV2) TestListNamespaces added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestListNamespaces()

TestListNamespaces test

func (*MetadataPersistenceSuiteV2) TestListNamespaces_DeletedNamespace added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestListNamespaces_DeletedNamespace()

func (*MetadataPersistenceSuiteV2) TestRenameNamespace added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestRenameNamespace()

func (*MetadataPersistenceSuiteV2) TestRenameNamespaceCassandra added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestRenameNamespaceCassandra()

TestRenameNamespaceCassandra tests Cassandra-specific RenameNamespace behavior This test verifies the two-step non-atomic rename operation in Cassandra

func (*MetadataPersistenceSuiteV2) TestRenameNamespaceNotFound added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestRenameNamespaceNotFound()

TestRenameNamespaceNotFound tests renaming a non-existent namespace

func (*MetadataPersistenceSuiteV2) TestRenameNamespaceSQL added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestRenameNamespaceSQL()

TestRenameNamespaceSQL tests SQL RenameNamespace behavior This test verifies the atomic transaction-based rename operation in SQL databases

func (*MetadataPersistenceSuiteV2) TestRenameNamespaceWithNameConflict added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestRenameNamespaceWithNameConflict()

TestRenameNamespaceWithNameConflict tests name conflict when trying to rename a namespace

func (*MetadataPersistenceSuiteV2) TestUpdateNamespace added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestUpdateNamespace()

TestUpdateNamespace test

func (*MetadataPersistenceSuiteV2) TestUpdateNamespaceNotFound added in v1.2.0

func (m *MetadataPersistenceSuiteV2) TestUpdateNamespaceNotFound()

TestUpdateNamespaceNotFound tests updating a non-existent namespace

func (*MetadataPersistenceSuiteV2) UpdateNamespace added in v1.2.0

func (m *MetadataPersistenceSuiteV2) UpdateNamespace(
	info *persistencespb.NamespaceInfo,
	config *persistencespb.NamespaceConfig,
	replicationConfig *persistencespb.NamespaceReplicationConfig,
	configVersion int64,
	failoverVersion int64,
	failoverNotificationVersion int64,
	failoverEndTime time.Time,
	notificationVersion int64,
	isGlobalNamespace bool,
) error

UpdateNamespace helper method

type PersistenceTestCluster

type PersistenceTestCluster interface {
	SetupTestDatabase()
	TearDownTestDatabase()
	Config() config.Persistence
}

PersistenceTestCluster exposes management operations on a database

type QueuePersistenceSuite added in v1.2.0

type QueuePersistenceSuite struct {
	*TestBase
	// override suite.Suite.Assertions with require.Assertions; this means that s.NotNil(nil) will stop the test,
	// not merely log an error
	*require.Assertions
	// contains filtered or unexported fields
}

QueuePersistenceSuite contains queue persistence tests

func (*QueuePersistenceSuite) SetupSuite added in v1.2.0

func (s *QueuePersistenceSuite) SetupSuite()

SetupSuite implementation

func (*QueuePersistenceSuite) SetupTest added in v1.2.0

func (s *QueuePersistenceSuite) SetupTest()

SetupTest implementation

func (*QueuePersistenceSuite) TearDownSuite added in v1.2.0

func (s *QueuePersistenceSuite) TearDownSuite()

TearDownSuite implementation

func (*QueuePersistenceSuite) TearDownTest added in v1.2.0

func (s *QueuePersistenceSuite) TearDownTest()

func (*QueuePersistenceSuite) TestNamespaceDLQMetadataOperations added in v1.2.0

func (s *QueuePersistenceSuite) TestNamespaceDLQMetadataOperations()

TestNamespaceDLQMetadataOperations tests queue metadata operations

func (*QueuePersistenceSuite) TestNamespaceReplicationDLQ added in v1.2.0

func (s *QueuePersistenceSuite) TestNamespaceReplicationDLQ()

TestNamespaceReplicationDLQ tests namespace DLQ operations

func (*QueuePersistenceSuite) TestNamespaceReplicationQueue added in v1.2.0

func (s *QueuePersistenceSuite) TestNamespaceReplicationQueue()

TestNamespaceReplicationQueue tests namespace replication queue operations

func (*QueuePersistenceSuite) TestQueueMetadataOperations added in v1.2.0

func (s *QueuePersistenceSuite) TestQueueMetadataOperations()

TestQueueMetadataOperations tests queue metadata operations

type TestBase

type TestBase struct {
	suite.Suite
	ShardMgr                  persistence.ShardManager
	AbstractDataStoreFactory  client.AbstractDataStoreFactory
	VisibilityStoreFactory    visibility.VisibilityStoreFactory
	Factory                   client.Factory
	ExecutionManager          persistence.ExecutionManager
	TaskMgr                   persistence.TaskManager
	FairTaskMgr               persistence.FairTaskManager
	ClusterMetadataManager    persistence.ClusterMetadataManager
	MetadataManager           persistence.MetadataManager
	NamespaceReplicationQueue persistence.NamespaceReplicationQueue
	NexusEndpointManager      persistence.NexusEndpointManager
	ShardInfo                 *persistencespb.ShardInfo
	TaskIDGenerator           TransferTaskIDGenerator
	ClusterMetadata           cluster.Metadata
	SearchAttributesManager   searchattribute.Manager
	PersistenceRateLimiter    quotas.RequestRateLimiter
	PersistenceHealthSignals  persistence.HealthSignalAggregator
	ReadLevel                 int64
	ReplicationReadLevel      int64
	DefaultTestCluster        PersistenceTestCluster
	Logger                    log.Logger
	TracerProvider            trace.TracerProvider
}

TestBase wraps the base setup needed to create workflows over persistence layer.

func NewTestBase added in v0.5.7

func NewTestBase(options *TestBaseOptions) *TestBase

NewTestBase returns a persistence test base backed by either cassandra or sql

func NewTestBaseForCluster added in v1.2.0

func NewTestBaseForCluster(testCluster PersistenceTestCluster, logger log.Logger) *TestBase

func NewTestBaseWithCassandra

func NewTestBaseWithCassandra(options *TestBaseOptions) *TestBase

NewTestBaseWithCassandra returns a persistence test base backed by cassandra datastore

func NewTestBaseWithSQL

func NewTestBaseWithSQL(options *TestBaseOptions) *TestBase

NewTestBaseWithSQL returns a new persistence test base backed by SQL

func (*TestBase) DeleteMessageFromNamespaceDLQ added in v1.2.0

func (s *TestBase) DeleteMessageFromNamespaceDLQ(
	ctx context.Context,
	messageID int64,
) error

DeleteMessageFromNamespaceDLQ deletes one message from namespace DLQ

func (*TestBase) EqualTimes

func (s *TestBase) EqualTimes(t1, t2 time.Time)

EqualTimes assertion that two times are equal within two millisecond precision

func (*TestBase) EqualTimesWithPrecision

func (s *TestBase) EqualTimesWithPrecision(t1, t2 time.Time, precision time.Duration)

EqualTimesWithPrecision assertion that two times are equal within precision

func (*TestBase) GetAckLevels added in v1.2.0

func (s *TestBase) GetAckLevels(
	ctx context.Context,
) (map[string]int64, error)

GetAckLevels returns replication queue ack levels

func (*TestBase) GetMessagesFromNamespaceDLQ added in v1.2.0

func (s *TestBase) GetMessagesFromNamespaceDLQ(
	ctx context.Context,
	firstMessageID int64,
	lastMessageID int64,
	pageSize int,
	pageToken []byte,
) ([]*replicationspb.ReplicationTask, []byte, error)

GetMessagesFromNamespaceDLQ is a utility method to get messages from the namespace DLQ

func (*TestBase) GetNamespaceDLQAckLevel added in v1.2.0

func (s *TestBase) GetNamespaceDLQAckLevel(
	ctx context.Context,
) (int64, error)

GetNamespaceDLQAckLevel returns namespace dlq ack level

func (*TestBase) GetReplicationMessages added in v1.2.0

func (s *TestBase) GetReplicationMessages(
	ctx context.Context,
	lastMessageID int64,
	pageSize int,
) ([]*replicationspb.ReplicationTask, int64, error)

GetReplicationMessages is a utility method to get messages from the queue

func (*TestBase) Publish added in v1.2.0

Publish is a utility method to add messages to the queue

func (*TestBase) PublishToNamespaceDLQ added in v1.2.0

func (s *TestBase) PublishToNamespaceDLQ(ctx context.Context, task *replicationspb.ReplicationTask) error

PublishToNamespaceDLQ is a utility method to add messages to the namespace DLQ

func (*TestBase) RangeDeleteMessagesFromNamespaceDLQ added in v1.2.0

func (s *TestBase) RangeDeleteMessagesFromNamespaceDLQ(
	ctx context.Context,
	firstMessageID int64,
	lastMessageID int64,
) error

RangeDeleteMessagesFromNamespaceDLQ deletes messages from namespace DLQ

func (*TestBase) Setup

func (s *TestBase) Setup(clusterMetadataConfig *cluster.Config)

Setup sets up the test base, must be called as part of SetupSuite

func (*TestBase) TearDownWorkflowStore

func (s *TestBase) TearDownWorkflowStore()

TearDownWorkflowStore to cleanup

func (*TestBase) UpdateAckLevel added in v1.2.0

func (s *TestBase) UpdateAckLevel(
	ctx context.Context,
	lastProcessedMessageID int64,
	clusterName string,
) error

UpdateAckLevel updates replication queue ack level

func (*TestBase) UpdateNamespaceDLQAckLevel added in v1.2.0

func (s *TestBase) UpdateNamespaceDLQAckLevel(
	ctx context.Context,
	lastProcessedMessageID int64,
) error

UpdateNamespaceDLQAckLevel updates namespace dlq ack level

type TestBaseOptions

type TestBaseOptions struct {
	SQLDBPluginName   string
	DBName            string
	DBUsername        string
	DBPassword        string
	DBHost            string
	DBPort            int `yaml:"-"`
	ConnectAttributes map[string]string
	StoreType         string `yaml:"-"`
	SchemaDir         string `yaml:"-"`
	FaultInjection    *config.FaultInjection
	Logger            log.Logger `yaml:"-"`
}

TestBaseOptions options to configure workflow test base.

func GetCassandraTestClusterOption added in v1.2.0

func GetCassandraTestClusterOption() *TestBaseOptions

GetCassandraTestClusterOption returns test options

func GetMySQLTestClusterOption added in v1.2.0

func GetMySQLTestClusterOption() *TestBaseOptions

GetMySQLTestClusterOption return test options

func GetPostgreSQLPGXTestClusterOption added in v1.2.0

func GetPostgreSQLPGXTestClusterOption() *TestBaseOptions

GetPostgreSQLPGXTestClusterOption return test options

func GetPostgreSQLTestClusterOption added in v1.2.0

func GetPostgreSQLTestClusterOption() *TestBaseOptions

GetPostgreSQLTestClusterOption return test options

func GetSQLiteFileTestClusterOption added in v1.2.0

func GetSQLiteFileTestClusterOption() *TestBaseOptions

GetSQLiteFileTestClusterOption return test options

func GetSQLiteMemoryTestClusterOption added in v1.2.0

func GetSQLiteMemoryTestClusterOption() *TestBaseOptions

GetSQLiteMemoryTestClusterOption return test options

func GetTestClusterOption added in v1.2.0

func GetTestClusterOption(storeType, driver string) *TestBaseOptions

GetTestClusterOption returns test options for the given store type and driver.

func (*TestBaseOptions) ApplyDefaults added in v1.2.0

func (o *TestBaseOptions) ApplyDefaults(src *TestBaseOptions)

ApplyDefaults copies database configuration from src, preserving any non-zero values already set.

type TestTransferTaskIDGenerator

type TestTransferTaskIDGenerator struct {
	// contains filtered or unexported fields
}

TestTransferTaskIDGenerator helper

func (*TestTransferTaskIDGenerator) GenerateTransferTaskID added in v0.7.0

func (g *TestTransferTaskIDGenerator) GenerateTransferTaskID() (int64, error)

GenerateTransferTaskID helper

type TransferTaskIDGenerator

type TransferTaskIDGenerator interface {
	GenerateTransferTaskID() (int64, error)
}

TransferTaskIDGenerator generates IDs for transfer tasks written by helper methods

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL