Skip to content

Commit 9d8f7fb

Browse files
committed
Use HydrationState for bucket names.
1 parent 60290ae commit 9d8f7fb

16 files changed

+861
-287
lines changed

packages/sync-rules/src/BucketSource.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { BucketParameterQuerier, ParameterLookup, PendingQueriers } from './BucketParameterQuerier.js';
22
import { ColumnDefinition } from './ExpressionType.js';
3+
import { HydrationState } from './HydrationState.js';
34
import { SourceTableInterface } from './SourceTableInterface.js';
45
import { GetQuerierOptions } from './SqlSyncRules.js';
56
import { TablePattern } from './TablePattern.js';
@@ -13,7 +14,11 @@ import {
1314
} from './types.js';
1415

1516
export interface CreateSourceParams {
16-
bucketIdTransformer: BucketIdTransformer;
17+
hydrationState?: HydrationState;
18+
/**
19+
* @deprecated Use hydrationState instead.
20+
*/
21+
bucketIdTransformer?: BucketIdTransformer;
1722
}
1823

1924
/**
@@ -123,6 +128,13 @@ export interface BucketParameterQuerierSourceDefinition {
123128
*/
124129
readonly bucketParameters: string[];
125130

131+
/**
132+
* The data source linked to this querier. This determines the bucket names that the querier generates.
133+
*
134+
* Note that queriers do not persist data themselves; they only resolve which buckets to load based on request parameters.
135+
*/
136+
readonly querierDataSource: BucketDataSourceDefinition;
137+
126138
createParameterQuerierSource(params: CreateSourceParams): BucketParameterQuerierSource;
127139
}
128140

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { BucketDataSource, BucketDataSourceDefinition, BucketParameterLookupSourceDefinition } from './BucketSource.js';
2+
import { BucketIdTransformer, CompatibilityContext, CompatibilityOption, CreateSourceParams } from './index.js';
3+
4+
export interface BucketSourceState {
5+
/** The prefix is the bucket name before the parameters. */
6+
bucketPrefix: string;
7+
}
8+
9+
export interface BucketParameterLookupSourceState {
10+
/** The lookup name + queryid is used to reference the parameter lookup record. */
11+
lookupName: string;
12+
queryId: string;
13+
}
14+
15+
/**
16+
* Hydration state information for a source.
17+
*
18+
* This is what keeps track of bucket name and parameter lookup mappings for hydration. This can be used
19+
* both to re-use mappings across hydrations of different sync rule versions, or to generate new mappings.
20+
*/
21+
export interface HydrationState<
22+
T extends BucketSourceState = BucketSourceState,
23+
U extends BucketParameterLookupSourceState = BucketParameterLookupSourceState
24+
> {
25+
/**
26+
* Given a bucket data source definition, get the bucket prefix to use for it.
27+
*/
28+
getBucketSourceState(source: BucketDataSourceDefinition): T;
29+
30+
/**
31+
* Given a bucket parameter lookup definition, get the persistence name to use.
32+
*/
33+
getParameterLookupState(source: BucketParameterLookupSourceDefinition): U;
34+
}
35+
36+
/**
37+
* This represents hydration state that performs no transformations.
38+
*
39+
* This is the legacy default behavior with no bucket versioning.
40+
*/
41+
export const DEFAULT_HYDRATION_STATE: HydrationState = {
42+
getBucketSourceState(source: BucketDataSourceDefinition) {
43+
return {
44+
bucketPrefix: source.defaultBucketPrefix
45+
};
46+
},
47+
getParameterLookupState(source) {
48+
return {
49+
lookupName: source.defaultLookupName,
50+
queryId: source.defaultQueryId
51+
};
52+
}
53+
};
54+
55+
export function versionedHydrationState(version: number) {
56+
return new VersionedHydrationState((bucketId: string) => {
57+
return `${version}#${bucketId}`;
58+
});
59+
}
60+
61+
export class VersionedHydrationState implements HydrationState {
62+
constructor(private transformer: BucketIdTransformer) {}
63+
64+
getBucketSourceState(source: BucketDataSourceDefinition): BucketSourceState {
65+
return {
66+
bucketPrefix: this.transformer(source.defaultBucketPrefix)
67+
};
68+
}
69+
70+
getParameterLookupState(source: BucketParameterLookupSourceDefinition): BucketParameterLookupSourceState {
71+
// No transformations applied here
72+
return {
73+
lookupName: source.defaultLookupName,
74+
queryId: source.defaultQueryId
75+
};
76+
}
77+
}
78+
79+
export class BucketIdTransformerHydrationState implements HydrationState {
80+
constructor(private transformer: BucketIdTransformer) {}
81+
82+
getBucketSourceState(source: BucketDataSourceDefinition): BucketSourceState {
83+
return {
84+
bucketPrefix: this.transformer(source.defaultBucketPrefix)
85+
};
86+
}
87+
88+
getParameterLookupState(source: BucketParameterLookupSourceDefinition): BucketParameterLookupSourceState {
89+
// No transformations applied here
90+
return {
91+
lookupName: source.defaultLookupName,
92+
queryId: source.defaultQueryId
93+
};
94+
}
95+
}
96+
97+
export function resolveHydrationState(params: CreateSourceParams): HydrationState {
98+
if (params.hydrationState) {
99+
return params.hydrationState;
100+
} else if (params.bucketIdTransformer) {
101+
return new BucketIdTransformerHydrationState(params.bucketIdTransformer);
102+
} else {
103+
return DEFAULT_HYDRATION_STATE;
104+
}
105+
}

