Skip to content

Commit 76994ff

Browse files
committed
bring back in changes
1 parent b3928f3 commit 76994ff

3 files changed

Lines changed: 125 additions & 81 deletions

File tree

bttest/cmv.go

Lines changed: 22 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"os"
77
"regexp"
8-
"sort"
98
"strconv"
109
"strings"
1110
)
@@ -23,7 +22,7 @@ type CMVConfig struct {
2322
// KeyMapping defines how source key components map to CMV key components.
2423
// Each entry is the 0-based index into the SPLIT result of the source key.
2524
// The CMV row key is built by joining the mapped components with KeySeparator.
26-
// Example: [3,4,1,2,0,5] means CMV key = source[3]#source[4]#source[1]#...
25+
// Example: [3,4,1,2,0] means CMV key = source[3]#source[4]#source[1]#source[2]#source[0]
2726
KeyMapping []int `json:"key_mapping"`
2827
// IncludeFamilies lists the column families to carry from source to CMV.
2928
// An empty list means all families are included.
@@ -60,13 +59,13 @@ func LoadCMVConfigs(path string) ([]CMVConfig, error) {
6059
}
6160

6261
// ParseCMVConfigFromSQL attempts to extract a CMVConfig from a CMV SQL query.
63-
// This handles the common pattern used by Bigtable CMVs:
62+
// It parses the standard Bigtable CMV SQL pattern:
6463
//
65-
// SELECT CAST(SPLIT(_key, '#')[SAFE_OFFSET(n)] AS STRING) AS col, ...
64+
// SELECT SPLIT(_key, '#')[SAFE_OFFSET(n)] AS col, ..., _key AS source_key, family AS family
6665
// FROM `table_name`
67-
// ORDER BY col1, col2, ...
66+
// ORDER BY col1, col2, ..., source_key
6867
//
69-
// Returns a CMVConfig with the key mapping derived from the ORDER BY + SELECT.
68+
// _key (optionally aliased) in ORDER BY sets AppendSourceKey = true.
7069
func ParseCMVConfigFromSQL(viewID, query string) (*CMVConfig, error) {
7170
cfg := &CMVConfig{
7271
ViewID: viewID,
@@ -81,9 +80,6 @@ func ParseCMVConfigFromSQL(viewID, query string) (*CMVConfig, error) {
8180
cfg.SourceTable = fromMatch[1]
8281

8382
// Extract column aliases and their SAFE_OFFSET indices from SELECT.
84-
// Handles patterns like:
85-
// CAST(SPLIT(_key, '#')[SAFE_OFFSET(n)] AS STRING) AS alias
86-
// SPLIT(_key, '#')[SAFE_OFFSET(n)] AS alias
8783
offsetRe := regexp.MustCompile(`SPLIT\(_key,\s*'([^']+)'\)\[SAFE_OFFSET\((\d+)\)\].*?\bAS\s+\w+\)\s+AS\s+(\w+)|SPLIT\(_key,\s*'([^']+)'\)\[SAFE_OFFSET\((\d+)\)\]\s+AS\s+(\w+)`)
8884
matches := offsetRe.FindAllStringSubmatch(query, -1)
8985
if len(matches) == 0 {
@@ -92,20 +88,27 @@ func ParseCMVConfigFromSQL(viewID, query string) (*CMVConfig, error) {
9288

9389
colMap := make(map[string]int) // alias → SAFE_OFFSET index
9490
for _, m := range matches {
95-
// First alternative matched: CAST(...) AS alias
9691
if m[1] != "" {
92+
// CAST(...) AS alias form
9793
cfg.KeySeparator = m[1]
9894
idx, _ := strconv.Atoi(m[2])
9995
colMap[m[3]] = idx
10096
} else {
101-
// Second alternative matched: direct SPLIT AS alias
97+
// Direct SPLIT AS alias form
10298
cfg.KeySeparator = m[4]
10399
idx, _ := strconv.Atoi(m[5])
104100
colMap[m[6]] = idx
105101
}
106102
}
107103

108-
// Extract ORDER BY columns (excluding _key if present).
104+
// Detect "_key AS <alias>" in SELECT so we can recognise the alias in ORDER BY.
105+
var sourceKeyAlias string
106+
keyAliasRe := regexp.MustCompile(`(?i)\b_key\s+AS\s+(\w+)`)
107+
if m := keyAliasRe.FindStringSubmatch(query); m != nil {
108+
sourceKeyAlias = m[1]
109+
}
110+
111+
// Extract ORDER BY columns; _key or its alias sets AppendSourceKey.
109112
orderRe := regexp.MustCompile(`(?i)ORDER\s+BY\s+(.+)$`)
110113
orderMatch := orderRe.FindStringSubmatch(strings.TrimSpace(query))
111114
if orderMatch == nil {
@@ -114,7 +117,7 @@ func ParseCMVConfigFromSQL(viewID, query string) (*CMVConfig, error) {
114117
orderCols := strings.Split(orderMatch[1], ",")
115118
for _, col := range orderCols {
116119
col = strings.TrimSpace(col)
117-
if col == "_key" {
120+
if col == "_key" || (sourceKeyAlias != "" && col == sourceKeyAlias) {
118121
cfg.AppendSourceKey = true
119122
continue
120123
}
@@ -129,7 +132,7 @@ func ParseCMVConfigFromSQL(viewID, query string) (*CMVConfig, error) {
129132
return nil, fmt.Errorf("no key mapping columns found in ORDER BY")
130133
}
131134

132-
// Extract included column families from SELECT (pattern: family_name AS family_name).
135+
// Extract included column families from SELECT (pattern: family AS family).
133136
famRe := regexp.MustCompile(`(?:,\s*)(\w+)\s+AS\s+(\w+)`)
134137
famMatches := famRe.FindAllStringSubmatch(query, -1)
135138
for _, m := range famMatches {
@@ -144,12 +147,9 @@ func ParseCMVConfigFromSQL(viewID, query string) (*CMVConfig, error) {
144147
return cfg, nil
145148
}
146149

147-
// cmvRegistry maps source table IDs to their CMV definitions.
148-
// The emulator uses fully-qualified table names like
149-
// "projects/{project}/instances/{instance}/tables/{table}", but the CMV config
150-
// only specifies plain table IDs. The registry matches by suffix.
150+
// cmvRegistry maps plain source table IDs to CMV definitions.
151+
// Lookups match by table ID suffix against fully-qualified table names.
151152
type cmvRegistry struct {
152-
// configs stores raw CMV configs indexed by source table ID (plain name).
153153
configs map[string][]CMVConfig
154154
}
155155

@@ -159,13 +159,10 @@ func newCMVRegistry() *cmvRegistry {
159159
}
160160
}
161161

162-
// register adds a CMV definition keyed by its source table ID.
163162
func (r *cmvRegistry) register(cfg CMVConfig) {
164163
r.configs[cfg.SourceTable] = append(r.configs[cfg.SourceTable], cfg)
165164
}
166165

167-
// cmvsForTable returns all CMV instances for a fully-qualified table name.
168-
// Extracts the parent prefix and plain table ID from the FQ name.
169166
func (r *cmvRegistry) cmvsForTable(fqTable string) []*cmvInstance {
170167
parent, tableID := splitFQTable(fqTable)
171168
cfgs, ok := r.configs[tableID]
@@ -193,7 +190,6 @@ type cmvInstance struct {
193190
parent string // e.g., projects/p/instances/i
194191
}
195192

196-
// transformKey applies the CMV key mapping to produce a new row key.
197193
func (c *cmvInstance) transformKey(sourceKey string) string {
198194
parts := strings.Split(sourceKey, c.config.KeySeparator)
199195
var newParts []string
@@ -210,8 +206,7 @@ func (c *cmvInstance) transformKey(sourceKey string) string {
210206
return strings.Join(newParts, c.config.KeySeparator)
211207
}
212208

213-
// shouldIncludeFamily returns true if the given family name should be carried
214-
// to the CMV. Returns true for all families if IncludeFamilies is empty.
209+
// shouldIncludeFamily returns true for all families when IncludeFamilies is empty.
215210
func (c *cmvInstance) shouldIncludeFamily(famName string) bool {
216211
if len(c.config.IncludeFamilies) == 0 {
217212
return true
@@ -224,8 +219,7 @@ func (c *cmvInstance) shouldIncludeFamily(famName string) bool {
224219
return false
225220
}
226221

227-
// buildCMVRow creates a new row for the CMV table from a source row.
228-
// It copies all included families and their cells.
222+
// buildCMVRow builds a re-keyed CMV row by copying all included families from the source row.
229223
func (c *cmvInstance) buildCMVRow(sourceRow *row) *row {
230224
newKey := c.transformKey(sourceRow.key)
231225
cmvRow := newRow(newKey)
@@ -250,18 +244,7 @@ func (c *cmvInstance) buildCMVRow(sourceRow *row) *row {
250244
return cmvRow
251245
}
252246

253-
// deriveCMVKey computes what the CMV row key would be for a given source key.
254-
// Used for delete propagation — we need to find the CMV row to delete.
247+
// deriveCMVKey returns the CMV row key for a given source key.
255248
func (c *cmvInstance) deriveCMVKey(sourceKey string) string {
256249
return c.transformKey(sourceKey)
257250
}
258-
259-
// sortedCMVConfigs returns configs sorted by ViewID for deterministic behavior.
260-
func sortedCMVConfigs(configs []CMVConfig) []CMVConfig {
261-
sorted := make([]CMVConfig, len(configs))
262-
copy(sorted, configs)
263-
sort.Slice(sorted, func(i, j int) bool {
264-
return sorted[i].ViewID < sorted[j].ViewID
265-
})
266-
return sorted
267-
}

bttest/cmv_test.go

Lines changed: 85 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,25 @@ func newTestServerWithCMV(t *testing.T, configs []CMVConfig) *server {
3232
}
3333

3434
func TestCMVTransformKey(t *testing.T) {
35+
// Mirrors the production CMV config: key_mapping [3,4,1,2,0] + append_source_key.
36+
// SELECT SPLIT(_key,'#')[SAFE_OFFSET(3)] AS provider_id, ..., _key AS source_key
37+
// ORDER BY provider_id, client_id, rev_ts, event_category, bitlink_id, source_key
38+
// CMV key = provider_id#client_id#rev_ts#event_category#bitlink_id#<full_source_key>
3539
inst := &cmvInstance{
3640
config: CMVConfig{
3741
SourceTable: "attributions_conversion_events",
3842
ViewID: "attributions_conversion_events_by_client",
3943
KeySeparator: "#",
40-
KeyMapping: []int{3, 4, 1, 2, 0, 5},
44+
KeyMapping: []int{3, 4, 1, 2, 0},
4145
AppendSourceKey: true,
4246
},
4347
}
4448

4549
sourceKey := "bitlink123#9999999999#order#shopify#myshop.myshopify.com#01HWXYZ"
4650
got := inst.transformKey(sourceKey)
47-
want := "shopify#myshop.myshopify.com#9999999999#order#bitlink123#01HWXYZ#bitlink123#9999999999#order#shopify#myshop.myshopify.com#01HWXYZ"
51+
// parts: [0]bitlink123 [1]9999999999 [2]order [3]shopify [4]myshop.myshopify.com [5]01HWXYZ
52+
// mapped: parts[3]#parts[4]#parts[1]#parts[2]#parts[0] + full source key
53+
want := "shopify#myshop.myshopify.com#9999999999#order#bitlink123#bitlink123#9999999999#order#shopify#myshop.myshopify.com#01HWXYZ"
4854
assert.Equal(t, want, got)
4955
}
5056

@@ -261,7 +267,7 @@ func TestLoadCMVConfigs(t *testing.T) {
261267
"source_table": "attributions_conversion_events",
262268
"view_id": "attributions_conversion_events_by_client",
263269
"key_separator": "#",
264-
"key_mapping": [3, 4, 1, 2, 0, 5],
270+
"key_mapping": [3, 4, 1, 2, 0],
265271
"include_families": ["cr", "d", "m"],
266272
"append_source_key": true
267273
}
@@ -276,34 +282,100 @@ func TestLoadCMVConfigs(t *testing.T) {
276282
assert.Equal(t, "attributions_conversion_events", configs[0].SourceTable)
277283
assert.Equal(t, "attributions_conversion_events_by_client", configs[0].ViewID)
278284
assert.Equal(t, "#", configs[0].KeySeparator)
279-
assert.Equal(t, []int{3, 4, 1, 2, 0, 5}, configs[0].KeyMapping)
285+
assert.Equal(t, []int{3, 4, 1, 2, 0}, configs[0].KeyMapping)
280286
assert.Equal(t, []string{"cr", "d", "m"}, configs[0].IncludeFamilies)
281287
assert.True(t, configs[0].AppendSourceKey)
282288
}
283289

284290
func TestParseCMVConfigFromSQL(t *testing.T) {
291+
// Mirrors the production Terraform CMV SQL exactly: no CAST, _key aliased as
292+
// source_key, attribution_source_id is embedded in source_key not a separate component.
285293
query := `SELECT
286-
CAST(SPLIT(_key, '#')[SAFE_OFFSET(3)] AS STRING) AS provider_id,
287-
CAST(SPLIT(_key, '#')[SAFE_OFFSET(4)] AS STRING) AS client_id,
288-
CAST(SPLIT(_key, '#')[SAFE_OFFSET(1)] AS STRING) AS rev_ts,
289-
CAST(SPLIT(_key, '#')[SAFE_OFFSET(2)] AS STRING) AS conversion_type,
290-
CAST(SPLIT(_key, '#')[SAFE_OFFSET(0)] AS STRING) AS bitlink_id,
291-
CAST(SPLIT(_key, '#')[SAFE_OFFSET(5)] AS STRING) AS attribution_source_id,
292-
_key,
294+
SPLIT(_key, '#')[SAFE_OFFSET(3)] AS provider_id,
295+
SPLIT(_key, '#')[SAFE_OFFSET(4)] AS client_id,
296+
SPLIT(_key, '#')[SAFE_OFFSET(1)] AS rev_ts,
297+
SPLIT(_key, '#')[SAFE_OFFSET(2)] AS event_category,
298+
SPLIT(_key, '#')[SAFE_OFFSET(0)] AS bitlink_id,
299+
_key AS source_key,
293300
cr AS cr,
294301
d AS d,
295302
m AS m
296303
FROM ` + "`attributions_conversion_events`" + `
297-
ORDER BY provider_id, client_id, rev_ts, conversion_type, bitlink_id, attribution_source_id, _key`
304+
ORDER BY provider_id, client_id, rev_ts, event_category, bitlink_id, source_key`
298305

299306
cfg, err := ParseCMVConfigFromSQL("attributions_conversion_events_by_client", query)
300307
require.NoError(t, err)
301308
assert.Equal(t, "attributions_conversion_events", cfg.SourceTable)
302309
assert.Equal(t, "attributions_conversion_events_by_client", cfg.ViewID)
303310
assert.Equal(t, "#", cfg.KeySeparator)
304-
assert.Equal(t, []int{3, 4, 1, 2, 0, 5}, cfg.KeyMapping)
311+
assert.Equal(t, []int{3, 4, 1, 2, 0}, cfg.KeyMapping)
305312
assert.True(t, cfg.AppendSourceKey)
306313
assert.Contains(t, cfg.IncludeFamilies, "cr")
307314
assert.Contains(t, cfg.IncludeFamilies, "d")
308315
assert.Contains(t, cfg.IncludeFamilies, "m")
309316
}
317+
318+
func TestCMVDropRowRangePrefix(t *testing.T) {
319+
ctx := context.Background()
320+
// Key mapping [1,0]: CMV key = source[1]#source[0] (swap two components).
321+
configs := []CMVConfig{{
322+
SourceTable: "src_table",
323+
ViewID: "src_cmv",
324+
KeySeparator: "#",
325+
KeyMapping: []int{1, 0},
326+
}}
327+
s := newTestServerWithCMV(t, configs)
328+
329+
parent := "projects/test/instances/test"
330+
fqSrc := parent + "/tables/src_table"
331+
fqCMV := parent + "/tables/src_cmv"
332+
333+
_, err := s.CreateTable(ctx, &btapb.CreateTableRequest{
334+
Parent: parent,
335+
TableId: "src_table",
336+
Table: &btapb.Table{
337+
ColumnFamilies: map[string]*btapb.ColumnFamily{
338+
"cf1": {},
339+
},
340+
},
341+
})
342+
require.NoError(t, err)
343+
344+
// Write three rows: two share the prefix "alpha#" and one does not.
345+
for _, key := range []string{"alpha#one", "alpha#two", "beta#three"} {
346+
_, err = s.MutateRow(ctx, &btpb.MutateRowRequest{
347+
TableName: fqSrc,
348+
RowKey: []byte(key),
349+
Mutations: []*btpb.Mutation{{
350+
Mutation: &btpb.Mutation_SetCell_{
351+
SetCell: &btpb.Mutation_SetCell{
352+
FamilyName: "cf1",
353+
ColumnQualifier: []byte("c"),
354+
TimestampMicros: 1000,
355+
Value: []byte("v"),
356+
},
357+
},
358+
}},
359+
})
360+
require.NoError(t, err)
361+
}
362+
363+
s.mu.Lock()
364+
cmvTbl := s.tables[fqCMV]
365+
s.mu.Unlock()
366+
require.NotNil(t, cmvTbl)
367+
assert.Equal(t, 3, cmvTbl.rows.Len())
368+
369+
// Drop source rows with prefix "alpha#".
370+
_, err = s.DropRowRange(ctx, &btapb.DropRowRangeRequest{
371+
Name: fqSrc,
372+
Target: &btapb.DropRowRangeRequest_RowKeyPrefix{RowKeyPrefix: []byte("alpha#")},
373+
})
374+
require.NoError(t, err)
375+
376+
// CMV should now have only 1 row: "three#beta" (from "beta#three").
377+
assert.Equal(t, 1, cmvTbl.rows.Len())
378+
// The remaining CMV row should be the transformed "beta#three" → "three#beta".
379+
cmvRow := cmvTbl.rows.Get(btreeKey("three#beta"))
380+
assert.NotNil(t, cmvRow, "CMV row for non-deleted source should still exist")
381+
}

0 commit comments

Comments
 (0)