Skip to content

Commit 712a531

Browse files
committed
Fix trailing whitespace
Signed-off-by: Zeynel Koca <[email protected]>
2 parents 8a447ef + 494a6b9 commit 712a531

File tree

34 files changed

+1965
-97
lines changed

34 files changed

+1965
-97
lines changed
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
version: '2'
22
services:
33
etcd:
4-
image: gcr.io/etcd-development/etcd:v3.4.20
4+
image: gcr.io/etcd-development/etcd:v3.5.21
55
ports:
66
- "12379:2379"
7-
command: etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2379
7+
command: etcd --listen-client-urls http://0.0.0.0:2379 --advertise-client-urls http://0.0.0.0:2379

.github/scripts/test-info.mjs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,7 @@ const components = {
839839
* @property {boolean?} requireAWSCredentials If true, requires AWS credentials and makes the test "cloud-only"
840840
* @property {boolean?} requireGCPCredentials If true, requires GCP credentials and makes the test "cloud-only"
841841
* @property {boolean?} requireCloudflareCredentials If true, requires Cloudflare credentials and makes the test "cloud-only"
842+
* @property {boolean?} requireRavenDBCredentials If true, requires RavenDB credentials
842843
* @property {boolean?} requireTerraform If true, requires Terraform
843844
* @property {boolean?} requireKind If true, requires KinD
844845
* @property {string?} conformanceSetup Setup script for conformance tests
@@ -860,6 +861,7 @@ const components = {
860861
* @property {boolean?} require-aws-credentials Requires AWS credentials
861862
* @property {boolean?} require-gcp-credentials Requires GCP credentials
862863
* @property {boolean?} require-cloudflare-credentials Requires Cloudflare credentials
864+
* @property {boolean?} require-ravendb-credentials Requires RavenDB credentials
863865
* @property {boolean?} require-terraform Requires Terraform
864866
* @property {boolean?} require-kind Requires KinD
865867
* @property {string?} setup-script Setup script
@@ -931,6 +933,9 @@ function GenerateMatrix(testKind, enableCloudTests) {
931933
'require-cloudflare-credentials': comp.requireCloudflareCredentials
932934
? 'true'
933935
: undefined,
936+
'require-ravendb-credentials': comp.requireRavenDBCredentials
937+
? 'true'
938+
: undefined,
934939
'require-terraform': comp.requireTerraform ? 'true' : undefined,
935940
'require-kind': comp.requireKind ? 'true' : undefined,
936941
'setup-script': comp[testKind + 'Setup'] || undefined,

.github/workflows/conformance.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,12 @@ jobs:
226226
aws-secret-access-key: ${{ secrets.AWS_SECRET_KEY }}
227227
aws-region: us-west-1
228228

229+
- name: Set RavenDB env vars
230+
if: matrix.require-ravendb-credentials == 'true'
231+
run: |
232+
echo "::add-mask::${{ secrets.RAVENDBLICENSE }}"
233+
echo "RAVENDBLICENSE=${{ secrets.RAVENDBLICENSE }}" >> $GITHUB_ENV
234+
229235
- name: Start MongoDB
230236
if: matrix.mongodb-version != ''
231237
uses: supercharge/[email protected]

common/component/postgresql/v1/postgresql.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"fmt"
2222
"reflect"
2323
"strconv"
24+
"strings"
2425
"time"
2526

2627
"github.com/jackc/pgx/v5"
@@ -182,6 +183,7 @@ func (p *PostgreSQL) Features() []state.Feature {
182183
state.FeatureETag,
183184
state.FeatureTransactional,
184185
state.FeatureTTL,
186+
state.FeatureKeysLike,
185187
}
186188
}
187189

@@ -531,3 +533,88 @@ func (p *PostgreSQL) GetComponentMetadata() (metadataInfo metadata.MetadataMap)
531533
metadata.GetMetadataInfoFromStructType(reflect.TypeOf(metadataStruct), &metadataInfo, metadata.StateStoreType)
532534
return
533535
}
536+
537+
func (p *PostgreSQL) KeysLike(ctx context.Context, req *state.KeysLikeRequest) (*state.KeysLikeResponse, error) {
538+
if len(req.Pattern) == 0 {
539+
return nil, state.ErrKeysLikeEmptyPattern
540+
}
541+
542+
// Match with backslash-escaping for % and _
543+
where := []string{
544+
`key LIKE $1 ESCAPE '\'`,
545+
`(expiredate IS NULL OR expiredate > CURRENT_TIMESTAMP)`,
546+
}
547+
args := []any{req.Pattern}
548+
549+
// Pagination: resume strictly AFTER the last returned row_id
550+
if req.ContinuationToken != nil && *req.ContinuationToken != "" {
551+
rid, err := strconv.ParseInt(*req.ContinuationToken, 10, 64)
552+
if err != nil {
553+
return nil, fmt.Errorf("invalid continue token: %w", err)
554+
}
555+
where = append(where, fmt.Sprintf("row_id > $%d", len(args)+1))
556+
args = append(args, rid)
557+
}
558+
559+
// Optional LIMIT: fetch one extra row to detect "has next"
560+
limitClause := ""
561+
var pageSize uint32
562+
if req.PageSize != nil && *req.PageSize > 0 {
563+
pageSize = *req.PageSize
564+
limitClause = fmt.Sprintf(" LIMIT $%d", len(args)+1)
565+
args = append(args, pageSize+1)
566+
}
567+
568+
query := fmt.Sprintf(`
569+
SELECT key, row_id
570+
FROM %s
571+
WHERE %s
572+
ORDER BY row_id ASC%s`,
573+
p.metadata.TableName,
574+
strings.Join(where, " AND "),
575+
limitClause,
576+
)
577+
578+
rows, err := p.db.Query(ctx, query, args...)
579+
if err != nil {
580+
return nil, err
581+
}
582+
defer rows.Close()
583+
584+
type rec struct {
585+
key string
586+
rowID uint64
587+
}
588+
list := make([]rec, 0, 256)
589+
590+
for rows.Next() {
591+
var k string
592+
var rid uint64
593+
if err := rows.Scan(&k, &rid); err != nil {
594+
return nil, err
595+
}
596+
list = append(list, rec{key: k, rowID: rid})
597+
}
598+
if err := rows.Err(); err != nil {
599+
return nil, err
600+
}
601+
602+
resp := &state.KeysLikeResponse{
603+
Keys: make([]string, 0, len(list)),
604+
}
605+
606+
// If we fetched more than a page, set the token to the last returned row's row_id
607+
//nolint:gosec
608+
if pageSize > 0 && uint32(len(list)) > pageSize {
609+
lastReturned := list[pageSize-1].rowID
610+
tok := strconv.FormatUint(lastReturned, 10)
611+
resp.ContinuationToken = &tok
612+
list = list[:pageSize]
613+
}
614+
615+
for _, r := range list {
616+
resp.Keys = append(resp.Keys, r.key)
617+
}
618+
619+
return resp, nil
620+
}

common/component/postgresql/v1/postgresql_query.go

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,7 @@ func NewPostgreSQLQueryStateStore(logger logger.Logger, opts Options) state.Stor
4949

5050
// Features returns the features available in this component.
5151
func (p *PostgreSQLQuery) Features() []state.Feature {
52-
return []state.Feature{
53-
state.FeatureETag,
54-
state.FeatureTransactional,
55-
state.FeatureQueryAPI,
56-
state.FeatureTTL,
57-
}
52+
return append(p.PostgreSQL.Features(), state.FeatureQueryAPI)
5853
}
5954

6055
// Query executes a query against store.

state/aws/dynamodb/dynamodb_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1027,7 +1027,6 @@ func TestParseTTLWithDefault(t *testing.T) {
10271027
ttl, err := s.parseTTL(req)
10281028
require.NoError(t, err)
10291029
require.NotNil(t, ttl)
1030-
10311030
// Should use explicit value (300), not default (600)
10321031
expectedTime := time.Now().Unix() + 300
10331032
assert.InDelta(t, expectedTime, *ttl, 2) // Allow 2 second tolerance

state/cockroachdb/cockroachdb.go

Lines changed: 71 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ WHERE
7070
}
7171

7272
func ensureTables(ctx context.Context, db pginterfaces.PGXPoolConn, opts postgresql.MigrateOptions) error {
73+
// Create state table if missing, with row_id ready for pagination
7374
exists, err := tableExists(ctx, db, opts.StateTableName)
7475
if err != nil {
7576
return err
@@ -78,42 +79,88 @@ func ensureTables(ctx context.Context, db pginterfaces.PGXPoolConn, opts postgre
7879
if !exists {
7980
opts.Logger.Info("Creating CockroachDB state table")
8081
_, err = db.Exec(ctx, fmt.Sprintf(`CREATE TABLE %s (
81-
key text NOT NULL PRIMARY KEY,
82-
value jsonb NOT NULL,
83-
isbinary boolean NOT NULL,
84-
etag INT,
85-
insertdate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
86-
updatedate TIMESTAMP WITH TIME ZONE NULL,
87-
expiredate TIMESTAMP WITH TIME ZONE NULL,
88-
INDEX expiredate_idx (expiredate)
82+
key text NOT NULL PRIMARY KEY,
83+
value jsonb NOT NULL,
84+
isbinary boolean NOT NULL,
85+
etag INT,
86+
insertdate TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
87+
updatedate TIMESTAMP WITH TIME ZONE NULL,
88+
expiredate TIMESTAMP WITH TIME ZONE NULL,
89+
row_id INT8 NOT NULL DEFAULT unique_rowid(),
90+
UNIQUE (row_id)
8991
);`, opts.StateTableName))
9092
if err != nil {
9193
return err
9294
}
93-
}
95+
// Indexes created after table create for idempotency
96+
if _, err = db.Exec(ctx, fmt.Sprintf(
97+
`CREATE INDEX IF NOT EXISTS %s_expiredate_idx ON %s (expiredate);`,
98+
opts.StateTableName, opts.StateTableName)); err != nil {
99+
return err
100+
}
101+
} else {
102+
// Existing table: make sure columns + indexes exist
103+
// 1) expiredate (idempotent)
104+
if _, err = db.Exec(ctx, fmt.Sprintf(
105+
`ALTER TABLE %s ADD COLUMN IF NOT EXISTS expiredate TIMESTAMPTZ NULL;`,
106+
opts.StateTableName)); err != nil {
107+
return err
108+
}
109+
if _, err = db.Exec(ctx, fmt.Sprintf(
110+
`CREATE INDEX IF NOT EXISTS %s_expiredate_idx ON %s (expiredate);`,
111+
opts.StateTableName, opts.StateTableName)); err != nil {
112+
return err
113+
}
94114

95-
// If table was created before v1.11.
96-
_, err = db.Exec(ctx, fmt.Sprintf(
97-
`ALTER TABLE %s ADD COLUMN IF NOT EXISTS expiredate TIMESTAMP WITH TIME ZONE NULL;`, opts.StateTableName))
98-
if err != nil {
99-
return err
100-
}
101-
_, err = db.Exec(ctx, fmt.Sprintf(
102-
`CREATE INDEX IF NOT EXISTS expiredate_idx ON %s (expiredate);`, opts.StateTableName))
103-
if err != nil {
104-
return err
115+
// 2) row_id for keyset pagination
116+
opts.Logger.Infof("Ensuring row_id exists on '%s'", opts.StateTableName)
117+
118+
// Add column if missing (nullable initially)
119+
if _, err = db.Exec(ctx, fmt.Sprintf(
120+
`ALTER TABLE %s ADD COLUMN IF NOT EXISTS row_id INT8;`,
121+
opts.StateTableName)); err != nil {
122+
return err
123+
}
124+
125+
// Ensure it has a default generator
126+
if _, err = db.Exec(ctx, fmt.Sprintf(
127+
`ALTER TABLE %s ALTER COLUMN row_id SET DEFAULT unique_rowid();`,
128+
opts.StateTableName)); err != nil {
129+
return err
130+
}
131+
132+
// Backfill NULLs (older rows) with generated values
133+
if _, err = db.Exec(ctx, fmt.Sprintf(
134+
`UPDATE %s SET row_id = unique_rowid() WHERE row_id IS NULL;`,
135+
opts.StateTableName)); err != nil {
136+
return err
137+
}
138+
139+
// Enforce NOT NULL
140+
if _, err = db.Exec(ctx, fmt.Sprintf(
141+
`ALTER TABLE %s ALTER COLUMN row_id SET NOT NULL;`,
142+
opts.StateTableName)); err != nil {
143+
return err
144+
}
145+
146+
// Unique index to guarantee ordering without changing PK
147+
if _, err = db.Exec(ctx, fmt.Sprintf(
148+
`CREATE UNIQUE INDEX IF NOT EXISTS %s_row_id_uidx ON %s (row_id);`,
149+
opts.StateTableName, opts.StateTableName)); err != nil {
150+
return err
151+
}
105152
}
106153

154+
// Metadata table
107155
exists, err = tableExists(ctx, db, opts.MetadataTableName)
108156
if err != nil {
109157
return err
110158
}
111-
112159
if !exists {
113160
opts.Logger.Info("Creating CockroachDB metadata table")
114161
_, err = db.Exec(ctx, fmt.Sprintf(`CREATE TABLE %s (
115-
key text NOT NULL PRIMARY KEY,
116-
value text NOT NULL
162+
key text NOT NULL PRIMARY KEY,
163+
value text NOT NULL
117164
);`, opts.MetadataTableName))
118165
if err != nil {
119166
return err
@@ -124,7 +171,7 @@ func ensureTables(ctx context.Context, db pginterfaces.PGXPoolConn, opts postgre
124171
}
125172

126173
func tableExists(ctx context.Context, db pginterfaces.PGXPoolConn, tableName string) (bool, error) {
127-
exists := false
128-
err := db.QueryRow(ctx, "SELECT EXISTS (SELECT * FROM pg_tables where tablename = $1)", tableName).Scan(&exists)
174+
var exists bool
175+
err := db.QueryRow(ctx, "SELECT EXISTS (SELECT * FROM pg_tables WHERE tablename = $1)", tableName).Scan(&exists)
129176
return exists, err
130177
}

state/errors.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ const (
2828
ETagMismatch ETagErrorKind = "mismatch"
2929
)
3030

31+
var ErrKeysLikeEmptyPattern = errors.New("keys like pattern cannot be empty")
32+
3133
// ETagError is a custom error type for etag exceptions.
3234
type ETagError struct {
3335
err error

0 commit comments

Comments
 (0)