Skip to content

Commit 2991a0e

Browse files
committed
Use iso8601 timestamps as an opt-in
1 parent 86807d0 commit 2991a0e

File tree

3 files changed

+60
-2
lines changed

3 files changed

+60
-2
lines changed

packages/jpgwire/src/pgwire_types.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,14 @@ export class PgType {
182182
const decoded = this.decode(unescaped, elemTypeOid);
183183
stack[0].push(decoded);
184184
}
185-
result = ch == 0x7d /*}*/ && stack.shift();
185+
186+
if (ch == 0x7d /*}*/) {
187+
const entry = stack.shift();
188+
result = entry;
189+
} else {
190+
result = false;
191+
}
192+
186193
elStart = i + 1; // TODO dry
187194
}
188195
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import { JSONBig } from '@powersync/service-jsonbig';
2+
import { CompatibilityContext } from '../quirks.js';
3+
import { SqliteValue, EvaluatedRow, SqliteInputValue, DatabaseInputValue } from '../types.js';
4+
import { filterJsonData } from '../utils.js';
5+
6+
/**
7+
* A value that decays into a {@link SqliteValue} in a context-specific way.
8+
*
9+
* This is used to conditionally render some values in different formats depending on compatibility options. For
10+
* instance, old versions of the sync service used to [encode timestamp values incorrectly](https://github.com/powersync-ja/powersync-service/issues/286).
11+
* To fix this without breaking backwards-compatibility, we now represent timestamp values as a {@link CustomSqliteType}
12+
* subtype where `toSqliteValue` returns the old or the new format depending on options.
13+
*
14+
* Instances of {@link CustomSqliteType} are always temporary structures that aren't persisted. They are created by the
15+
* replicator implementations, the sync rule implementation will invoke {@link toSqliteValue} to ensure that an
16+
* {@link EvaluatedRow} only consists of proper SQLite values.
17+
*/
18+
export abstract class CustomSqliteType {
19+
/**
20+
* Renders this custom value into a {@link SqliteValue}.
21+
*
22+
* @param context The current compatibility options.
23+
*/
24+
abstract toSqliteValue(context: CompatibilityContext): SqliteValue;
25+
26+
static wrapArray(elements: DatabaseInputValue[]): SqliteInputValue {
27+
const hasCustomValue = elements.some((v) => v instanceof CustomSqliteType);
28+
if (hasCustomValue) {
29+
// We need access to the compatibility context before encoding contents as JSON.
30+
return new CustomArray(elements);
31+
} else {
32+
// We can encode the array statically.
33+
return JSONBig.stringify(elements.map((element) => filterJsonData(element)));
34+
}
35+
}
36+
}
37+
38+
class CustomArray extends CustomSqliteType {
39+
constructor(private readonly elements: DatabaseInputValue[]) {
40+
super();
41+
}
42+
43+
toSqliteValue(context: CompatibilityContext): SqliteValue {
44+
return JSONBig.stringify(
45+
this.elements.map((element) => {
46+
const mapped = element instanceof CustomSqliteType ? element.toSqliteValue(context) : element;
47+
return filterJsonData(mapped);
48+
})
49+
);
50+
}
51+
}

packages/sync-rules/test/src/compatibility.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { describe, expect, test } from 'vitest';
2-
import { CustomSqliteValue, SqlSyncRules, DateTimeValue, toSyncRulesValue } from '../../src/index.js';
2+
import { SqlSyncRules, DateTimeValue, toSyncRulesValue } from '../../src/index.js';
33

44
import { ASSETS, PARSE_OPTIONS } from './util.js';
55

0 commit comments

Comments
 (0)