packages/sync-rules/src/SqlBucketDescriptor.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
CreateSourceParams
77
} from './BucketSource.js';
88
import { ColumnDefinition } from './ExpressionType.js';
9+
import { resolveHydrationState } from './HydrationState.js';
910
import { IdSequence } from './IdSequence.js';
1011
import { SourceTableInterface } from './SourceTableInterface.js';
1112
import { SqlDataQuery } from './SqlDataQuery.js';
@@ -83,7 +84,13 @@ export class SqlBucketDescriptor implements BucketSource {
8384
}
8485

8586
addParameterQuery(sql: string, options: QueryParseOptions): QueryParseResult {
86-
const parameterQuery = SqlParameterQuery.fromSql(this.name, sql, options, this.parameterIdSequence.nextId());
87+
const parameterQuery = SqlParameterQuery.fromSql(
88+
this.name,
89+
sql,
90+
options,
91+
this.parameterIdSequence.nextId(),
92+
this.dataSource
93+
);
8794
if (this.bucketParametersInternal == null) {
8895
this.bucketParametersInternal = parameterQuery.bucketParameters;
8996
} else {
@@ -151,6 +158,8 @@ export class BucketDefinitionDataSource implements BucketDataSourceDefinition {
151158
}
152159

153160
createDataSource(params: CreateSourceParams): BucketDataSource {
161+
const hydrationState = resolveHydrationState(params);
162+
const bucketPrefix = hydrationState.getBucketSourceState(this).bucketPrefix;
154163
return {
155164
evaluateRow: (options) => {
156165
let results: EvaluationResult[] = [];
@@ -159,7 +168,7 @@ export class BucketDefinitionDataSource implements BucketDataSourceDefinition {
159168
continue;
160169
}
161170

162-
results.push(...query.evaluateRow(options.sourceTable, options.record, params.bucketIdTransformer));
171+
results.push(...query.evaluateRow(options.sourceTable, options.record, bucketPrefix));
163172
}
164173
return results;
165174
}

packages/sync-rules/src/SqlDataQuery.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -192,19 +192,13 @@ export class SqlDataQuery extends BaseSqlDataQuery {
192192
this.filter = options.filter;
193193
}
194194

195-
evaluateRow(
196-
table: SourceTableInterface,
197-
row: SqliteRow,
198-
bucketIdTransformer: BucketIdTransformer
199-
): EvaluationResult[] {
195+
evaluateRow(table: SourceTableInterface, row: SqliteRow, bucketPrefix: string): EvaluationResult[] {
200196
return this.evaluateRowWithOptions({
201197
table,
202198
row,
203199
bucketIds: (tables) => {
204200
const bucketParameters = this.filter.filterRow(tables);
205-
return bucketParameters.map((params) =>
206-
getBucketId(this.descriptorName, this.bucketParameters, params, bucketIdTransformer)
207-
);
201+
return bucketParameters.map((params) => getBucketId(bucketPrefix, this.bucketParameters, params));
208202
}
209203
});
210204
}

packages/sync-rules/src/SqlParameterQuery.ts

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ import {
1919
CreateSourceParams
2020
} from './BucketSource.js';
2121
import { SqlRuleError } from './errors.js';
22-
import { GetQuerierOptions } from './index.js';
22+
import { BucketDataSourceDefinition, GetQuerierOptions } from './index.js';
2323
import { SourceTableInterface } from './SourceTableInterface.js';
2424
import { AvailableTable, SqlTools } from './sql_filters.js';
2525
import { checkUnsupportedFeatures, isClauseError } from './sql_support.js';
@@ -44,6 +44,7 @@ import {
4444
} from './types.js';
4545
import { filterJsonRow, getBucketId, isJsonValue, isSelectStatement, normalizeParameterValue } from './utils.js';
4646
import { DetectRequestParameters } from './validators.js';
47+
import { HydrationState, resolveHydrationState } from './HydrationState.js';
4748

4849
export interface SqlParameterQueryOptions {
4950
sourceTable: TablePattern;
@@ -59,6 +60,7 @@ export interface SqlParameterQueryOptions {
5960
bucketParameters: string[];
6061
queryId: string;
6162
tools: SqlTools;
63+
querierDataSource: BucketDataSourceDefinition;
6264
errors?: SqlRuleError[];
6365
}
6466

@@ -75,7 +77,8 @@ export class SqlParameterQuery
7577
descriptorName: string,
7678
sql: string,
7779
options: QueryParseOptions,
78-
queryId: string
80+
queryId: string,
81+
querierDataSource: BucketDataSourceDefinition
7982
): SqlParameterQuery | StaticSqlParameterQuery | TableValuedFunctionSqlParameterQuery {
8083
const parsed = parse(sql, { locationTracking: true });
8184
const schema = options?.schema;
@@ -91,7 +94,7 @@ export class SqlParameterQuery
9194

9295
if (q.from == null) {
9396
// E.g. SELECT token_parameters.user_id as user_id WHERE token_parameters.is_admin
94-
return StaticSqlParameterQuery.fromSql(descriptorName, sql, q, options, queryId);
97+
return StaticSqlParameterQuery.fromSql(descriptorName, sql, q, options, queryId, querierDataSource);
9598
}
9699

97100
let errors: SqlRuleError[] = [];
@@ -102,7 +105,15 @@ export class SqlParameterQuery
102105
throw new SqlRuleError('Must SELECT from a single table', sql, q.from?.[0]._location);
103106
} else if (q.from[0].type == 'call') {
104107
const from = q.from[0];
105-
return TableValuedFunctionSqlParameterQuery.fromSql(descriptorName, sql, from, q, options, queryId);
108+
return TableValuedFunctionSqlParameterQuery.fromSql(
109+
descriptorName,
110+
sql,
111+
from,
112+
q,
113+
options,
114+
queryId,
115+
querierDataSource
116+
);
106117
} else if (q.from[0].type == 'statement') {
107118
throw new SqlRuleError('Subqueries are not supported yet', sql, q.from?.[0]._location);
108119
}
@@ -203,6 +214,7 @@ export class SqlParameterQuery
203214
bucketParameters,
204215
queryId,
205216
tools,
217+
querierDataSource,
206218
errors
207219
});
208220

@@ -297,6 +309,8 @@ export class SqlParameterQuery
297309
readonly queryId: string;
298310
readonly tools: SqlTools;
299311

312+
readonly querierDataSource: BucketDataSourceDefinition;
313+
300314
readonly errors: SqlRuleError[];
301315

302316
constructor(options: SqlParameterQueryOptions) {
@@ -314,6 +328,7 @@ export class SqlParameterQuery
314328
this.queryId = options.queryId;
315329
this.tools = options.tools;
316330
this.errors = options.errors ?? [];
331+
this.querierDataSource = options.querierDataSource;
317332
}
318333

319334
public get defaultLookupName(): string {
@@ -333,15 +348,18 @@ export class SqlParameterQuery
333348
}
334349

335350
createParameterQuerierSource(params: CreateSourceParams): BucketParameterQuerierSource {
351+
const hydrationState = resolveHydrationState(params);
352+
const bucketPrefix = hydrationState.getBucketSourceState(this.querierDataSource).bucketPrefix;
336353
return {
337354
pushBucketParameterQueriers: (result: PendingQueriers, options: GetQuerierOptions) => {
338-
const q = this.getBucketParameterQuerier(options.globalParameters, ['default'], params.bucketIdTransformer);
355+
const q = this.getBucketParameterQuerier(options.globalParameters, ['default'], bucketPrefix);
339356
result.queriers.push(q);
340357
}
341358
};
342359
}
343360

344361
createParameterLookupSource(params: CreateSourceParams): BucketParameterLookupSource {
362+
// FIXME: Use HydrationState for lookups.
345363
return {
346364
evaluateParameterRow: (sourceTable: SourceTableInterface, row: SqliteRow): EvaluatedParametersResult[] => {
347365
if (this.tableSyncsParameters(sourceTable)) {
@@ -403,7 +421,7 @@ export class SqlParameterQuery
403421
resolveBucketDescriptions(
404422
bucketParameters: SqliteJsonRow[],
405423
parameters: RequestParameters,
406-
transformer: BucketIdTransformer
424+
bucketPrefix: string
407425
): BucketDescription[] {
408426
// Filters have already been applied and gotten us the set of bucketParameters - don't attempt to filter again.
409427
// We _do_ need to evaluate the output columns here, using a combination of precomputed bucketParameters,
@@ -428,7 +446,7 @@ export class SqlParameterQuery
428446
}
429447

430448
return {
431-
bucket: getBucketId(this.descriptorName, this.bucketParameters, result, transformer),
449+
bucket: getBucketId(bucketPrefix, this.bucketParameters, result),
432450
priority: this.priority
433451
};
434452
})
@@ -514,7 +532,7 @@ export class SqlParameterQuery
514532
getBucketParameterQuerier(
515533
requestParameters: RequestParameters,
516534
reasons: BucketInclusionReason[],
517-
transformer: BucketIdTransformer
535+
bucketPrefix: string
518536
): BucketParameterQuerier {
519537
const lookups = this.getLookups(requestParameters);
520538
if (lookups.length == 0) {
@@ -534,7 +552,7 @@ export class SqlParameterQuery
534552
parameterQueryLookups: lookups,
535553
queryDynamicBucketDescriptions: async (source: ParameterLookupSource) => {
536554
const bucketParameters = await source.getParameterSets(lookups);
537-
return this.resolveBucketDescriptions(bucketParameters, requestParameters, transformer).map((bucket) => ({
555+
return this.resolveBucketDescriptions(bucketParameters, requestParameters, bucketPrefix).map((bucket) => ({
538556
...bucket,
539557
definition: this.descriptorName,
540558
inclusion_reasons: reasons

0 commit comments

Comments
 (0)