diff --git a/cmd/example/main.go b/cmd/example/main.go index 8ab11ea4..66af10e8 100644 --- a/cmd/example/main.go +++ b/cmd/example/main.go @@ -7,16 +7,17 @@ import ( "syscall" "github.com/dogmatiq/example" + "github.com/dogmatiq/persistencekit/driver/memory/memoryjournal" + "github.com/dogmatiq/persistencekit/driver/memory/memorykv" "github.com/dogmatiq/veracity" - "github.com/dogmatiq/veracity/persistence/driver/memory" "golang.org/x/exp/slog" ) func main() { e := veracity.New( &example.App{}, - veracity.WithJournalStore(&memory.JournalStore{}), - veracity.WithKeyValueStore(&memory.KeyValueStore{}), + veracity.WithJournalStore(&memoryjournal.Store{}), + veracity.WithKeyValueStore(&memorykv.Store{}), veracity.WithLogger( slog.New( slog.NewJSONHandler( diff --git a/engineoption.go b/engineoption.go index 9b2d4a79..fa8b5c2b 100644 --- a/engineoption.go +++ b/engineoption.go @@ -2,9 +2,9 @@ package veracity import ( "github.com/dogmatiq/enginekit/protobuf/uuidpb" + "github.com/dogmatiq/persistencekit/journal" + "github.com/dogmatiq/persistencekit/kv" "github.com/dogmatiq/veracity/internal/engineconfig" - "github.com/dogmatiq/veracity/persistence/journal" - "github.com/dogmatiq/veracity/persistence/kv" "go.opentelemetry.io/otel/metric" "go.opentelemetry.io/otel/trace" "golang.org/x/exp/slog" diff --git a/engineshutdown.go b/engineshutdown.go index d18c8de7..fc00286d 100644 --- a/engineshutdown.go +++ b/engineshutdown.go @@ -7,12 +7,12 @@ import ( const defaultShutdownTimeout = 15 * time.Second -// shutdownContextFor returns a context for shutting down an engine subsystem that -// has stopped because ctx is done. +// shutdownContextFor returns a context for shutting down an engine subsystem +// that has stopped because ctx is done. // // The returned context is NOT a child of ctx, as ctx is expected to have // already been canceled. -func shutdownContextFor(ctx context.Context) (context.Context, context.CancelFunc) { +func shutdownContextFor(context.Context) (context.Context, context.CancelFunc) { // TODO: use ctx's cancel cause to obtain a shutdown context that is // controlled by the user. return context.WithTimeout(context.Background(), defaultShutdownTimeout) diff --git a/go.mod b/go.mod index b8cd43e0..b85d7790 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,6 @@ module github.com/dogmatiq/veracity go 1.22 require ( - github.com/aws/aws-sdk-go-v2 v1.25.2 - github.com/aws/aws-sdk-go-v2/config v1.27.4 - github.com/aws/aws-sdk-go-v2/credentials v1.17.4 - github.com/aws/aws-sdk-go-v2/service/dynamodb v1.30.1 github.com/cespare/xxhash/v2 v2.2.0 github.com/dogmatiq/configkit v0.12.2 github.com/dogmatiq/discoverkit v0.1.2 @@ -15,8 +11,8 @@ require ( github.com/dogmatiq/example v0.0.0-20230606031437-2bd84c72050b github.com/dogmatiq/ferrite v1.2.1 github.com/dogmatiq/marshalkit v0.7.3 + github.com/dogmatiq/persistencekit v0.2.0 github.com/dogmatiq/primo v0.2.0 - github.com/dogmatiq/sqltest v0.3.0 github.com/google/go-cmp v0.6.0 go.opentelemetry.io/otel v1.24.0 go.opentelemetry.io/otel/metric v1.24.0 @@ -29,17 +25,6 @@ require ( ) require ( - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.2 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.2 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.20.1 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.1 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.28.1 // indirect - github.com/aws/smithy-go v1.20.1 // indirect github.com/dogmatiq/cosyne v0.2.0 // indirect github.com/dogmatiq/iago v0.4.0 // indirect github.com/dogmatiq/interopspec v0.5.3 // indirect @@ -48,29 +33,15 @@ require ( github.com/fxamacker/cbor/v2 v2.4.0 // indirect github.com/go-logr/logr v1.4.1 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-sql-driver/mysql v1.5.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/jackc/chunkreader/v2 v2.0.1 // indirect - github.com/jackc/pgconn v1.8.0 // indirect - github.com/jackc/pgio v1.0.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgproto3/v2 v2.0.6 // indirect - github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect - github.com/jackc/pgtype v1.6.2 // indirect - github.com/jackc/pgx/v4 v4.10.0 // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/lib/pq v1.9.0 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect - github.com/mattn/go-sqlite3 v1.14.17 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/x448/float16 v0.8.4 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.18.0 // indirect golang.org/x/net v0.20.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect - golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240123012728-ef4313101c80 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 38c154a3..66b1549b 100644 --- a/go.sum +++ b/go.sum @@ -1,42 +1,5 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/aws/aws-sdk-go-v2 v1.25.2 h1:/uiG1avJRgLGiQM9X3qJM8+Qa6KRGK5rRPuXE0HUM+w= -github.com/aws/aws-sdk-go-v2 v1.25.2/go.mod h1:Evoc5AsmtveRt1komDwIsjHFyrP5tDuF1D1U+6z6pNo= -github.com/aws/aws-sdk-go-v2/config v1.27.4 h1:AhfWb5ZwimdsYTgP7Od8E9L1u4sKmDW2ZVeLcf2O42M= -github.com/aws/aws-sdk-go-v2/config v1.27.4/go.mod h1:zq2FFXK3A416kiukwpsd+rD4ny6JC7QSkp4QdN1Mp2g= -github.com/aws/aws-sdk-go-v2/credentials v1.17.4 h1:h5Vztbd8qLppiPwX+y0Q6WiwMZgpd9keKe2EAENgAuI= -github.com/aws/aws-sdk-go-v2/credentials v1.17.4/go.mod h1:+30tpwrkOgvkJL1rUZuRLoxcJwtI/OkeBLYnHxJtVe0= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.2 h1:AK0J8iYBFeUk2Ax7O8YpLtFsfhdOByh2QIkHmigpRYk= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.15.2/go.mod h1:iRlGzMix0SExQEviAyptRWRGdYNo3+ufW/lCzvKVTUc= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.2 h1:bNo4LagzUKbjdxE0tIcR9pMzLR2U/Tgie1Hq1HQ3iH8= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.2/go.mod h1:wRQv0nN6v9wDXuWThpovGQjqF1HFdcgWjporw14lS8k= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.2 h1:EtOU5jsPdIQNP+6Q2C5e3d65NKT1PeCiQk+9OdzO12Q= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.2/go.mod h1:tyF5sKccmDz0Bv4NrstEr+/9YkSPJHrcO7UsUKf7pWM= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0 h1:hT8rVHwugYE2lEfdFE0QWVo81lF7jMrYJVDWI+f+VxU= -github.com/aws/aws-sdk-go-v2/internal/ini v1.8.0/go.mod h1:8tu/lYfQfFe6IGnaOdrpVgEL2IrrDOf6/m9RQum4NkY= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.30.1 h1:haLXE5R07oaq/UnvSyE43V4jp9gA2XRMYcxkFYHEpdU= -github.com/aws/aws-sdk-go-v2/service/dynamodb v1.30.1/go.mod h1:mM51J0CILKQjqIawPDM4g6E1nyxdlvk/qaCDyJkx0II= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1 h1:EyBZibRTVAs6ECHZOw5/wlylS9OcTzwyjeQMudmREjE= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.11.1/go.mod h1:JKpmtYhhPs7D97NL/ltqz7yCkERFW5dOlHyVl66ZYF8= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.2 h1:3tS2g6P3N+Wz64e9aNx7X4BCWN/gT9MUvIuv5l2eoho= -github.com/aws/aws-sdk-go-v2/service/internal/endpoint-discovery v1.9.2/go.mod h1:1Pf5vPqk8t9pdYB3dmUMRE/0m8u0IHHg8ESSiutJd0I= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.2 h1:5ffmXjPtwRExp1zc7gENLgCPyHFbhEPwVTkTiH9niSk= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.11.2/go.mod h1:Ru7vg1iQ7cR4i7SZ/JTLYN9kaXtbL69UdgG0OQWQxW0= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.1 h1:utEGkfdQ4L6YW/ietH7111ZYglLJvS+sLriHJ1NBJEQ= -github.com/aws/aws-sdk-go-v2/service/sso v1.20.1/go.mod h1:RsYqzYr2F2oPDdpy+PdhephuZxTfjHQe7SOBcZGoAU8= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.1 h1:9/GylMS45hGGFCcMrUZDVayQE1jYSIN6da9jo7RAYIw= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.23.1/go.mod h1:YjAPFn4kGFqKC54VsHs5fn5B6d+PCY2tziEa3U/GB5Y= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.1 h1:3I2cBEYgKhrWlwyZgfpSO2BpaMY1LHPqXYk/QGlu2ew= -github.com/aws/aws-sdk-go-v2/service/sts v1.28.1/go.mod h1:uQ7YYKZt3adCRrdCBREm1CD3efFLOUNH77MrUCvx5oA= -github.com/aws/smithy-go v1.20.1 h1:4SZlSlMr36UEqC7XOyRVb27XMeZubNcBNN+9IgEPIQw= -github.com/aws/smithy-go v1.20.1/go.mod h1:krry+ya/rV9RDcV/Q16kpu6ypI4K2czasz0NC3qS14E= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= -github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dogmatiq/configkit v0.12.2 h1:3CgioafFI57yxreHouRjDmRP8eXKlQOQPIHH1N0VwKE= @@ -63,6 +26,8 @@ github.com/dogmatiq/linger v1.1.0 h1:kGL9sL79qRa6Cr8PhadeJ/ptbum+b48pAaNWWlyVVKg github.com/dogmatiq/linger v1.1.0/go.mod h1:OOWJUwTxNkFolhuVdaTYjO4FmFLjZHZ8EMc5H5qOJ7Q= github.com/dogmatiq/marshalkit v0.7.3 h1:kBymR5txcHFBJcYvzld6kFWshqL9YqfBfnFzl9KwEaI= github.com/dogmatiq/marshalkit v0.7.3/go.mod h1:gGiQXt9aHidlR1GIgHlZxJU8QAd004kFxU6beq+MPmI= +github.com/dogmatiq/persistencekit v0.2.0 h1:XI0ZGfE5xwX0k8fZoT3gq8tuP/Ze2ZRayWiB0zisSvY= +github.com/dogmatiq/persistencekit v0.2.0/go.mod h1:zI5wGbprKk6DnnJRHYvQI1gYGtWgKeEFoWDc2N2S5T4= github.com/dogmatiq/primo v0.2.0 h1:XSgal1oykHCFtHvHXdsaSDvQ2x/V/h+clDS1YIqtwHM= github.com/dogmatiq/primo v0.2.0/go.mod h1:c1EGDvqJQSaIlTxpT1jPgXMBhOXLnQ3jtKPRH5nuUis= github.com/dogmatiq/projectionkit v0.6.5 h1:3Ues+QL5oVtJcx4WogMA6XjJF1QyOlcx1uRmUrl2ghI= @@ -83,11 +48,8 @@ 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.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls= -github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE= -github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= @@ -106,93 +68,35 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= -github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= -github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= -github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= -github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= -github.com/jackc/pgconn v1.4.0/go.mod h1:Y2O3ZDF0q4mMacyWV3AstPJpeHXWGEetiFttmq5lahk= -github.com/jackc/pgconn v1.5.0/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= -github.com/jackc/pgconn v1.5.1-0.20200601181101-fa742c524853/go.mod h1:QeD3lBfpTFe8WUnPZWN5KY/mB8FGMIYRdd8P8Jr0fAI= github.com/jackc/pgconn v1.8.0 h1:FmjZ0rOyXTr1wfWs45i4a9vjnjWUAGpMuQLD9OSs+lw= github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2 h1:JVX6jT/XfzNqIjye4717ITLaNwV9mWbJx0dLCpcRzdA= -github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= 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/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= -github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= -github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= -github.com/jackc/pgproto3/v2 v2.0.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/pgproto3/v2 v2.0.6 h1:b1105ZGEMFe7aCvrT1Cca3VoVb4ZFMaFJLJcg/3zD+8= github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= -github.com/jackc/pgservicefile v0.0.0-20200307190119-3430c5407db8/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= -github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= -github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= -github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= -github.com/jackc/pgtype v1.2.0/go.mod h1:5m2OfMh1wTK7x+Fk952IDmI4nw3nPrvtQdM0ZT4WpC0= -github.com/jackc/pgtype v1.3.1-0.20200510190516-8cd94a14c75a/go.mod h1:vaogEUkALtxZMCH411K+tKzNpwzCKU+AnPzBKZ+I+Po= -github.com/jackc/pgtype v1.3.1-0.20200606141011-f6355165a91c/go.mod h1:cvk9Bgu/VzJ9/lxTO5R5sf80p0DiucVtN7ZxvaC4GmQ= github.com/jackc/pgtype v1.6.2 h1:b3pDeuhbbzBYcg5kwNmNDun4pFUD/0AAr1kLXZLeNt8= github.com/jackc/pgtype v1.6.2/go.mod h1:JCULISAZBFGrHaOXIIFiyfzW5VY0GRitRr8NeJsrdig= -github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= -github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= -github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= -github.com/jackc/pgx/v4 v4.5.0/go.mod h1:EpAKPLdnTorwmPUUsqrPxy5fphV18j9q3wrfRXgo+kA= -github.com/jackc/pgx/v4 v4.6.1-0.20200510190926-94ba730bb1e9/go.mod h1:t3/cdRQl6fOLDxqtlyhe9UWgfIi9R8+8v8GKV5TRA/o= -github.com/jackc/pgx/v4 v4.6.1-0.20200606145419-4e5062306904/go.mod h1:ZDaNWkt9sW1JMiNn0kdYBaLelIhw7Pg4qd+Vk6tw7Hg= github.com/jackc/pgx/v4 v4.10.0 h1:xXTl+lSiF1eFQ4U7vUL493n/1q8ZhSDP962rSKhgRZo= github.com/jackc/pgx/v4 v4.10.0/go.mod h1:QlrWebbs3kqEZPHCTGyxecvzG6tvIsYu+A5b1raylkA= -github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= -github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= github.com/jmalloc/gomegax v0.0.0-20200507221434-64fca4c0e03a h1:Gk7Gkwl1KUJII/FiAjvBjRgEz/lpvTV8kNYp+9jdpuk= github.com/jmalloc/gomegax v0.0.0-20200507221434-64fca4c0e03a/go.mod h1:TZpc8ObQEKqTuy1/VXpPRfcMU80QFDU4zK3nchXts/k= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.9.0 h1:L8nSXQQzAYByakOFMTwpjRoHsMJklur4Gi59b6VivR8= github.com/lib/pq v1.9.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= -github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= -github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI= github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= @@ -212,96 +116,46 @@ github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1y github.com/onsi/gomega v1.10.4/go.mod h1:g/HbgYopi++010VEqkFgJHKC09uJiW9UkXvMUuKHUCQ= github.com/onsi/gomega v1.30.0 h1:hvMK7xYz4D3HapigLTeGdId/NcfQx1VHMJc60ew99+8= github.com/onsi/gomega v1.30.0/go.mod h1:9sxs+SwGrKI0+PWe4Fxa9tFQQBG5xSsSbMXOI8PPpoQ= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= -github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= -github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= -github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc h1:jUIKcSPO9MoMJBbEoyE/RJoE8vz7Mb8AjvifMMwSyvY= -github.com/shopspring/decimal v0.0.0-20200227202807-02e2044944cc/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo= go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo= go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI= go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco= go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI= go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20230810033253-352e893a4cad h1:g0bG7Z4uG+OgH2QDODnjp6ggkk1bJDsINcuWmJN1iJU= golang.org/x/exp v0.0.0-20230810033253-352e893a4cad/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -313,17 +167,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc= golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps= -golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -342,23 +187,15 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= pgregory.net/rapid v1.1.0 h1:CMa0sjHSru3puNx+J0MIAuiiEV4N0qj8/cMWGBBCsjw= pgregory.net/rapid v1.1.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04= diff --git a/internal/cluster/internal/registrypb/registration.pb.go b/internal/cluster/internal/registrypb/registration.pb.go index 6863c937..0609e971 100644 --- a/internal/cluster/internal/registrypb/registration.pb.go +++ b/internal/cluster/internal/registrypb/registration.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.24.4 // source: github.com/dogmatiq/veracity/internal/cluster/internal/registrypb/registration.proto diff --git a/internal/cluster/registry.go b/internal/cluster/registry.go index f92cdc17..d389ff56 100644 --- a/internal/cluster/registry.go +++ b/internal/cluster/registry.go @@ -7,11 +7,11 @@ import ( "time" "github.com/dogmatiq/enginekit/protobuf/uuidpb" + "github.com/dogmatiq/persistencekit/kv" "github.com/dogmatiq/veracity/internal/cluster/internal/registrypb" "github.com/dogmatiq/veracity/internal/fsm" "github.com/dogmatiq/veracity/internal/protobuf/protokv" "github.com/dogmatiq/veracity/internal/signaling" - "github.com/dogmatiq/veracity/persistence/kv" "golang.org/x/exp/slog" "google.golang.org/protobuf/types/known/timestamppb" ) diff --git a/internal/cluster/registry_test.go b/internal/cluster/registry_test.go index 348bff41..a5791b00 100644 --- a/internal/cluster/registry_test.go +++ b/internal/cluster/registry_test.go @@ -5,9 +5,9 @@ import ( "time" "github.com/dogmatiq/enginekit/protobuf/uuidpb" + "github.com/dogmatiq/persistencekit/driver/memory/memorykv" . "github.com/dogmatiq/veracity/internal/cluster" "github.com/dogmatiq/veracity/internal/test" - "github.com/dogmatiq/veracity/persistence/driver/memory" ) func TestRegistry(t *testing.T) { @@ -21,7 +21,7 @@ func TestRegistry(t *testing.T) { Observer *RegistryObserver }, ) { - keyspaces := &memory.KeyValueStore{} + keyspaces := &memorykv.Store{} deps.Node = Node{ ID: uuidpb.Generate(), diff --git a/internal/engineconfig/app.go b/internal/engineconfig/app.go index ed1e0f40..9c20950d 100644 --- a/internal/engineconfig/app.go +++ b/internal/engineconfig/app.go @@ -51,16 +51,15 @@ func (c applicationVisitor) VisitRichApplication(ctx context.Context, cfg config return cfg.RichHandlers().AcceptRichVisitor(ctx, c) } -func (c applicationVisitor) VisitRichAggregate(ctx context.Context, cfg configkit.RichAggregate) error { +func (c applicationVisitor) VisitRichAggregate(context.Context, configkit.RichAggregate) error { return nil } -func (c applicationVisitor) VisitRichProcess(ctx context.Context, cfg configkit.RichProcess) error { +func (c applicationVisitor) VisitRichProcess(context.Context, configkit.RichProcess) error { return nil } -func (c applicationVisitor) VisitRichIntegration(ctx context.Context, cfg configkit.RichIntegration) error { - +func (c applicationVisitor) VisitRichIntegration(_ context.Context, cfg configkit.RichIntegration) error { sup := &integration.Supervisor{ Handler: cfg.Handler(), HandlerIdentity: marshalIdentity(cfg.Identity()), @@ -86,7 +85,7 @@ func (c applicationVisitor) VisitRichIntegration(ctx context.Context, cfg config return nil } -func (c applicationVisitor) VisitRichProjection(ctx context.Context, cfg configkit.RichProjection) error { +func (c applicationVisitor) VisitRichProjection(context.Context, configkit.RichProjection) error { return nil } diff --git a/internal/engineconfig/config.go b/internal/engineconfig/config.go index 673803fd..06c8e3ae 100644 --- a/internal/engineconfig/config.go +++ b/internal/engineconfig/config.go @@ -9,9 +9,9 @@ import ( "github.com/dogmatiq/enginekit/protobuf/identitypb" "github.com/dogmatiq/enginekit/protobuf/uuidpb" "github.com/dogmatiq/ferrite" + "github.com/dogmatiq/persistencekit/journal" + "github.com/dogmatiq/persistencekit/kv" "github.com/dogmatiq/veracity/internal/telemetry" - "github.com/dogmatiq/veracity/persistence/journal" - "github.com/dogmatiq/veracity/persistence/kv" "google.golang.org/grpc" ) diff --git a/internal/engineconfig/persistence.go b/internal/engineconfig/persistence.go index 2f401a82..0d9e3df9 100644 --- a/internal/engineconfig/persistence.go +++ b/internal/engineconfig/persistence.go @@ -4,9 +4,9 @@ import ( "net/url" "github.com/dogmatiq/ferrite" + "github.com/dogmatiq/persistencekit/journal" + "github.com/dogmatiq/persistencekit/kv" "github.com/dogmatiq/veracity/internal/telemetry/instrumentedpersistence" - "github.com/dogmatiq/veracity/persistence/journal" - "github.com/dogmatiq/veracity/persistence/kv" ) // journalStoreDSN is the DSN describing which journal store to use. @@ -20,12 +20,12 @@ var keyValueStoreDSN = ferrite. Optional(ferrite.WithRegistry(FerriteRegistry)) // journalStoreFromDSN returns the journal store described by the given DSN. -func journalStoreFromDSN(dsn *url.URL) journal.Store { +func journalStoreFromDSN(*url.URL) journal.Store { panic("not implemented") } // keyValueStoreFromDSN returns the key/value store described by the given DSN. -func keyValueStoreFromDSN(dsn *url.URL) kv.Store { +func keyValueStoreFromDSN(*url.URL) kv.Store { panic("not implemented") } diff --git a/internal/eventstream/append_test.go b/internal/eventstream/append_test.go index 9c28837f..6c525584 100644 --- a/internal/eventstream/append_test.go +++ b/internal/eventstream/append_test.go @@ -10,27 +10,27 @@ import ( "github.com/dogmatiq/enginekit/protobuf/identitypb" "github.com/dogmatiq/enginekit/protobuf/uuidpb" . "github.com/dogmatiq/marshalkit/fixtures" + "github.com/dogmatiq/persistencekit/driver/memory/memoryjournal" + "github.com/dogmatiq/persistencekit/journal" "github.com/dogmatiq/veracity/internal/envelope" . "github.com/dogmatiq/veracity/internal/eventstream" "github.com/dogmatiq/veracity/internal/eventstream/internal/journalpb" "github.com/dogmatiq/veracity/internal/protobuf/protojournal" "github.com/dogmatiq/veracity/internal/test" - "github.com/dogmatiq/veracity/persistence/driver/memory" - "github.com/dogmatiq/veracity/persistence/journal" ) func TestAppend(t *testing.T) { t.Parallel() type dependencies struct { - Journals *memory.JournalStore + Journals *memoryjournal.Store Supervisor *Supervisor Events <-chan Event Packer *envelope.Packer } setup := func(t test.TestingT) (deps dependencies) { - deps.Journals = &memory.JournalStore{} + deps.Journals = &memoryjournal.Store{} events := make(chan Event, 100) @@ -67,7 +67,7 @@ func TestAppend(t *testing.T) { { Desc: "failure to open journal", InduceFailure: func(deps *dependencies) { - memory.FailOnJournalOpen( + test.FailOnJournalOpen( deps.Journals, JournalName(streamID), errors.New(""), @@ -77,7 +77,7 @@ func TestAppend(t *testing.T) { { Desc: "failure before appending to journal", InduceFailure: func(deps *dependencies) { - memory.FailBeforeJournalAppend( + test.FailBeforeJournalAppend( deps.Journals, JournalName(streamID), func(*journalpb.Record) bool { @@ -90,7 +90,7 @@ func TestAppend(t *testing.T) { { Desc: "failure after appending to journal", InduceFailure: func(deps *dependencies) { - memory.FailAfterJournalAppend( + test.FailAfterJournalAppend( deps.Journals, JournalName(streamID), func(*journalpb.Record) bool { diff --git a/internal/eventstream/internal/journalpb/record.pb.go b/internal/eventstream/internal/journalpb/record.pb.go index 4c7b840f..a847e870 100644 --- a/internal/eventstream/internal/journalpb/record.pb.go +++ b/internal/eventstream/internal/journalpb/record.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.24.4 // source: github.com/dogmatiq/veracity/internal/eventstream/internal/journalpb/record.proto diff --git a/internal/eventstream/journal.go b/internal/eventstream/journal.go index 4256f882..194253db 100644 --- a/internal/eventstream/journal.go +++ b/internal/eventstream/journal.go @@ -4,9 +4,9 @@ import ( "context" "github.com/dogmatiq/enginekit/protobuf/uuidpb" + "github.com/dogmatiq/persistencekit/journal" "github.com/dogmatiq/veracity/internal/eventstream/internal/journalpb" "github.com/dogmatiq/veracity/internal/protobuf/protojournal" - "github.com/dogmatiq/veracity/persistence/journal" ) // JournalName returns the name of the journal that contains the state @@ -27,8 +27,7 @@ func searchByOffset(off Offset) protojournal.CompareFunc[*journalpb.Record] { return +1, nil } else if rec.StreamOffsetAfter <= uint64(off) { return -1, nil - } else { - return 0, nil } + return 0, nil } } diff --git a/internal/eventstream/supervisor.go b/internal/eventstream/supervisor.go index 37be91e8..eef9af2b 100644 --- a/internal/eventstream/supervisor.go +++ b/internal/eventstream/supervisor.go @@ -5,10 +5,10 @@ import ( "errors" "github.com/dogmatiq/enginekit/protobuf/uuidpb" + "github.com/dogmatiq/persistencekit/journal" "github.com/dogmatiq/veracity/internal/fsm" "github.com/dogmatiq/veracity/internal/messaging" "github.com/dogmatiq/veracity/internal/signaling" - "github.com/dogmatiq/veracity/persistence/journal" "golang.org/x/exp/slog" ) diff --git a/internal/eventstream/worker.go b/internal/eventstream/worker.go index 71cc07e2..c1dccd12 100644 --- a/internal/eventstream/worker.go +++ b/internal/eventstream/worker.go @@ -4,12 +4,12 @@ import ( "context" "time" + "github.com/dogmatiq/persistencekit/journal" "github.com/dogmatiq/veracity/internal/eventstream/internal/journalpb" "github.com/dogmatiq/veracity/internal/fsm" "github.com/dogmatiq/veracity/internal/messaging" "github.com/dogmatiq/veracity/internal/protobuf/protojournal" "github.com/dogmatiq/veracity/internal/signaling" - "github.com/dogmatiq/veracity/persistence/journal" "golang.org/x/exp/slog" ) diff --git a/internal/integration/internal/journalpb/record.pb.go b/internal/integration/internal/journalpb/record.pb.go index 13ac408c..41d55dc9 100644 --- a/internal/integration/internal/journalpb/record.pb.go +++ b/internal/integration/internal/journalpb/record.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.31.0 +// protoc-gen-go v1.32.0 // protoc v4.24.4 // source: github.com/dogmatiq/veracity/internal/integration/internal/journalpb/record.proto diff --git a/internal/integration/supervisor.go b/internal/integration/supervisor.go index aeda8488..c9919da9 100644 --- a/internal/integration/supervisor.go +++ b/internal/integration/supervisor.go @@ -7,6 +7,8 @@ import ( "github.com/dogmatiq/enginekit/protobuf/envelopepb" "github.com/dogmatiq/enginekit/protobuf/identitypb" "github.com/dogmatiq/enginekit/protobuf/uuidpb" + "github.com/dogmatiq/persistencekit/journal" + "github.com/dogmatiq/persistencekit/kv" "github.com/dogmatiq/veracity/internal/envelope" "github.com/dogmatiq/veracity/internal/eventstream" "github.com/dogmatiq/veracity/internal/fsm" @@ -14,8 +16,6 @@ import ( "github.com/dogmatiq/veracity/internal/messaging" "github.com/dogmatiq/veracity/internal/protobuf/protojournal" "github.com/dogmatiq/veracity/internal/signaling" - "github.com/dogmatiq/veracity/persistence/journal" - "github.com/dogmatiq/veracity/persistence/kv" "golang.org/x/exp/slices" "google.golang.org/protobuf/proto" ) diff --git a/internal/integration/supervisor_test.go b/internal/integration/supervisor_test.go index dcb9db96..04cb570c 100644 --- a/internal/integration/supervisor_test.go +++ b/internal/integration/supervisor_test.go @@ -15,12 +15,13 @@ import ( "github.com/dogmatiq/enginekit/protobuf/identitypb" "github.com/dogmatiq/enginekit/protobuf/uuidpb" . "github.com/dogmatiq/marshalkit/fixtures" + "github.com/dogmatiq/persistencekit/driver/memory/memoryjournal" + "github.com/dogmatiq/persistencekit/driver/memory/memorykv" "github.com/dogmatiq/veracity/internal/envelope" "github.com/dogmatiq/veracity/internal/eventstream" . "github.com/dogmatiq/veracity/internal/integration" "github.com/dogmatiq/veracity/internal/integration/internal/journalpb" "github.com/dogmatiq/veracity/internal/test" - "github.com/dogmatiq/veracity/persistence/driver/memory" "github.com/google/go-cmp/cmp/cmpopts" "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/timestamppb" @@ -29,20 +30,20 @@ import ( func TestSupervisor(t *testing.T) { type dependencies struct { Packer *envelope.Packer - Journals *memory.JournalStore - Keyspaces *memory.KeyValueStore + Journals *memoryjournal.Store + Keyspaces *memorykv.Store Handler *IntegrationMessageHandler EventRecorder *eventRecorderStub Supervisor *Supervisor Executor *CommandExecutor } - setup := func(t test.TestingT) (deps dependencies) { + setup := func(test.TestingT) (deps dependencies) { deps.Packer = newPacker() - deps.Journals = &memory.JournalStore{} + deps.Journals = &memoryjournal.Store{} - deps.Keyspaces = &memory.KeyValueStore{} + deps.Keyspaces = &memorykv.Store{} deps.Handler = &IntegrationMessageHandler{} @@ -81,7 +82,7 @@ func TestSupervisor(t *testing.T) { { Desc: "failure to open journal", InduceFailure: func(deps *dependencies) { - memory.FailOnJournalOpen( + test.FailOnJournalOpen( deps.Journals, JournalName(deps.Supervisor.HandlerIdentity.Key), errors.New(""), @@ -91,7 +92,7 @@ func TestSupervisor(t *testing.T) { { Desc: "failure before appending CommandEnqueued record to the journal", InduceFailure: func(deps *dependencies) { - memory.FailBeforeJournalAppend( + test.FailBeforeJournalAppend( deps.Journals, JournalName(deps.Supervisor.HandlerIdentity.Key), func(r *journalpb.Record) bool { @@ -104,7 +105,7 @@ func TestSupervisor(t *testing.T) { { Desc: "failure after appending CommandEnqueued record to the journal", InduceFailure: func(deps *dependencies) { - memory.FailAfterJournalAppend( + test.FailAfterJournalAppend( deps.Journals, JournalName(deps.Supervisor.HandlerIdentity.Key), func(r *journalpb.Record) bool { @@ -158,7 +159,7 @@ func TestSupervisor(t *testing.T) { { Desc: "failure before appending CommandHandled record to the journal", InduceFailure: func(deps *dependencies) { - memory.FailBeforeJournalAppend( + test.FailBeforeJournalAppend( deps.Journals, JournalName(deps.Supervisor.HandlerIdentity.Key), func(r *journalpb.Record) bool { @@ -171,7 +172,7 @@ func TestSupervisor(t *testing.T) { { Desc: "failure after appending CommandHandled record to the journal", InduceFailure: func(deps *dependencies) { - memory.FailAfterJournalAppend( + test.FailAfterJournalAppend( deps.Journals, JournalName(deps.Supervisor.HandlerIdentity.Key), func(r *journalpb.Record) bool { @@ -227,7 +228,7 @@ func TestSupervisor(t *testing.T) { Desc: "failure before appending EventsAppendedToStream record to the journal", ExpectMultipleEventAppendRequests: true, InduceFailure: func(deps *dependencies) { - memory.FailBeforeJournalAppend( + test.FailBeforeJournalAppend( deps.Journals, JournalName(deps.Supervisor.HandlerIdentity.Key), func(r *journalpb.Record) bool { @@ -240,7 +241,7 @@ func TestSupervisor(t *testing.T) { { Desc: "failure after appending EventsAppendedToStream record to the journal", InduceFailure: func(deps *dependencies) { - memory.FailAfterJournalAppend( + test.FailAfterJournalAppend( deps.Journals, JournalName(deps.Supervisor.HandlerIdentity.Key), func(r *journalpb.Record) bool { @@ -253,7 +254,7 @@ func TestSupervisor(t *testing.T) { { Desc: "failure before adding command to handled-commands keyspace", InduceFailure: func(deps *dependencies) { - memory.FailBeforeKeyspaceSet( + test.FailBeforeKeyspaceSet( deps.Keyspaces, HandledCommandsKeyspaceName(deps.Supervisor.HandlerIdentity.Key), func(k, v []byte) bool { @@ -265,7 +266,7 @@ func TestSupervisor(t *testing.T) { { Desc: "failure after adding command to handled-commands keyspace", InduceFailure: func(deps *dependencies) { - memory.FailAfterKeyspaceSet( + test.FailAfterKeyspaceSet( deps.Keyspaces, HandledCommandsKeyspaceName(deps.Supervisor.HandlerIdentity.Key), func(k, v []byte) bool { diff --git a/internal/protobuf/protojournal/append.go b/internal/protobuf/protojournal/append.go index 7043722c..adf2707c 100644 --- a/internal/protobuf/protojournal/append.go +++ b/internal/protobuf/protojournal/append.go @@ -4,8 +4,8 @@ import ( "context" "fmt" + "github.com/dogmatiq/persistencekit/journal" "github.com/dogmatiq/veracity/internal/protobuf/typedproto" - "github.com/dogmatiq/veracity/persistence/journal" ) // Append adds a record to the journal. diff --git a/internal/protobuf/protojournal/get.go b/internal/protobuf/protojournal/get.go index 9f58e2f5..cf982752 100644 --- a/internal/protobuf/protojournal/get.go +++ b/internal/protobuf/protojournal/get.go @@ -5,8 +5,8 @@ import ( "errors" "fmt" + "github.com/dogmatiq/persistencekit/journal" "github.com/dogmatiq/veracity/internal/protobuf/typedproto" - "github.com/dogmatiq/veracity/persistence/journal" ) // Get returns the record at the given position. diff --git a/internal/protobuf/protojournal/range.go b/internal/protobuf/protojournal/range.go index 65c9383e..e6d78ce4 100644 --- a/internal/protobuf/protojournal/range.go +++ b/internal/protobuf/protojournal/range.go @@ -4,8 +4,8 @@ import ( "context" "fmt" + "github.com/dogmatiq/persistencekit/journal" "github.com/dogmatiq/veracity/internal/protobuf/typedproto" - "github.com/dogmatiq/veracity/persistence/journal" "google.golang.org/protobuf/proto" ) diff --git a/internal/protobuf/protojournal/scan.go b/internal/protobuf/protojournal/scan.go index 21d28ad7..db45354d 100644 --- a/internal/protobuf/protojournal/scan.go +++ b/internal/protobuf/protojournal/scan.go @@ -3,8 +3,8 @@ package protojournal import ( "context" + "github.com/dogmatiq/persistencekit/journal" "github.com/dogmatiq/veracity/internal/protobuf/typedproto" - "github.com/dogmatiq/veracity/persistence/journal" "google.golang.org/protobuf/proto" ) diff --git a/internal/protobuf/protojournal/search.go b/internal/protobuf/protojournal/search.go index 79a544fa..1cce3f8f 100644 --- a/internal/protobuf/protojournal/search.go +++ b/internal/protobuf/protojournal/search.go @@ -4,8 +4,8 @@ import ( "context" "errors" + "github.com/dogmatiq/persistencekit/journal" "github.com/dogmatiq/veracity/internal/protobuf/typedproto" - "github.com/dogmatiq/veracity/persistence/journal" "google.golang.org/protobuf/proto" ) diff --git a/internal/protobuf/protokv/kv.go b/internal/protobuf/protokv/kv.go index 62b9d14a..cae0934e 100644 --- a/internal/protobuf/protokv/kv.go +++ b/internal/protobuf/protokv/kv.go @@ -4,8 +4,8 @@ import ( "context" "fmt" + "github.com/dogmatiq/persistencekit/kv" "github.com/dogmatiq/veracity/internal/protobuf/typedproto" - "github.com/dogmatiq/veracity/persistence/kv" "google.golang.org/protobuf/proto" ) diff --git a/internal/telemetry/instrumentedpersistence/journal.go b/internal/telemetry/instrumentedpersistence/journal.go index 7632996f..a44152ba 100644 --- a/internal/telemetry/instrumentedpersistence/journal.go +++ b/internal/telemetry/instrumentedpersistence/journal.go @@ -4,8 +4,8 @@ import ( "context" "errors" + "github.com/dogmatiq/persistencekit/journal" "github.com/dogmatiq/veracity/internal/telemetry" - "github.com/dogmatiq/veracity/persistence/journal" "go.opentelemetry.io/otel/metric" ) diff --git a/internal/telemetry/instrumentedpersistence/journal_test.go b/internal/telemetry/instrumentedpersistence/journal_test.go index 2b5a069a..c4f02f8f 100644 --- a/internal/telemetry/instrumentedpersistence/journal_test.go +++ b/internal/telemetry/instrumentedpersistence/journal_test.go @@ -3,13 +3,13 @@ package instrumentedpersistence_test import ( "testing" + "github.com/dogmatiq/persistencekit/driver/memory/memoryjournal" + "github.com/dogmatiq/persistencekit/journal" "github.com/dogmatiq/veracity/internal/telemetry" . "github.com/dogmatiq/veracity/internal/telemetry/instrumentedpersistence" "github.com/dogmatiq/veracity/internal/test" - "github.com/dogmatiq/veracity/persistence/driver/memory" - "github.com/dogmatiq/veracity/persistence/journal" - "go.opentelemetry.io/otel/metric/noop" - "go.opentelemetry.io/otel/trace" + noopmetric "go.opentelemetry.io/otel/metric/noop" + nooptrace "go.opentelemetry.io/otel/trace/noop" ) func TestJournalStore(t *testing.T) { @@ -17,10 +17,10 @@ func TestJournalStore(t *testing.T) { t, func(t *testing.T) journal.Store { return &JournalStore{ - Next: &memory.JournalStore{}, + Next: &memoryjournal.Store{}, Telemetry: &telemetry.Provider{ - TracerProvider: trace.NewNoopTracerProvider(), - MeterProvider: noop.NewMeterProvider(), + TracerProvider: nooptrace.NewTracerProvider(), + MeterProvider: noopmetric.NewMeterProvider(), Logger: test.NewLogger(t), }, } diff --git a/internal/telemetry/instrumentedpersistence/keyvalue.go b/internal/telemetry/instrumentedpersistence/keyvalue.go index 106c8d40..f4a096b0 100644 --- a/internal/telemetry/instrumentedpersistence/keyvalue.go +++ b/internal/telemetry/instrumentedpersistence/keyvalue.go @@ -3,8 +3,8 @@ package instrumentedpersistence import ( "context" + "github.com/dogmatiq/persistencekit/kv" "github.com/dogmatiq/veracity/internal/telemetry" - "github.com/dogmatiq/veracity/persistence/kv" "go.opentelemetry.io/otel/metric" ) diff --git a/internal/telemetry/instrumentedpersistence/keyvalue_test.go b/internal/telemetry/instrumentedpersistence/keyvalue_test.go index e4e45d61..78f5f129 100644 --- a/internal/telemetry/instrumentedpersistence/keyvalue_test.go +++ b/internal/telemetry/instrumentedpersistence/keyvalue_test.go @@ -3,13 +3,13 @@ package instrumentedpersistence_test import ( "testing" + "github.com/dogmatiq/persistencekit/driver/memory/memorykv" + "github.com/dogmatiq/persistencekit/kv" "github.com/dogmatiq/veracity/internal/telemetry" . "github.com/dogmatiq/veracity/internal/telemetry/instrumentedpersistence" "github.com/dogmatiq/veracity/internal/test" - "github.com/dogmatiq/veracity/persistence/driver/memory" - "github.com/dogmatiq/veracity/persistence/kv" - "go.opentelemetry.io/otel/metric/noop" - "go.opentelemetry.io/otel/trace" + noopmetric "go.opentelemetry.io/otel/metric/noop" + nooptrace "go.opentelemetry.io/otel/trace/noop" ) func TestKeyValueStore(t *testing.T) { @@ -17,10 +17,10 @@ func TestKeyValueStore(t *testing.T) { t, func(t *testing.T) kv.Store { return &KeyValueStore{ - Next: &memory.KeyValueStore{}, + Next: &memorykv.Store{}, Telemetry: &telemetry.Provider{ - TracerProvider: trace.NewNoopTracerProvider(), - MeterProvider: noop.NewMeterProvider(), + TracerProvider: nooptrace.NewTracerProvider(), + MeterProvider: noopmetric.NewMeterProvider(), Logger: test.NewLogger(t), }, } diff --git a/internal/test/error.go b/internal/test/error.go new file mode 100644 index 00000000..b256f342 --- /dev/null +++ b/internal/test/error.go @@ -0,0 +1,17 @@ +package test + +import "sync" + +// FailOnce returns a function that returns the given error once, and then +// returns nil on subsequent calls. +func FailOnce(err error) func() error { + var once sync.Once + + return func() error { + var e error + once.Do(func() { + e = err + }) + return e + } +} diff --git a/persistence/driver/memory/journaltest.go b/internal/test/journal.go similarity index 56% rename from persistence/driver/memory/journaltest.go rename to internal/test/journal.go index 99671d46..23fe81e2 100644 --- a/persistence/driver/memory/journaltest.go +++ b/internal/test/journal.go @@ -1,32 +1,25 @@ -package memory +package test import ( - "context" "reflect" - "sync" + "github.com/dogmatiq/persistencekit/driver/memory/memoryjournal" "google.golang.org/protobuf/proto" ) // FailOnJournalOpen configures the journal with the given name to return an // error on the next call to Open(). func FailOnJournalOpen( - s *JournalStore, + s *memoryjournal.Store, name string, - produceErr error, + err error, ) { - var once sync.Once - - s.onOpen = func(n string) error { - var err error - + fail := FailOnce(err) + s.BeforeOpen = func(n string) error { if name == n { - once.Do(func() { - err = produceErr - }) + return fail() } - - return err + return nil } } @@ -36,23 +29,12 @@ func FailOnJournalOpen( // // The error is returned before the append is actually performed. func FailBeforeJournalAppend[R proto.Message]( - s *JournalStore, + s *memoryjournal.Store, name string, pred func(R) bool, - produceErr error, + err error, ) { - j, err := s.Open(context.Background(), name) - if err != nil { - panic(err) - } - defer j.Close() - - h := j.(*journalHandle) - - h.state.Lock() - defer h.state.Unlock() - - h.state.BeforeAppend = failAppendOnce(pred, produceErr) + s.BeforeAppend = failAppendOnce(name, pred, err) } // FailAfterJournalAppend configures the journal with the given name to return @@ -61,32 +43,26 @@ func FailBeforeJournalAppend[R proto.Message]( // // The error is returned after the append is actually performed. func FailAfterJournalAppend[R proto.Message]( - s *JournalStore, + s *memoryjournal.Store, name string, pred func(R) bool, - produceErr error, + err error, ) { - j, err := s.Open(context.Background(), name) - if err != nil { - panic(err) - } - defer j.Close() - - h := j.(*journalHandle) - - h.state.Lock() - defer h.state.Unlock() - - h.state.AfterAppend = failAppendOnce(pred, produceErr) + s.AfterAppend = failAppendOnce(name, pred, err) } func failAppendOnce[R proto.Message]( + name string, pred func(R) bool, - produceErr error, -) func([]byte) error { - var once sync.Once + err error, +) func(string, []byte) error { + fail := FailOnce(err) + + return func(n string, data []byte) error { + if n != name { + return nil + } - return func(data []byte) error { var rec R rec = reflect.New( reflect.TypeOf(rec).Elem(), @@ -96,14 +72,9 @@ func failAppendOnce[R proto.Message]( panic(err) } - var err error - if pred(rec) { - once.Do(func() { - err = produceErr - }) + return fail() } - - return err + return nil } } diff --git a/persistence/driver/memory/keyvaluetest.go b/internal/test/kv.go similarity index 51% rename from persistence/driver/memory/keyvaluetest.go rename to internal/test/kv.go index 22c3cca6..5a504762 100644 --- a/persistence/driver/memory/keyvaluetest.go +++ b/internal/test/kv.go @@ -1,9 +1,9 @@ -package memory +package test import ( - "context" "errors" - "sync" + + "github.com/dogmatiq/persistencekit/driver/memory/memorykv" ) // FailBeforeKeyspaceSet configures the keyspace with the given name to return @@ -12,22 +12,11 @@ import ( // // The error is returned before the set is actually performed. func FailBeforeKeyspaceSet( - s *KeyValueStore, + s *memorykv.Store, name string, pred func(k, v []byte) bool, ) { - j, err := s.Open(context.Background(), name) - if err != nil { - panic(err) - } - defer j.Close() - - h := j.(*keyspaceHandle) - - h.state.Lock() - defer h.state.Unlock() - - h.state.BeforeSet = failSetOnce(pred) + s.BeforeSet = failSetOnce(name, pred) } // FailAfterKeyspaceSet configures the keyspace with the given name to return an @@ -36,34 +25,28 @@ func FailBeforeKeyspaceSet( // // The error is returned after the set is actually performed. func FailAfterKeyspaceSet( - s *KeyValueStore, + s *memorykv.Store, name string, pred func(k, v []byte) bool, ) { - j, err := s.Open(context.Background(), name) - if err != nil { - panic(err) - } - defer j.Close() - - h := j.(*keyspaceHandle) - - h.state.Lock() - defer h.state.Unlock() - - h.state.AfterSet = failSetOnce(pred) + s.AfterSet = failSetOnce(name, pred) } -func failSetOnce(pred func(k, v []byte) bool) func(k, v []byte) error { - var once sync.Once +func failSetOnce( + name string, + pred func(k, v []byte) bool, +) func(ks string, k, v []byte) error { + fail := FailOnce(errors.New("")) + + return func(ks string, k, v []byte) error { + if ks != name { + return nil + } - return func(k, v []byte) (err error) { if pred(k, v) { - once.Do(func() { - err = errors.New("") - }) + return fail() } - return err + return nil } } diff --git a/internal/test/run.go b/internal/test/run.go index 45cf3bb7..5a4ffaff 100644 --- a/internal/test/run.go +++ b/internal/test/run.go @@ -100,9 +100,8 @@ func (r TaskRunner) RepeatedlyUntilSuccess() *Task { return err } else if err == nil { return nil - } else { - r.t.Logf("restarting %q because it returned an error: %s", r.name, err) } + r.t.Logf("restarting %q because it returned an error: %s", r.name, err) } }, func(err error) { diff --git a/persistence/driver/aws/dynamodb/doc.go b/persistence/driver/aws/dynamodb/doc.go deleted file mode 100644 index 83f62153..00000000 --- a/persistence/driver/aws/dynamodb/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package dynamodb provides low-level persistence implementations that store -// data in DynamoDB. -package dynamodb diff --git a/persistence/driver/aws/dynamodb/journal.go b/persistence/driver/aws/dynamodb/journal.go deleted file mode 100644 index d1ebb5a4..00000000 --- a/persistence/driver/aws/dynamodb/journal.go +++ /dev/null @@ -1,388 +0,0 @@ -package dynamodb - -import ( - "context" - "errors" - "fmt" - "strconv" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/dynamodb" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/dogmatiq/veracity/persistence/driver/aws/internal/awsx" - "github.com/dogmatiq/veracity/persistence/journal" -) - -// JournalStore is an implementation of [journal.Store] that persists journals -// in a DynamoDB table. -type JournalStore struct { - // Client is the DynamoDB client to use. - Client *dynamodb.Client - - // Table is the table name used for storage of journal records. - Table string - - // DecorateGetItem is an optional function that is called before each - // DynamoDB "GetItem" request. - // - // It may modify the API input in-place. It returns options that will be - // applied to the request. - DecorateGetItem func(*dynamodb.GetItemInput) []func(*dynamodb.Options) - - // DecorateQuery is an optional function that is called before each DynamoDB - // "Query" request. - // - // It may modify the API input in-place. It returns options that will be - // applied to the request. - DecorateQuery func(*dynamodb.QueryInput) []func(*dynamodb.Options) - - // DecoratePutItem is an optional function that is called before each - // DynamoDB "PutItem" request. - // - // It may modify the API input in-place. It returns options that will be - // applied to the request. - DecoratePutItem func(*dynamodb.PutItemInput) []func(*dynamodb.Options) - - // DecorateDeleteItem is an optional function that is called before each - // DynamoDB "DeleteItem" request. - // - // It may modify the API input in-place. It returns options that will be - // applied to the request. - DecorateDeleteItem func(*dynamodb.DeleteItemInput) []func(*dynamodb.Options) -} - -const ( - journalNameAttr = "Name" - journalPositionAttr = "Position" - journalRecordAttr = "Record" -) - -// Open returns the journal with the given name. -func (s *JournalStore) Open(ctx context.Context, name string) (journal.Journal, error) { - j := &journ{ - Client: s.Client, - DecorateGetItem: s.DecorateGetItem, - DecorateQuery: s.DecorateQuery, - DecoratePutItem: s.DecoratePutItem, - DecorateDeleteItem: s.DecorateDeleteItem, - - name: &types.AttributeValueMemberS{Value: name}, - position: &types.AttributeValueMemberN{}, - record: &types.AttributeValueMemberB{}, - } - - j.boundsQueryRequest = dynamodb.QueryInput{ - TableName: aws.String(s.Table), - KeyConditionExpression: aws.String(`#N = :N`), - ProjectionExpression: aws.String("#P"), - ExpressionAttributeNames: map[string]string{ - "#N": journalNameAttr, - "#P": journalPositionAttr, - }, - ExpressionAttributeValues: map[string]types.AttributeValue{ - ":N": j.name, - }, - ScanIndexForward: aws.Bool(true), - Limit: aws.Int32(1), - } - - j.getRequest = dynamodb.GetItemInput{ - TableName: aws.String(s.Table), - Key: map[string]types.AttributeValue{ - journalNameAttr: j.name, - journalPositionAttr: j.position, - }, - ProjectionExpression: aws.String(`#R`), - ExpressionAttributeNames: map[string]string{ - "#R": journalRecordAttr, - }, - } - - j.rangeQueryRequest = dynamodb.QueryInput{ - TableName: aws.String(s.Table), - KeyConditionExpression: aws.String(`#N = :N AND #P >= :P`), - ProjectionExpression: aws.String("#P, #R"), - ExpressionAttributeNames: map[string]string{ - "#N": journalNameAttr, - "#P": journalPositionAttr, - "#R": journalRecordAttr, - }, - ExpressionAttributeValues: map[string]types.AttributeValue{ - ":N": j.name, - ":P": j.position, - }, - } - - j.putRequest = dynamodb.PutItemInput{ - TableName: aws.String(s.Table), - ConditionExpression: aws.String(`attribute_not_exists(#N)`), - ExpressionAttributeNames: map[string]string{ - "#N": journalNameAttr, - }, - Item: map[string]types.AttributeValue{ - journalNameAttr: j.name, - journalPositionAttr: j.position, - journalRecordAttr: j.record, - }, - } - - j.deleteRequest = dynamodb.DeleteItemInput{ - TableName: aws.String(s.Table), - Key: map[string]types.AttributeValue{ - journalNameAttr: j.name, - journalPositionAttr: j.position, - }, - } - - return j, nil -} - -// journ is an implementation of journal.Journal that stores records in -// a DynamoDB table. -type journ struct { - Client *dynamodb.Client - DecorateGetItem func(*dynamodb.GetItemInput) []func(*dynamodb.Options) - DecorateQuery func(*dynamodb.QueryInput) []func(*dynamodb.Options) - DecoratePutItem func(*dynamodb.PutItemInput) []func(*dynamodb.Options) - DecorateDeleteItem func(*dynamodb.DeleteItemInput) []func(*dynamodb.Options) - - name *types.AttributeValueMemberS - position *types.AttributeValueMemberN - record *types.AttributeValueMemberB - - boundsQueryRequest dynamodb.QueryInput - getRequest dynamodb.GetItemInput - rangeQueryRequest dynamodb.QueryInput - putRequest dynamodb.PutItemInput - deleteRequest dynamodb.DeleteItemInput -} - -func (j *journ) Bounds(ctx context.Context) (begin, end journal.Position, err error) { - *j.boundsQueryRequest.ScanIndexForward = true - out, err := awsx.Do( - ctx, - j.Client.Query, - j.DecorateQuery, - &j.boundsQueryRequest, - ) - if err != nil || len(out.Items) == 0 { - return 0, 0, err - } - - begin, err = parsePosition(out.Items[0]) - if err != nil { - return 0, 0, err - } - - *j.boundsQueryRequest.ScanIndexForward = false - out, err = awsx.Do( - ctx, - j.Client.Query, - j.DecorateQuery, - &j.boundsQueryRequest, - ) - if err != nil || len(out.Items) == 0 { - return 0, 0, err - } - - end, err = parsePosition(out.Items[0]) - if err != nil { - return 0, 0, err - } - - return begin, end + 1, nil -} - -func (j *journ) Get(ctx context.Context, pos journal.Position) ([]byte, error) { - j.position.Value = formatPosition(pos) - - out, err := awsx.Do( - ctx, - j.Client.GetItem, - j.DecorateGetItem, - &j.getRequest, - ) - if err != nil { - return nil, err - } - if out.Item == nil { - return nil, journal.ErrNotFound - } - - rec, err := getAttr[*types.AttributeValueMemberB](out.Item, journalRecordAttr) - if err != nil { - return nil, err - } - - return rec.Value, nil -} - -func (j *journ) Range( - ctx context.Context, - begin journal.Position, - fn journal.RangeFunc, -) error { - j.rangeQueryRequest.ExclusiveStartKey = nil - j.position.Value = formatPosition(begin) - - expectPos := begin - - for { - out, err := awsx.Do( - ctx, - j.Client.Query, - j.DecorateQuery, - &j.rangeQueryRequest, - ) - if err != nil { - return err - } - - for _, item := range out.Items { - pos, err := parsePosition(item) - if err != nil { - return err - } - - if pos != expectPos { - return journal.ErrNotFound - } - - expectPos++ - - rec, err := getAttr[*types.AttributeValueMemberB](item, journalRecordAttr) - if err != nil { - return err - } - - ok, err := fn(ctx, pos, rec.Value) - if !ok || err != nil { - return err - } - } - - if out.LastEvaluatedKey == nil { - return nil - } - - j.rangeQueryRequest.ExclusiveStartKey = out.LastEvaluatedKey - } -} - -func (j *journ) Append(ctx context.Context, end journal.Position, rec []byte) error { - j.position.Value = formatPosition(end) - j.record.Value = rec - - _, err := awsx.Do( - ctx, - j.Client.PutItem, - j.DecoratePutItem, - &j.putRequest, - ) - - if errors.As(err, new(*types.ConditionalCheckFailedException)) { - return journal.ErrConflict - } - - return err -} - -func (j *journ) Truncate(ctx context.Context, end journal.Position) error { - begin, actualEnd, err := j.Bounds(ctx) - if err != nil { - return err - } - - if end >= actualEnd { - return errors.New("cannot truncate beyond the end of the journal") - } - - for pos := begin; pos < end; pos++ { - j.position.Value = formatPosition(pos) - - if _, err := awsx.Do( - ctx, - j.Client.DeleteItem, - j.DecorateDeleteItem, - &j.deleteRequest, - ); err != nil { - return err - } - } - - return nil -} - -func (j *journ) Close() error { - return nil -} - -// CreateJournalStoreTable creates a DynamoDB table for use with [JournalStore]. -func CreateJournalStoreTable( - ctx context.Context, - client *dynamodb.Client, - table string, - decorators ...func(*dynamodb.CreateTableInput) []func(*dynamodb.Options), -) error { - _, err := awsx.Do( - ctx, - client.CreateTable, - func(in *dynamodb.CreateTableInput) []func(*dynamodb.Options) { - var options []func(*dynamodb.Options) - for _, dec := range decorators { - options = append(options, dec(in)...) - } - - return options - }, - &dynamodb.CreateTableInput{ - TableName: aws.String(table), - AttributeDefinitions: []types.AttributeDefinition{ - { - AttributeName: aws.String(journalNameAttr), - AttributeType: types.ScalarAttributeTypeS, - }, - { - AttributeName: aws.String(journalPositionAttr), - AttributeType: types.ScalarAttributeTypeN, - }, - }, - KeySchema: []types.KeySchemaElement{ - { - AttributeName: aws.String(journalNameAttr), - KeyType: types.KeyTypeHash, - }, - { - AttributeName: aws.String(journalPositionAttr), - KeyType: types.KeyTypeRange, - }, - }, - BillingMode: types.BillingModePayPerRequest, - }, - ) - - if errors.As(err, new(*types.ResourceInUseException)) { - return nil - } - - return err -} - -// parsePosition parses the position attribute in the given item. -func parsePosition(item map[string]types.AttributeValue) (journal.Position, error) { - attr, err := getAttr[*types.AttributeValueMemberN](item, journalPositionAttr) - if err != nil { - return 0, err - } - - pos, err := strconv.ParseUint(attr.Value, 10, 64) - if err != nil { - return 0, fmt.Errorf("item is corrupt: invalid position: %w", err) - } - - return journal.Position(pos), nil -} - -func formatPosition(pos journal.Position) string { - return strconv.FormatUint(uint64(pos), 10) -} diff --git a/persistence/driver/aws/dynamodb/journal_test.go b/persistence/driver/aws/dynamodb/journal_test.go deleted file mode 100644 index 6e422657..00000000 --- a/persistence/driver/aws/dynamodb/journal_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package dynamodb_test - -import ( - "context" - "testing" - "time" - - . "github.com/dogmatiq/veracity/persistence/driver/aws/dynamodb" - "github.com/dogmatiq/veracity/persistence/journal" -) - -func TestJournalStore(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - - client := newClient(t) - table := "journal" - - if err := CreateJournalStoreTable(ctx, client, table); err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := deleteTable(ctx, client, table); err != nil { - t.Fatal(err) - } - - cancel() - }) - - journal.RunTests( - t, - func(t *testing.T) journal.Store { - return &JournalStore{ - Client: client, - Table: table, - } - }, - ) -} diff --git a/persistence/driver/aws/dynamodb/keyvalue.go b/persistence/driver/aws/dynamodb/keyvalue.go deleted file mode 100644 index da3d8993..00000000 --- a/persistence/driver/aws/dynamodb/keyvalue.go +++ /dev/null @@ -1,315 +0,0 @@ -package dynamodb - -import ( - "context" - "errors" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/service/dynamodb" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" - "github.com/dogmatiq/veracity/persistence/driver/aws/internal/awsx" - "github.com/dogmatiq/veracity/persistence/kv" -) - -// KeyValueStore is an implementation of [kv.Store] that persists keyspaces in a -// DynamoDB table. -type KeyValueStore struct { - // Client is the DynamoDB client to use. - Client *dynamodb.Client - - // Table is the table name used for storage of journal records. - Table string - - // DecorateGetItem is an optional function that is called before each - // DynamoDB "GetItem" request. - // - // It may modify the API input in-place. It returns options that will be - // applied to the request. - DecorateGetItem func(*dynamodb.GetItemInput) []func(*dynamodb.Options) - - // DecorateQuery is an optional function that is called before each DynamoDB - // "Query" request. - // - // It may modify the API input in-place. It returns options that will be - // applied to the request. - DecorateQuery func(*dynamodb.QueryInput) []func(*dynamodb.Options) - - // DecoratePutItem is an optional function that is called before each - // DynamoDB "PutItem" request. - // - // It may modify the API input in-place. It returns options that will be - // applied to the request. - DecoratePutItem func(*dynamodb.PutItemInput) []func(*dynamodb.Options) - - // DecorateDeleteItem is an optional function that is called before each - // DynamoDB "DeleteItem" request. - // - // It may modify the API input in-place. It returns options that will be - // applied to the request. - DecorateDeleteItem func(*dynamodb.DeleteItemInput) []func(*dynamodb.Options) -} - -const ( - kvKeyspaceAttr = "Keyspace" - kvKeyAttr = "Key" - kvValueAttr = "Value" -) - -// Open returns the keyspace with the given name. -func (s *KeyValueStore) Open(ctx context.Context, name string) (kv.Keyspace, error) { - ks := &keyspace{ - Client: s.Client, - DecorateGetItem: s.DecorateGetItem, - DecorateQuery: s.DecorateQuery, - DecoratePutItem: s.DecoratePutItem, - DecorateDeleteItem: s.DecorateDeleteItem, - - name: &types.AttributeValueMemberS{Value: name}, - key: &types.AttributeValueMemberB{}, - value: &types.AttributeValueMemberB{}, - } - - ks.getRequest = dynamodb.GetItemInput{ - TableName: aws.String(s.Table), - Key: map[string]types.AttributeValue{ - kvKeyspaceAttr: ks.name, - kvKeyAttr: ks.key, - }, - ProjectionExpression: aws.String(`#V`), - ExpressionAttributeNames: map[string]string{ - "#V": kvValueAttr, - }, - } - - // Has() requests an unknown attribute to avoid fetching unnecessary data. - ks.hasRequest = dynamodb.GetItemInput{ - TableName: aws.String(s.Table), - Key: map[string]types.AttributeValue{ - kvKeyspaceAttr: ks.name, - kvKeyAttr: ks.key, - }, - ProjectionExpression: aws.String(`NonExistent`), - } - - ks.queryRequest = dynamodb.QueryInput{ - TableName: aws.String(s.Table), - KeyConditionExpression: aws.String(`#S = :S`), - ProjectionExpression: aws.String("#K, #V"), - ExpressionAttributeNames: map[string]string{ - "#S": kvKeyspaceAttr, - "#K": kvKeyAttr, - "#V": kvValueAttr, - }, - ExpressionAttributeValues: map[string]types.AttributeValue{ - ":S": ks.name, - }, - } - - ks.putRequest = dynamodb.PutItemInput{ - TableName: aws.String(s.Table), - Item: map[string]types.AttributeValue{ - kvKeyspaceAttr: ks.name, - kvKeyAttr: ks.key, - kvValueAttr: ks.value, - }, - } - - ks.deleteRequest = dynamodb.DeleteItemInput{ - TableName: aws.String(s.Table), - Key: map[string]types.AttributeValue{ - kvKeyspaceAttr: ks.name, - kvKeyAttr: ks.key, - }, - } - - return ks, nil -} - -type keyspace struct { - Client *dynamodb.Client - DecorateGetItem func(*dynamodb.GetItemInput) []func(*dynamodb.Options) - DecorateQuery func(*dynamodb.QueryInput) []func(*dynamodb.Options) - DecoratePutItem func(*dynamodb.PutItemInput) []func(*dynamodb.Options) - DecorateDeleteItem func(*dynamodb.DeleteItemInput) []func(*dynamodb.Options) - - name *types.AttributeValueMemberS - key *types.AttributeValueMemberB - value *types.AttributeValueMemberB - - getRequest dynamodb.GetItemInput - hasRequest dynamodb.GetItemInput - queryRequest dynamodb.QueryInput - putRequest dynamodb.PutItemInput - deleteRequest dynamodb.DeleteItemInput -} - -func (ks *keyspace) Get(ctx context.Context, k []byte) ([]byte, error) { - ks.key.Value = k - - out, err := awsx.Do( - ctx, - ks.Client.GetItem, - ks.DecorateGetItem, - &ks.getRequest, - ) - if err != nil || out.Item == nil { - return nil, err - } - - v, err := getAttr[*types.AttributeValueMemberB](out.Item, kvValueAttr) - if err != nil { - return nil, err - } - - return v.Value, nil - -} - -func (ks *keyspace) Has(ctx context.Context, k []byte) (bool, error) { - ks.key.Value = k - - out, err := awsx.Do( - ctx, - ks.Client.GetItem, - ks.DecorateGetItem, - &ks.hasRequest, - ) - if err != nil { - return false, err - } - - return out.Item != nil, nil -} - -func (ks *keyspace) Set(ctx context.Context, k, v []byte) error { - if v == nil { - return ks.delete(ctx, k) - } - - return ks.set(ctx, k, v) -} - -func (ks *keyspace) set(ctx context.Context, k, v []byte) error { - ks.key.Value = k - ks.value.Value = v - - _, err := awsx.Do( - ctx, - ks.Client.PutItem, - ks.DecoratePutItem, - &ks.putRequest, - ) - - return err -} - -func (ks *keyspace) delete(ctx context.Context, k []byte) error { - ks.key.Value = k - - _, err := awsx.Do( - ctx, - ks.Client.DeleteItem, - ks.DecorateDeleteItem, - &ks.deleteRequest, - ) - - return err -} - -func (ks *keyspace) Range( - ctx context.Context, - fn kv.RangeFunc, -) error { - ks.queryRequest.ExclusiveStartKey = nil - - for { - out, err := awsx.Do( - ctx, - ks.Client.Query, - ks.DecorateQuery, - &ks.queryRequest, - ) - if err != nil { - return err - } - - for _, item := range out.Items { - key, err := getAttr[*types.AttributeValueMemberB](item, kvKeyAttr) - if err != nil { - return err - } - - value, err := getAttr[*types.AttributeValueMemberB](item, kvValueAttr) - if err != nil { - return err - } - - ok, err := fn(ctx, key.Value, value.Value) - if !ok || err != nil { - return err - } - } - - if out.LastEvaluatedKey == nil { - return nil - } - - ks.queryRequest.ExclusiveStartKey = out.LastEvaluatedKey - } -} - -func (ks *keyspace) Close() error { - return nil -} - -// CreateKeyValueStoreTable creates a DynamoDB table for use with -// [KeyValueStore]. -func CreateKeyValueStoreTable( - ctx context.Context, - client *dynamodb.Client, - table string, - decorators ...func(*dynamodb.CreateTableInput) []func(*dynamodb.Options), -) error { - _, err := awsx.Do( - ctx, - client.CreateTable, - func(in *dynamodb.CreateTableInput) []func(*dynamodb.Options) { - var options []func(*dynamodb.Options) - for _, dec := range decorators { - options = append(options, dec(in)...) - } - - return options - }, - &dynamodb.CreateTableInput{ - TableName: aws.String(table), - AttributeDefinitions: []types.AttributeDefinition{ - { - AttributeName: aws.String(kvKeyspaceAttr), - AttributeType: types.ScalarAttributeTypeS, - }, - { - AttributeName: aws.String(kvKeyAttr), - AttributeType: types.ScalarAttributeTypeB, - }, - }, - KeySchema: []types.KeySchemaElement{ - { - AttributeName: aws.String(kvKeyspaceAttr), - KeyType: types.KeyTypeHash, - }, - { - AttributeName: aws.String(kvKeyAttr), - KeyType: types.KeyTypeRange, - }, - }, - BillingMode: types.BillingModePayPerRequest, - }, - ) - - if errors.As(err, new(*types.ResourceInUseException)) { - return nil - } - - return err -} diff --git a/persistence/driver/aws/dynamodb/keyvalue_test.go b/persistence/driver/aws/dynamodb/keyvalue_test.go deleted file mode 100644 index a4ef614a..00000000 --- a/persistence/driver/aws/dynamodb/keyvalue_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package dynamodb_test - -import ( - "context" - "testing" - "time" - - . "github.com/dogmatiq/veracity/persistence/driver/aws/dynamodb" - "github.com/dogmatiq/veracity/persistence/kv" -) - -func TestKeyValueStore(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - - client := newClient(t) - table := "kvstore" - - if err := CreateKeyValueStoreTable(ctx, client, table); err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := deleteTable(ctx, client, table); err != nil { - t.Fatal(err) - } - - cancel() - }) - - kv.RunTests( - t, - func(t *testing.T) kv.Store { - return &KeyValueStore{ - Client: client, - Table: table, - } - }, - ) -} diff --git a/persistence/driver/aws/dynamodb/util.go b/persistence/driver/aws/dynamodb/util.go deleted file mode 100644 index 010158c1..00000000 --- a/persistence/driver/aws/dynamodb/util.go +++ /dev/null @@ -1,30 +0,0 @@ -package dynamodb - -import ( - "fmt" - "reflect" - - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" -) - -func getAttr[T types.AttributeValue]( - item map[string]types.AttributeValue, - name string, -) (v T, err error) { - a, ok := item[name] - if !ok { - return v, fmt.Errorf("item is corrupt: missing %q attribute", name) - } - - v, ok = a.(T) - if !ok { - return v, fmt.Errorf( - "item is corrupt: %q attribute should be %s not %s", - name, - reflect.TypeOf(v).Name(), - reflect.TypeOf(a).Name(), - ) - } - - return v, nil -} diff --git a/persistence/driver/aws/dynamodb/util_test.go b/persistence/driver/aws/dynamodb/util_test.go deleted file mode 100644 index 198c9f24..00000000 --- a/persistence/driver/aws/dynamodb/util_test.go +++ /dev/null @@ -1,71 +0,0 @@ -package dynamodb_test - -import ( - "context" - "errors" - "os" - "testing" - - "github.com/aws/aws-sdk-go-v2/aws" - "github.com/aws/aws-sdk-go-v2/config" - "github.com/aws/aws-sdk-go-v2/credentials" - "github.com/aws/aws-sdk-go-v2/service/dynamodb" - "github.com/aws/aws-sdk-go-v2/service/dynamodb/types" -) - -func newClient(t *testing.T) *dynamodb.Client { - endpoint := os.Getenv("DOGMATIQ_TEST_DYNAMODB_ENDPOINT") - if endpoint == "" { - endpoint = "http://localhost:28000" - } - - cfg, err := config.LoadDefaultConfig( - context.Background(), - config.WithRegion("us-east-1"), - config.WithEndpointResolverWithOptions( - aws.EndpointResolverWithOptionsFunc( - func(service, region string, options ...any) (aws.Endpoint, error) { - return aws.Endpoint{URL: endpoint}, nil - }, - ), - ), - config.WithCredentialsProvider( - credentials.StaticCredentialsProvider{ - Value: aws.Credentials{ - AccessKeyID: "id", - SecretAccessKey: "secret", - SessionToken: "", - }, - }, - ), - config.WithRetryer( - func() aws.Retryer { - return aws.NopRetryer{} - }, - ), - ) - if err != nil { - t.Fatal(err) - } - - return dynamodb.NewFromConfig(cfg) -} - -func deleteTable( - ctx context.Context, - client *dynamodb.Client, - table string, -) error { - if _, err := client.DeleteTable( - ctx, - &dynamodb.DeleteTableInput{ - TableName: aws.String(table), - }, - ); err != nil { - if !errors.As(err, new(*types.ResourceNotFoundException)) { - return err - } - } - - return nil -} diff --git a/persistence/driver/aws/internal/awsx/do.go b/persistence/driver/aws/internal/awsx/do.go deleted file mode 100644 index 51c2a246..00000000 --- a/persistence/driver/aws/internal/awsx/do.go +++ /dev/null @@ -1,38 +0,0 @@ -package awsx - -import ( - "context" - - "github.com/aws/aws-sdk-go-v2/service/dynamodb" -) - -// Do executes an AWS API request. -// -// fn is a function that is called to execute the request, typically a method on -// a *dynamodb.DynamoDB client. -// -// dec is a decorator function that mutates the input value before it is sent -// and returns any options that should be used when sending the request. -func Do[In, Out any]( - ctx context.Context, - fn func(context.Context, *In, ...func(*dynamodb.Options)) (Out, error), - dec func(*In) []func(*dynamodb.Options), - in *In, - options ...func(*dynamodb.Options), -) (out Out, err error) { - options = append(options, Decorate(in, dec)...) - return fn(ctx, in, options...) -} - -// Decorate mutates an input value in-place and returns any options that should -// be used when sending the request. -func Decorate[In any]( - in *In, - dec func(*In) []func(*dynamodb.Options), -) []func(*dynamodb.Options) { - if dec != nil { - return dec(in) - } - - return nil -} diff --git a/persistence/driver/memory/doc.go b/persistence/driver/memory/doc.go deleted file mode 100644 index e931e119..00000000 --- a/persistence/driver/memory/doc.go +++ /dev/null @@ -1,6 +0,0 @@ -// Package memory provides low-level persistence implementations that store data -// in memory. -// -// They are intended to be used as reference implementations and are also used -// throughout various internal test suites. -package memory diff --git a/persistence/driver/memory/journal.go b/persistence/driver/memory/journal.go deleted file mode 100644 index 293d3995..00000000 --- a/persistence/driver/memory/journal.go +++ /dev/null @@ -1,185 +0,0 @@ -package memory - -import ( - "context" - "errors" - "sync" - - "github.com/dogmatiq/veracity/persistence/journal" - "golang.org/x/exp/slices" -) - -// JournalStore is an implementation of [journal.Store] that stores journals in -// memory. -type JournalStore struct { - journals sync.Map // map[string]*journalState - onOpen func(name string) error -} - -// Open returns the journal with the given name. -func (s *JournalStore) Open(ctx context.Context, name string) (journal.Journal, error) { - if s.onOpen != nil { - if err := s.onOpen(name); err != nil { - return nil, err - } - } - - state, ok := s.journals.Load(name) - - if !ok { - state, _ = s.journals.LoadOrStore( - name, - &journalState{}, - ) - } - - return &journalHandle{ - state: state.(*journalState), - }, ctx.Err() -} - -// NewJournal returns a new standalone journal. -func NewJournal() journal.Journal { - return &journalHandle{ - state: &journalState{}, - } -} - -// journalState stores the underlying state of a journal. -type journalState struct { - sync.RWMutex - - Begin, End journal.Position - Records [][]byte - - BeforeAppend func([]byte) error - AfterAppend func([]byte) error -} - -// journalHandle is an implementation of [journal.Journal] that accesses -// in-memory journal state. -type journalHandle struct { - state *journalState -} - -func (h *journalHandle) Bounds(ctx context.Context) (begin, end journal.Position, err error) { - if h.state == nil { - panic("journal is closed") - } - - h.state.RLock() - defer h.state.RUnlock() - - return h.state.Begin, h.state.End, ctx.Err() -} - -func (h *journalHandle) Get(ctx context.Context, pos journal.Position) ([]byte, error) { - if h.state == nil { - panic("journal is closed") - } - - h.state.RLock() - defer h.state.RUnlock() - - if pos < h.state.Begin || pos >= h.state.End { - return nil, journal.ErrNotFound - } - - return slices.Clone(h.state.Records[pos-h.state.Begin]), ctx.Err() -} - -func (h *journalHandle) Range( - ctx context.Context, - begin journal.Position, - fn journal.RangeFunc, -) error { - if h.state == nil { - panic("journal is closed") - } - - h.state.RLock() - first := h.state.Begin - records := h.state.Records - h.state.RUnlock() - - if first > begin { - return journal.ErrNotFound - } - - start := begin - first - for i, rec := range records[start:] { - v := start + journal.Position(i) - ok, err := fn(ctx, v, slices.Clone(rec)) - if !ok || err != nil { - return err - } - begin++ - } - - return ctx.Err() -} - -func (h *journalHandle) Append(ctx context.Context, end journal.Position, rec []byte) error { - if h.state == nil { - panic("journal is closed") - } - - rec = slices.Clone(rec) - - h.state.Lock() - defer h.state.Unlock() - - if h.state.BeforeAppend != nil { - if err := h.state.BeforeAppend(rec); err != nil { - return err - } - } - - switch { - case end < h.state.End: - return journal.ErrConflict - case end == h.state.End: - h.state.Records = append(h.state.Records, rec) - h.state.End++ - default: - panic("position out of range, this causes undefined behavior in a 'real' journal implementation") - } - - if h.state.AfterAppend != nil { - if err := h.state.AfterAppend(rec); err != nil { - return err - } - } - - return ctx.Err() -} - -func (h *journalHandle) Truncate(ctx context.Context, end journal.Position) error { - if h.state == nil { - panic("journal is closed") - } - - h.state.Lock() - defer h.state.Unlock() - - if end > h.state.End { - panic("position out of range, this causes undefined behavior in a real journal implementation") - } - - if end > h.state.Begin { - h.state.Records = h.state.Records[end-h.state.Begin:] - h.state.Begin = end - } - - return ctx.Err() -} - -func (h *journalHandle) Close() error { - if h.state == nil { - return errors.New("journal is already closed") - } - - h.state = nil - - return nil -} diff --git a/persistence/driver/memory/journal_test.go b/persistence/driver/memory/journal_test.go deleted file mode 100644 index 826383ee..00000000 --- a/persistence/driver/memory/journal_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package memory_test - -import ( - "testing" - - . "github.com/dogmatiq/veracity/persistence/driver/memory" - "github.com/dogmatiq/veracity/persistence/journal" -) - -func TestJournalStore(t *testing.T) { - journal.RunTests( - t, - func(t *testing.T) journal.Store { - return &JournalStore{} - }, - ) -} diff --git a/persistence/driver/memory/keyvalue.go b/persistence/driver/memory/keyvalue.go deleted file mode 100644 index bcc7ef80..00000000 --- a/persistence/driver/memory/keyvalue.go +++ /dev/null @@ -1,136 +0,0 @@ -package memory - -import ( - "context" - "errors" - "sync" - - "github.com/dogmatiq/veracity/persistence/kv" - "golang.org/x/exp/maps" - "golang.org/x/exp/slices" -) - -// KeyValueStore is an implementation of [kv.Store] that stores keyspaces in -// memory. -type KeyValueStore struct { - keyspaces sync.Map // map[string]*keyspaceState -} - -// Open returns the keyspace with the given name. -func (s *KeyValueStore) Open(ctx context.Context, name string) (kv.Keyspace, error) { - state, ok := s.keyspaces.Load(name) - - if !ok { - state, _ = s.keyspaces.LoadOrStore( - name, - &keyspaceState{}, - ) - } - - return &keyspaceHandle{ - state: state.(*keyspaceState), - }, ctx.Err() -} - -type keyspaceState struct { - sync.RWMutex - - Values map[string][]byte - - BeforeSet func(k, v []byte) error - AfterSet func(k, v []byte) error -} - -type keyspaceHandle struct { - state *keyspaceState -} - -func (h *keyspaceHandle) Get(ctx context.Context, k []byte) (v []byte, err error) { - if h.state == nil { - panic("keyspace is closed") - } - - h.state.RLock() - defer h.state.RUnlock() - - return slices.Clone(h.state.Values[string(k)]), ctx.Err() -} - -func (h *keyspaceHandle) Has(ctx context.Context, k []byte) (ok bool, err error) { - if h.state == nil { - panic("keyspace is closed") - } - - h.state.RLock() - defer h.state.RUnlock() - - _, ok = h.state.Values[string(k)] - return ok, ctx.Err() -} - -func (h *keyspaceHandle) Set(ctx context.Context, k, v []byte) error { - if h.state == nil { - panic("keyspace is closed") - } - - v = slices.Clone(v) - - h.state.Lock() - defer h.state.Unlock() - - if h.state.BeforeSet != nil { - if err := h.state.BeforeSet(k, v); err != nil { - return err - } - } - - if len(v) == 0 { - delete(h.state.Values, string(k)) - } else { - if h.state.Values == nil { - h.state.Values = map[string][]byte{} - } - - h.state.Values[string(k)] = v - } - - if h.state.AfterSet != nil { - if err := h.state.AfterSet(k, v); err != nil { - return err - } - } - - return ctx.Err() -} - -func (h *keyspaceHandle) Range( - ctx context.Context, - fn kv.RangeFunc, -) error { - if h.state == nil { - panic("keyspace is closed") - } - - h.state.RLock() - values := maps.Clone(h.state.Values) - h.state.RUnlock() - - for k, v := range values { - ok, err := fn(ctx, []byte(k), slices.Clone(v)) - if !ok || err != nil { - return err - } - } - - return nil -} - -func (h *keyspaceHandle) Close() error { - if h.state == nil { - return errors.New("keyspace is already closed") - } - - h.state = nil - - return nil -} diff --git a/persistence/driver/memory/keyvalue_test.go b/persistence/driver/memory/keyvalue_test.go deleted file mode 100644 index c8964f45..00000000 --- a/persistence/driver/memory/keyvalue_test.go +++ /dev/null @@ -1,17 +0,0 @@ -package memory_test - -import ( - "testing" - - . "github.com/dogmatiq/veracity/persistence/driver/memory" - "github.com/dogmatiq/veracity/persistence/kv" -) - -func TestKeyValueStore(t *testing.T) { - kv.RunTests( - t, - func(t *testing.T) kv.Store { - return &KeyValueStore{} - }, - ) -} diff --git a/persistence/driver/postgres/doc.go b/persistence/driver/postgres/doc.go deleted file mode 100644 index 3846a5d7..00000000 --- a/persistence/driver/postgres/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package postgres provides low-level persistence implementations that store -// data in PostgreSQL database. -package postgres diff --git a/persistence/driver/postgres/journal.go b/persistence/driver/postgres/journal.go deleted file mode 100644 index c1259a15..00000000 --- a/persistence/driver/postgres/journal.go +++ /dev/null @@ -1,188 +0,0 @@ -package postgres - -import ( - "context" - "database/sql" - - "github.com/dogmatiq/veracity/persistence/journal" -) - -// JournalStore is an implementation of [journal.Store] that contains journals -// that persist records in a PostgresSQL table. -type JournalStore struct { - // DB is the PostgreSQL database connection. - DB *sql.DB -} - -// Open returns the journal with the given name. -func (s *JournalStore) Open(ctx context.Context, name string) (journal.Journal, error) { - return &journ{ - Name: name, - DB: s.DB, - }, nil -} - -// journ is an implementation of journal.Journal that stores records in -// a DynamoDB table. -type journ struct { - Name string - DB *sql.DB -} - -func (j *journ) Bounds(ctx context.Context) (begin, end journal.Position, err error) { - row := j.DB.QueryRowContext( - ctx, - `SELECT - COALESCE(MIN(position), 0), - COALESCE(MAX(position) + 1, 0) - FROM veracity.journal - WHERE name = $1`, - j.Name, - ) - - err = row.Scan(&begin, &end) - return begin, end, err -} - -func (j *journ) Get(ctx context.Context, pos journal.Position) ([]byte, error) { - row := j.DB.QueryRowContext( - ctx, - `SELECT record - FROM veracity.journal - WHERE name = $1 - AND position = $2`, - j.Name, - pos, - ) - - var rec []byte - err := row.Scan(&rec) - if err == sql.ErrNoRows { - return nil, journal.ErrNotFound - } - - return rec, err -} - -func (j *journ) Range( - ctx context.Context, - begin journal.Position, - fn journal.RangeFunc, -) error { - // TODO: "paginate" results across multiple queries to avoid loading - // everything into memory at once. - rows, err := j.DB.QueryContext( - ctx, - `SELECT position, record - FROM veracity.journal - WHERE name = $1 - AND position >= $2 - ORDER BY position`, - j.Name, - begin, - ) - if err != nil { - return err - } - defer rows.Close() - - expectPos := begin - - for rows.Next() { - var ( - pos journal.Position - rec []byte - ) - if err := rows.Scan(&pos, &rec); err != nil { - return err - } - - if pos != expectPos { - return journal.ErrNotFound - } - - expectPos++ - - ok, err := fn(ctx, pos, rec) - if !ok || err != nil { - return err - } - } - - return rows.Err() -} - -func (j *journ) Append(ctx context.Context, end journal.Position, rec []byte) error { - res, err := j.DB.ExecContext( - ctx, - `INSERT INTO veracity.journal - (name, position, record) VALUES ($1, $2, $3) - ON CONFLICT (name, position) DO NOTHING`, - j.Name, - end, - rec, - ) - if err != nil { - return err - } - - n, err := res.RowsAffected() - if err != nil { - return err - } - - if n != 1 { - return journal.ErrConflict - } - - return nil -} - -func (j *journ) Truncate(ctx context.Context, end journal.Position) error { - _, err := j.DB.ExecContext( - ctx, - `DELETE FROM veracity.journal - WHERE name = $1 - AND position < $2`, - j.Name, - end, - ) - - return err -} - -func (j *journ) Close() error { - return nil -} - -// CreateJournalStoreSchema creates the PostgreSQL schema elements required by -// [JournalStore]. -func CreateJournalStoreSchema( - ctx context.Context, - db *sql.DB, -) error { - tx, err := db.Begin() - if err != nil { - return err - } - defer tx.Rollback() // nolint:errcheck - - if _, err := db.ExecContext(ctx, `CREATE SCHEMA IF NOT EXISTS veracity`); err != nil { - return err - } - - if _, err := db.ExecContext( - ctx, - `CREATE TABLE IF NOT EXISTS veracity.journal ( - name TEXT NOT NULL, - position BIGINT NOT NULL, - record BYTEA NOT NULL, - - PRIMARY KEY (name, position) - )`, - ); err != nil { - return err - } - - return nil -} diff --git a/persistence/driver/postgres/journal_test.go b/persistence/driver/postgres/journal_test.go deleted file mode 100644 index 4ad5958a..00000000 --- a/persistence/driver/postgres/journal_test.go +++ /dev/null @@ -1,46 +0,0 @@ -package postgres_test - -import ( - "context" - "testing" - - "github.com/dogmatiq/sqltest" - . "github.com/dogmatiq/veracity/persistence/driver/postgres" - "github.com/dogmatiq/veracity/persistence/journal" -) - -func TestJournalStore(t *testing.T) { - ctx := context.Background() - database, err := sqltest.NewDatabase(ctx, sqltest.PGXDriver, sqltest.PostgreSQL) - if err != nil { - t.Fatal(err) - } - - db, err := database.Open() - if err != nil { - t.Fatal(err) - } - - if err := CreateJournalStoreSchema(ctx, db); err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := db.Close(); err != nil { - t.Fatal(err) - } - - if err := database.Close(); err != nil { - t.Fatal(err) - } - }) - - journal.RunTests( - t, - func(t *testing.T) journal.Store { - return &JournalStore{ - DB: db, - } - }, - ) -} diff --git a/persistence/driver/postgres/keyvalue.go b/persistence/driver/postgres/keyvalue.go deleted file mode 100644 index 1fb292a9..00000000 --- a/persistence/driver/postgres/keyvalue.go +++ /dev/null @@ -1,174 +0,0 @@ -package postgres - -import ( - "context" - "database/sql" - - "github.com/dogmatiq/veracity/persistence/kv" -) - -// KeyValueStore is an implementation of [kv.Store] that stores keyspaces in a -// PostgreSQL database. -type KeyValueStore struct { - DB *sql.DB -} - -// Open returns the keyspace with the given name. -func (s *KeyValueStore) Open(ctx context.Context, name string) (kv.Keyspace, error) { - return &keyspace{ - Name: name, - DB: s.DB, - }, nil -} - -type keyspace struct { - Name string - DB *sql.DB -} - -func (ks *keyspace) Get(ctx context.Context, k []byte) (v []byte, err error) { - row := ks.DB.QueryRowContext( - ctx, - `SELECT - value - FROM veracity.kv - WHERE keyspace = $1 - AND key = $2`, - ks.Name, - k, - ) - - var value []byte - err = row.Scan(&value) - if err == sql.ErrNoRows { - err = nil - } - - return value, err -} - -func (ks *keyspace) Has(ctx context.Context, k []byte) (ok bool, err error) { - row := ks.DB.QueryRowContext( - ctx, - `SELECT - 1 - FROM veracity.kv - WHERE keyspace = $1 - AND key = $2`, - ks.Name, - k, - ) - - var value []byte - err = row.Scan(&value) - if err == sql.ErrNoRows { - return false, nil - } - - return true, err -} - -func (ks *keyspace) Set(ctx context.Context, k, v []byte) error { - if len(v) == 0 { - _, err := ks.DB.ExecContext( - ctx, - `DELETE FROM veracity.kv - WHERE keyspace = $1 - AND key = $2`, - ks.Name, - k, - ) - - return err - } - - _, err := ks.DB.ExecContext( - ctx, - `INSERT INTO veracity.kv AS o ( - keyspace, - key, - value - ) VALUES ( - $1, $2, $3 - ) ON CONFLICT (keyspace, key) DO UPDATE SET - value = $3 - `, - ks.Name, - k, - v, - ) - - return err -} - -func (ks *keyspace) Range( - ctx context.Context, - fn kv.RangeFunc, -) error { - rows, err := ks.DB.QueryContext( - ctx, - `SELECT - key, - value - FROM veracity.kv - WHERE keyspace = $1`, - ks.Name, - ) - if err != nil { - return err - } - defer rows.Close() - - for rows.Next() { - var ( - k []byte - v []byte - ) - if err := rows.Scan(&k, &v); err != nil { - return err - } - - ok, err := fn(ctx, k, v) - if !ok || err != nil { - return err - } - } - - return rows.Err() -} - -func (ks *keyspace) Close() error { - return nil -} - -// CreateKeyValueStoreSchema creates the PostgreSQL schema elements required by -// [KeyValueStore]. -func CreateKeyValueStoreSchema( - ctx context.Context, - db *sql.DB, -) error { - tx, err := db.Begin() - if err != nil { - return err - } - defer tx.Rollback() // nolint:errcheck - - if _, err := db.ExecContext(ctx, `CREATE SCHEMA IF NOT EXISTS veracity`); err != nil { - return err - } - - if _, err := db.ExecContext( - ctx, - `CREATE TABLE IF NOT EXISTS veracity.kv ( - keyspace TEXT NOT NULL, - key BYTEA NOT NULL, - value BYTEA NOT NULL, - - PRIMARY KEY (keyspace, key) - )`, - ); err != nil { - return err - } - - return nil -} diff --git a/persistence/driver/postgres/keyvalue_test.go b/persistence/driver/postgres/keyvalue_test.go deleted file mode 100644 index 16b96caa..00000000 --- a/persistence/driver/postgres/keyvalue_test.go +++ /dev/null @@ -1,47 +0,0 @@ -package postgres_test - -import ( - "context" - "testing" - - "github.com/dogmatiq/sqltest" - . "github.com/dogmatiq/veracity/persistence/driver/postgres" - "github.com/dogmatiq/veracity/persistence/kv" -) - -func TestKeyValueStore(t *testing.T) { - ctx := context.Background() - - database, err := sqltest.NewDatabase(ctx, sqltest.PGXDriver, sqltest.PostgreSQL) - if err != nil { - t.Fatal(err) - } - - db, err := database.Open() - if err != nil { - t.Fatal(err) - } - - if err := CreateKeyValueStoreSchema(ctx, db); err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := db.Close(); err != nil { - t.Fatal(err) - } - - if err := database.Close(); err != nil { - t.Fatal(err) - } - }) - - kv.RunTests( - t, - func(t *testing.T) kv.Store { - return &KeyValueStore{ - DB: db, - } - }, - ) -} diff --git a/persistence/journal/doc.go b/persistence/journal/doc.go deleted file mode 100644 index b683fd18..00000000 --- a/persistence/journal/doc.go +++ /dev/null @@ -1,3 +0,0 @@ -// Package journal provides an abstraction of an append-only log with optimistic -// concurrency control. -package journal diff --git a/persistence/journal/journal.go b/persistence/journal/journal.go deleted file mode 100644 index b420b9fe..00000000 --- a/persistence/journal/journal.go +++ /dev/null @@ -1,71 +0,0 @@ -package journal - -import ( - "context" - "errors" -) - -// Position is the index of a record within a [Journal]. The first record is always -// at position 0. -type Position uint64 - -// A RangeFunc is a function used to range over the records in a [Journal]. -// -// If err is non-nil, ranging stops and err is propagated up the stack. -// Otherwise, if ok is false, ranging stops without any error being propagated. -type RangeFunc func(context.Context, Position, []byte) (ok bool, err error) - -var ( - // ErrNotFound is returned by [Journal.Get] and [Journal.Range] if the - // requested record does not exist, either because it has been truncated or - // because the given position has not been written yet. - ErrNotFound = errors.New("record not found") - - // ErrConflict is returned by [Journal.Append] if there is already a record at - // the specified position. - ErrConflict = errors.New("optimistic concurrency conflict") -) - -// A Journal is an append-only log of binary records. -type Journal interface { - // Bounds returns the half-open range [begin, end) describing the positions - // of the first and last journal records that are available for reading. - Bounds(ctx context.Context) (begin, end Position, err error) - - // Get returns the record at the given position. - // - // It returns [ErrNotFound] if there is no record at the given position. - Get(ctx context.Context, pos Position) (rec []byte, err error) - - // Range invokes fn for each record in the journal, in order, starting with - // the record at the given position. - // - // It returns [ErrNotFound] if there is no record at the given position. - Range(ctx context.Context, pos Position, fn RangeFunc) error - - // Append adds a record to the journal. - // - // end must be the next "unused" position in the journal; the first position - // is always 0. - // - // If there is already a record at the given position then [ErrConflict] is - // returned, indicating an optimistic concurrency conflict. - // - // The behavior is undefined if end is greater than the next position. - Append(ctx context.Context, end Position, rec []byte) error - - // Truncate removes journal records in the half-open range [0, end). That - // is, it removes the oldest records up to, but not including, the record at - // the given position. - // - // If it returns a non-nil error the truncation may have been partially - // applied. That is, some of the records may have been removed but not all. - // The implementation must guarantee that the oldest records are removed - // first, such that there is never a "gap" between positions. - // - // The behavior is undefined if end is greater than the next position. - Truncate(ctx context.Context, end Position) error - - // Close closes the journal. - Close() error -} diff --git a/persistence/journal/store.go b/persistence/journal/store.go deleted file mode 100644 index a86a6e56..00000000 --- a/persistence/journal/store.go +++ /dev/null @@ -1,11 +0,0 @@ -package journal - -import ( - "context" -) - -// Store is a collection of journals. -type Store interface { - // Open returns the journal with the given name. - Open(ctx context.Context, name string) (Journal, error) -} diff --git a/persistence/journal/test.go b/persistence/journal/test.go deleted file mode 100644 index 266587d1..00000000 --- a/persistence/journal/test.go +++ /dev/null @@ -1,564 +0,0 @@ -package journal - -import ( - "bytes" - "context" - "errors" - "fmt" - "sync/atomic" - "testing" - "time" - - "github.com/dogmatiq/veracity/internal/test" -) - -// RunTests runs tests that confirm a journal implementation behaves correctly. -func RunTests( - t *testing.T, - newStore func(t *testing.T) Store, -) { - type dependencies struct { - Store Store - JournalName string - Journal Journal - } - - setup := func( - t *testing.T, - newStore func(t *testing.T) Store, - ) *dependencies { - deps := &dependencies{ - Store: newStore(t), - JournalName: fmt.Sprintf("", journalCounter.Add(1)), - } - - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - j, err := deps.Store.Open(ctx, deps.JournalName) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := j.Close(); err != nil { - t.Fatal(err) - } - }) - - deps.Journal = j - - return deps - } - - t.Run("type Store", func(t *testing.T) { - t.Parallel() - - t.Run("func Open()", func(t *testing.T) { - t.Parallel() - - t.Run("allows a journal to be opened multiple times", func(t *testing.T) { - t.Parallel() - - tctx := test.WithContext(t) - store := newStore(t) - - j1, err := store.Open(tctx, "") - if err != nil { - t.Fatal(err) - } - defer j1.Close() - - j2, err := store.Open(tctx, "") - if err != nil { - t.Fatal(err) - } - defer j2.Close() - - want := []byte("") - if err := j1.Append(tctx, 0, want); err != nil { - t.Fatal(err) - } - - got, err := j2.Get(tctx, 0) - if err != nil { - t.Fatal(err) - } - - test.Expect( - t, - "unexpected journal record", - got, - want, - ) - }) - }) - }) - - t.Run("type Journal", func(t *testing.T) { - t.Parallel() - - t.Run("func Bounds()", func(t *testing.T) { - t.Parallel() - - t.Run("it returns the expected bounds", func(t *testing.T) { - cases := []struct { - Desc string - ExpectBegin, ExpectEnd Position - Setup func(context.Context, *testing.T, Journal) - }{ - { - "empty", - 0, 0, - func(ctx context.Context, t *testing.T, j Journal) {}, - }, - { - "with records", - 0, 10, - func(ctx context.Context, t *testing.T, j Journal) { - appendRecords(ctx, t, j, 10) - }, - }, - { - "with truncated records", - 5, 10, - func(ctx context.Context, t *testing.T, j Journal) { - appendRecords(ctx, t, j, 10) - if err := j.Truncate(ctx, 5); err != nil { - t.Fatal(err) - } - }, - }, - } - - for _, c := range cases { - c := c // capture loop variable - t.Run(c.Desc, func(t *testing.T) { - t.Parallel() - - tctx := test.WithContext(t) - deps := setup(t, newStore) - - c.Setup(tctx, t, deps.Journal) - - begin, end, err := deps.Journal.Bounds(tctx) - if err != nil { - t.Fatal(err) - } - - test.Expect( - t, - "unexpected begin position", - begin, - c.ExpectBegin, - ) - - test.Expect( - t, - "unexpected end position", - end, - c.ExpectEnd, - ) - }) - } - }) - }) - - t.Run("func Get()", func(t *testing.T) { - t.Parallel() - - t.Run("it returns ErrNotFound if there is no record at the given position", func(t *testing.T) { - t.Parallel() - - tctx := test.WithContext(t) - deps := setup(t, newStore) - - _, err := deps.Journal.Get(tctx, 1) - if !errors.Is(err, ErrNotFound) { - t.Fatal(err) - } - }) - - t.Run("it returns the record if it exists", func(t *testing.T) { - t.Parallel() - - tctx := test.WithContext(t) - deps := setup(t, newStore) - - // Ensure we test with a position that becomes 2 digits long to - // confirm that the implementation is not using a lexical sort. - records := appendRecords(tctx, t, deps.Journal, 15) - - for i, want := range records { - got, err := deps.Journal.Get(tctx, Position(i)) - if err != nil { - t.Fatal(err) - } - - test.Expect( - t, - fmt.Sprintf("unexpected record at position %d", i), - got, - want, - ) - - if !bytes.Equal(want, got) { - t.Fatalf( - "unexpected record, want %q, got %q", - string(want), - string(got), - ) - } - } - }) - - t.Run("it does not return its internal byte slice", func(t *testing.T) { - t.Parallel() - - tctx := test.WithContext(t) - deps := setup(t, newStore) - - appendRecords(tctx, t, deps.Journal, 1) - - rec, err := deps.Journal.Get(tctx, 0) - if err != nil { - t.Fatal(err) - } - - rec[0] = 'X' - - got, err := deps.Journal.Get(tctx, 0) - if err != nil { - t.Fatal(err) - } - - test.Expect( - t, - "unexpected record", - got, - []byte(""), - ) - }) - }) - - t.Run("func Range()", func(t *testing.T) { - t.Parallel() - - t.Run("calls the function for each record in the journal", func(t *testing.T) { - t.Parallel() - - tctx := test.WithContext(t) - deps := setup(t, newStore) - - want := appendRecords(tctx, t, deps.Journal, 15) - - var got [][]byte - wantPos := Position(10) - want = want[wantPos:] - - if err := deps.Journal.Range( - tctx, - wantPos, - func(ctx context.Context, gotPos Position, rec []byte) (bool, error) { - test.Expect( - t, - "unexpected position", - gotPos, - wantPos, - ) - - got = append(got, rec) - wantPos++ - - return true, nil - }, - ); err != nil { - t.Fatal(err) - } - - test.Expect( - t, - "unexpected records", - got, - want, - ) - }) - - t.Run("it stops iterating if the function returns false", func(t *testing.T) { - t.Parallel() - - tctx := test.WithContext(t) - deps := setup(t, newStore) - - appendRecords(tctx, t, deps.Journal, 2) - - called := false - if err := deps.Journal.Range( - tctx, - 0, - func(ctx context.Context, pos Position, rec []byte) (bool, error) { - if called { - return false, errors.New("unexpected call") - } - - called = true - return false, nil - }, - ); err != nil { - t.Fatal(err) - } - }) - - t.Run("it returns ErrNotFound if the first record is truncated", func(t *testing.T) { - t.Parallel() - - tctx := test.WithContext(t) - deps := setup(t, newStore) - - records := appendRecords(tctx, t, deps.Journal, 5) - retainPos := Position(len(records) - 1) - - err := deps.Journal.Truncate(tctx, retainPos) - if err != nil { - t.Fatal(err) - } - - err = deps.Journal.Range( - tctx, - 1, - func(ctx context.Context, pos Position, rec []byte) (bool, error) { - panic("unexpected call") - }, - ) - - test.Expect( - t, - "unexpected error", - err, - ErrNotFound, - ) - }) - - t.Run("it returns an error if a record is truncated during iteration", func(t *testing.T) { - t.Skip() // TODO - t.Parallel() - - tctx := test.WithContext(t) - deps := setup(t, newStore) - - appendRecords(tctx, t, deps.Journal, 5) - - err := deps.Journal.Range( - tctx, - 0, - func(ctx context.Context, pos Position, rec []byte) (bool, error) { - return true, deps.Journal.Truncate(ctx, 5) - }, - ) - - test.Expect( - t, - "unexpected error", - err, - ErrNotFound, - ) - }) - - t.Run("it does not invoke the function with its internal byte slice", func(t *testing.T) { - t.Parallel() - - tctx := test.WithContext(t) - deps := setup(t, newStore) - - appendRecords(tctx, t, deps.Journal, 1) - - if err := deps.Journal.Range( - tctx, - 0, - func(ctx context.Context, pos Position, rec []byte) (bool, error) { - rec[0] = 'X' - - return true, nil - }, - ); err != nil { - t.Fatal(err) - } - - got, err := deps.Journal.Get(tctx, 0) - if err != nil { - t.Fatal(err) - } - - test.Expect( - t, - "unexpected record", - got, - []byte(""), - ) - }) - }) - - t.Run("func Append()", func(t *testing.T) { - t.Parallel() - - t.Run("it does not return an error if there is no record at the given position", func(t *testing.T) { - t.Parallel() - - tctx := test.WithContext(t) - deps := setup(t, newStore) - - if err := deps.Journal.Append(tctx, 0, []byte("")); err != nil { - t.Fatal(err) - } - }) - - t.Run("it returns ErrConflict there is already a record at the given position", func(t *testing.T) { - t.Parallel() - - tctx := test.WithContext(t) - deps := setup(t, newStore) - - if err := deps.Journal.Append(tctx, 0, []byte("")); err != nil { - t.Fatal(err) - } - - want := []byte("") - if err := deps.Journal.Append(tctx, 1, want); err != nil { - t.Fatal(err) - } - - err := deps.Journal.Append(tctx, 1, []byte("")) - - test.Expect( - t, - "unexpected error", - err, - ErrConflict, - ) - - got, err := deps.Journal.Get(tctx, 1) - if err != nil { - t.Fatal(err) - } - - test.Expect( - t, - "unexpected record", - got, - want, - ) - }) - - t.Run("it does not keep a reference to the record slice", func(t *testing.T) { - t.Parallel() - - tctx := test.WithContext(t) - deps := setup(t, newStore) - - rec := []byte("") - - if err := deps.Journal.Append(tctx, 0, rec); err != nil { - t.Fatal(err) - } - - rec[0] = 'X' - - got, err := deps.Journal.Get(tctx, 0) - if err != nil { - t.Fatal(err) - } - - test.Expect( - t, - "unexpected record", - got, - []byte(""), - ) - }) - }) - - t.Run("func Truncate()", func(t *testing.T) { - t.Parallel() - - t.Run("it truncates the journal", func(t *testing.T) { - t.Parallel() - - tctx := test.WithContext(t) - deps := setup(t, newStore) - - appendRecords(tctx, t, deps.Journal, 3) - - if err := deps.Journal.Truncate(tctx, 1); err != nil { - t.Fatal(err) - } - - begin, _, err := deps.Journal.Bounds(tctx) - if err != nil { - t.Fatal(err) - } - - test.Expect( - t, - "unexpected begin position", - begin, - 1, - ) - }) - - t.Run("it truncates the journal when it has already been truncated", func(t *testing.T) { - t.Parallel() - - tctx := test.WithContext(t) - deps := setup(t, newStore) - - appendRecords(tctx, t, deps.Journal, 3) - - if err := deps.Journal.Truncate(tctx, 1); err != nil { - t.Fatal(err) - } - - if err := deps.Journal.Truncate(tctx, 2); err != nil { - t.Fatal(err) - } - - begin, _, err := deps.Journal.Bounds(tctx) - if err != nil { - t.Fatal(err) - } - - test.Expect( - t, - "unexpected begin position", - begin, - 2, - ) - }) - }) - }) -} - -var journalCounter atomic.Uint64 - -// appendRecords appends records to j. -func appendRecords( - ctx context.Context, - t test.FailerT, - j Journal, - n int, -) [][]byte { - var records [][]byte - - for pos := Position(0); pos < Position(n); pos++ { - rec := []byte( - fmt.Sprintf("", pos), - ) - - records = append(records, rec) - - if err := j.Append(ctx, pos, rec); err != nil { - t.Fatal(err) - } - } - - return records -} diff --git a/persistence/kv/doc.go b/persistence/kv/doc.go deleted file mode 100644 index 8a06f999..00000000 --- a/persistence/kv/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package kv provides an abstraction of a minimal key/value store. -package kv diff --git a/persistence/kv/keyspace.go b/persistence/kv/keyspace.go deleted file mode 100644 index 704cb995..00000000 --- a/persistence/kv/keyspace.go +++ /dev/null @@ -1,32 +0,0 @@ -package kv - -import "context" - -// A RangeFunc is a function used to range over the key/value pairs in a -// [Keyspace]. -// -// If err is non-nil, ranging stops and err is propagated up the stack. -// Otherwise, if ok is false, ranging stops without any error being propagated. -type RangeFunc func(ctx context.Context, k, v []byte) (ok bool, err error) - -// A Keyspace is an isolated collection of key/value pairs. -type Keyspace interface { - // Get returns the value associated with k. - // - // If the key does not exist v is empty. - Get(ctx context.Context, k []byte) (v []byte, err error) - - // Has returns true if k is present in the keyspace. - Has(ctx context.Context, k []byte) (ok bool, err error) - - // Set associates a value with k. - // - // If v is empty, the key is deleted. - Set(ctx context.Context, k, v []byte) error - - // Range invokes fn for each key in the keyspace in an undefined order. - Range(ctx context.Context, fn RangeFunc) error - - // Close closes the keyspace. - Close() error -} diff --git a/persistence/kv/store.go b/persistence/kv/store.go deleted file mode 100644 index 2b6dc8b5..00000000 --- a/persistence/kv/store.go +++ /dev/null @@ -1,11 +0,0 @@ -package kv - -import ( - "context" -) - -// Store is a collection of keyspaces. -type Store interface { - // Open returns the keyspace with the given name. - Open(ctx context.Context, name string) (Keyspace, error) -} diff --git a/persistence/kv/test.go b/persistence/kv/test.go deleted file mode 100644 index a9a2ce1b..00000000 --- a/persistence/kv/test.go +++ /dev/null @@ -1,558 +0,0 @@ -package kv - -import ( - "bytes" - "context" - "errors" - "fmt" - "sync/atomic" - "testing" - "time" - - "github.com/google/go-cmp/cmp" -) - -// RunTests runs tests that confirm a journal implementation behaves correctly. -func RunTests( - t *testing.T, - newStore func(t *testing.T) Store, -) { - t.Run("type Store", func(t *testing.T) { - t.Parallel() - - t.Run("func Open()", func(t *testing.T) { - t.Parallel() - - t.Run("allows keyspaces to be opened multiple times", func(t *testing.T) { - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - defer cancel() - - store := newStore(t) - - ks1, err := store.Open(ctx, "") - if err != nil { - t.Fatal(err) - } - defer ks1.Close() - - ks2, err := store.Open(ctx, "") - if err != nil { - t.Fatal(err) - } - defer ks2.Close() - - expect := []byte("") - if err := ks1.Set(ctx, []byte(""), expect); err != nil { - t.Fatal(err) - } - - actual, err := ks2.Get(ctx, []byte("")) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(expect, actual) { - t.Fatalf( - "unexpected record, want %q, got %q", - string(expect), - string(actual), - ) - } - }) - }) - }) - - t.Run("type Keyspace", func(t *testing.T) { - t.Parallel() - - t.Run("func Get()", func(t *testing.T) { - t.Parallel() - - t.Run("it returns an empty value if the key doesn't exist", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - v, err := ks.Get(ctx, []byte("")) - if err != nil { - t.Fatal(err) - } - if len(v) != 0 { - t.Fatal("expected zero-length value") - } - }) - - t.Run("it returns an empty value if the key has been deleted", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - k := []byte("") - - if err := ks.Set(ctx, k, []byte("")); err != nil { - t.Fatal(err) - } - - if err := ks.Set(ctx, k, nil); err != nil { - t.Fatal(err) - } - - v, err := ks.Get(ctx, k) - if err != nil { - t.Fatal(err) - } - if len(v) != 0 { - t.Fatal("expected zero-length value") - } - }) - - t.Run("it returns the value if the key exists", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - for i := 0; i < 5; i++ { - k := []byte(fmt.Sprintf("", i)) - v := []byte(fmt.Sprintf("", i)) - - if err := ks.Set(ctx, k, v); err != nil { - t.Fatal(err) - } - } - - for i := 0; i < 5; i++ { - k := []byte(fmt.Sprintf("", i)) - expect := []byte(fmt.Sprintf("", i)) - - actual, err := ks.Get(ctx, k) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(expect, actual) { - t.Fatalf( - "unexpected value, want %q, got %q", - string(expect), - string(actual), - ) - } - } - }) - - t.Run("it does not return its internal byte slice", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - k := []byte("") - - if err := ks.Set(ctx, k, []byte("")); err != nil { - t.Fatal(err) - } - - v, err := ks.Get(ctx, k) - if err != nil { - t.Fatal(err) - } - - v[0] = 'X' - - actual, err := ks.Get(ctx, k) - if err != nil { - t.Fatal(err) - } - - if expect := []byte(""); !bytes.Equal(expect, actual) { - t.Fatalf( - "unexpected value, want %q, got %q", - string(expect), - string(actual), - ) - } - }) - }) - - t.Run("func Set()", func(t *testing.T) { - t.Parallel() - - t.Run("it does not keep a reference to the key slice", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - k := []byte("") - v := []byte("") - - if err := ks.Set(ctx, k, v); err != nil { - t.Fatal(err) - } - - k[0] = 'X' - - ok, err := ks.Has(ctx, k) - if err != nil { - t.Fatal(err) - } - - if ok { - t.Fatalf("unexpected key: %q", string(k)) - } - - actual, err := ks.Get(ctx, []byte("")) - if err != nil { - t.Fatal(err) - } - - if expect := []byte(""); !bytes.Equal(expect, actual) { - t.Fatalf( - "unexpected value, want %q, got %q", - string(expect), - string(actual), - ) - } - }) - - t.Run("it does not keep a reference to the value slice", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - k := []byte("") - v := []byte("") - - if err := ks.Set(ctx, k, v); err != nil { - t.Fatal(err) - } - - v[0] = 'X' - - actual, err := ks.Get(ctx, k) - if err != nil { - t.Fatal(err) - } - - if expect := []byte(""); !bytes.Equal(expect, actual) { - t.Fatalf( - "unexpected value, want %q, got %q", - string(expect), - string(actual), - ) - } - }) - }) - - t.Run("func Has()", func(t *testing.T) { - t.Parallel() - - t.Run("it returns false if the key doesn't exist", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - ok, err := ks.Has(ctx, []byte("")) - if err != nil { - t.Fatal(err) - } - if ok { - t.Fatal("expected ok to be false") - } - }) - - t.Run("it returns true if the key exists", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - k := []byte("") - - if err := ks.Set(ctx, k, []byte("")); err != nil { - t.Fatal(err) - } - - ok, err := ks.Has(ctx, k) - if err != nil { - t.Fatal(err) - } - if !ok { - t.Fatal("expected ok to be true") - } - }) - - t.Run("it returns false if the key has been deleted", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - k := []byte("") - - if err := ks.Set(ctx, k, []byte("")); err != nil { - t.Fatal(err) - } - - if err := ks.Set(ctx, k, nil); err != nil { - t.Fatal(err) - } - - ok, err := ks.Has(ctx, k) - if err != nil { - t.Fatal(err) - } - if ok { - t.Fatal("expected ok to be false") - } - }) - }) - - t.Run("func Range()", func(t *testing.T) { - t.Parallel() - - t.Run("calls the function for each key in the keyspace", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - expect := map[string]string{} - - for n := uint64(0); n < 100; n++ { - k := fmt.Sprintf("", n) - v := fmt.Sprintf("", n) - if err := ks.Set(ctx, []byte(k), []byte(v)); err != nil { - t.Fatal(err) - } - - expect[k] = v - } - - actual := map[string]string{} - - if err := ks.Range( - ctx, - func(ctx context.Context, k, v []byte) (bool, error) { - actual[string(k)] = string(v) - return true, nil - }, - ); err != nil { - t.Fatal(err) - } - - if diff := cmp.Diff(expect, actual); diff != "" { - t.Fatal(diff) - } - }) - - t.Run("it stops iterating if the function returns false", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - for n := uint64(0); n < 2; n++ { - k := fmt.Sprintf("", n) - v := fmt.Sprintf("", n) - if err := ks.Set(ctx, []byte(k), []byte(v)); err != nil { - t.Fatal(err) - } - } - - called := false - if err := ks.Range( - ctx, - func(ctx context.Context, k, v []byte) (bool, error) { - if called { - return false, errors.New("unexpected call") - } - - called = true - return false, nil - }, - ); err != nil { - t.Fatal(err) - } - }) - - t.Run("it does not invoke the function with its internal byte slices", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - if err := ks.Set( - ctx, - []byte(""), - []byte(""), - ); err != nil { - t.Fatal(err) - } - - if err := ks.Range( - ctx, - func(ctx context.Context, k, v []byte) (bool, error) { - k[0] = 'X' - v[0] = 'Y' - - return true, nil - }, - ); err != nil { - t.Fatal(err) - } - - k := []byte("Xkey>") - - ok, err := ks.Has(ctx, k) - if err != nil { - t.Fatal(err) - } - - if ok { - t.Fatalf("unexpected key: %q", string(k)) - } - - actual, err := ks.Get(ctx, []byte("")) - if err != nil { - t.Fatal(err) - } - - if expect := []byte(""); !bytes.Equal(expect, actual) { - t.Fatalf( - "unexpected value, want %q, got %q", - string(expect), - string(actual), - ) - } - }) - - t.Run("it allows calls to Get() during iteration", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - if err := ks.Set( - ctx, - []byte(""), - []byte(""), - ); err != nil { - t.Fatal(err) - } - - if err := ks.Range( - ctx, - func(ctx context.Context, k, expect []byte) (bool, error) { - actual, err := ks.Get(ctx, k) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(expect, actual) { - t.Fatalf( - "unexpected value, want %q, got %q", - string(expect), - string(actual), - ) - } - - return false, nil - }, - ); err != nil { - t.Fatal(err) - } - }) - - t.Run("it allows calls to Has() during iteration", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - if err := ks.Set( - ctx, - []byte(""), - []byte(""), - ); err != nil { - t.Fatal(err) - } - - if err := ks.Range( - ctx, - func(ctx context.Context, k, _ []byte) (bool, error) { - ok, err := ks.Has(ctx, k) - if err != nil { - t.Fatal(err) - } - if !ok { - t.Fatal("expected key to exist") - } - return false, nil - }, - ); err != nil { - t.Fatal(err) - } - }) - - t.Run("it allows calls to Set() during iteration", func(t *testing.T) { - t.Parallel() - - ctx, ks := setup(t, newStore) - - k := []byte("") - - if err := ks.Set( - ctx, - k, - []byte(""), - ); err != nil { - t.Fatal(err) - } - - expect := []byte("") - - if err := ks.Range( - ctx, - func(ctx context.Context, k, _ []byte) (bool, error) { - if err := ks.Set(ctx, k, expect); err != nil { - t.Fatal(err) - } - return false, nil - }, - ); err != nil { - t.Fatal(err) - } - - actual, err := ks.Get(ctx, k) - if err != nil { - t.Fatal(err) - } - - if !bytes.Equal(expect, actual) { - t.Fatalf( - "unexpected value, want %q, got %q", - string(expect), - string(actual), - ) - } - }) - }) - }) -} - -var keyspaceID atomic.Uint64 - -func setup( - t *testing.T, - newStore func(t *testing.T) Store, -) (context.Context, Keyspace) { - ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) - t.Cleanup(cancel) - - store := newStore(t) - - name := fmt.Sprintf("", keyspaceID.Add(1)) - ks, err := store.Open(ctx, name) - if err != nil { - t.Fatal(err) - } - - t.Cleanup(func() { - if err := ks.Close(); err != nil { - t.Fatal(err) - } - }) - - return ctx, ks -}