Skip to content

Remove timestampsInSnapshots from FirestoreSettings #3897

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Oct 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/strange-rabbits-wait.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'firebase': major
'@firebase/firestore': major
'@firebase/firestore-types': major
---

Removed the `timestampsInSnapshots` option from `FirestoreSettings`. Now, Firestore always returns `Timestamp` values for all timestamp values.
34 changes: 0 additions & 34 deletions packages/firebase/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7825,40 +7825,6 @@ declare namespace firebase.firestore {
/** Whether to use SSL when connecting. */
ssl?: boolean;

/**
* Specifies whether to use `Timestamp` objects for timestamp fields in
* `DocumentSnapshot`s. This is enabled by default and should not be
* disabled.
*
* Previously, Firestore returned timestamp fields as `Date` but `Date`
* only supports millisecond precision, which leads to truncation and
* causes unexpected behavior when using a timestamp from a snapshot as a
* part of a subsequent query.
*
* Now, Firestore returns `Timestamp` values for all timestamp values stored
* in Cloud Firestore instead of system `Date` objects, avoiding this kind
* of problem. Consequently, you must update your code to handle `Timestamp`
* objects instead of `Date` objects.
*
* If you want to **temporarily** opt into the old behavior of returning
* `Date` objects, you may **temporarily** set `timestampsInSnapshots` to
* false. Opting into this behavior will no longer be possible in the next
* major release of Firestore, after which code that expects Date objects
* **will break**.
*
* @example **Using Date objects in Firestore.**
* // With deprecated setting `timestampsInSnapshot: true`:
* const date : Date = snapshot.get('created_at');
* // With new default behavior:
* const timestamp : Timestamp = snapshot.get('created_at');
* const date : Date = timestamp.toDate();
*
* @deprecated This setting will be removed in a future release. You should
* update your code to expect `Timestamp` objects and stop using the
* `timestampsInSnapshots` setting.
*/
timestampsInSnapshots?: boolean;

/**
* An approximate cache size threshold for the on-disk data. If the cache grows beyond this
* size, Firestore will start removing data that hasn't been recently used. The size is not a
Expand Down
1 change: 0 additions & 1 deletion packages/firestore-types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ export const CACHE_SIZE_UNLIMITED: number;
export interface Settings {
host?: string;
ssl?: boolean;
timestampsInSnapshots?: boolean;
cacheSizeBytes?: number;
experimentalForceLongPolling?: boolean;
ignoreUndefinedProperties?: boolean;
Expand Down
2 changes: 0 additions & 2 deletions packages/firestore/exp/src/api/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,7 +236,6 @@ export class DocumentSnapshot<T = DocumentData> extends LiteDocumentSnapshot<
} else {
const userDataWriter = new UserDataWriter(
this._firestoreImpl._databaseId,
/* timestampsInSnapshots= */ true,
options?.serverTimestamps || DEFAULT_SERVER_TIMESTAMP_BEHAVIOR,
key =>
new DocumentReference(
Expand Down Expand Up @@ -276,7 +275,6 @@ export class DocumentSnapshot<T = DocumentData> extends LiteDocumentSnapshot<
if (value !== null) {
const userDataWriter = new UserDataWriter(
this._firestoreImpl._databaseId,
/* timestampsInSnapshots= */ true,
options.serverTimestamps || DEFAULT_SERVER_TIMESTAMP_BEHAVIOR,
key =>
new DocumentReference(this._firestore, this._converter, key.path),
Expand Down
2 changes: 0 additions & 2 deletions packages/firestore/lite/src/api/snapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,6 @@ export class DocumentSnapshot<T = DocumentData> {
} else {
const userDataWriter = new UserDataWriter(
this._firestore._databaseId,
/* timestampsInSnapshots= */ true,
/* serverTimestampBehavior=*/ 'none',
key =>
new DocumentReference(
Expand Down Expand Up @@ -204,7 +203,6 @@ export class DocumentSnapshot<T = DocumentData> {
if (value !== null) {
const userDataWriter = new UserDataWriter(
this._firestore._databaseId,
/* timestampsInSnapshots= */ true,
/* serverTimestampBehavior=*/ 'none',
key =>
new DocumentReference(this._firestore, this._converter, key.path),
Expand Down
35 changes: 0 additions & 35 deletions packages/firestore/src/api/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ import { Provider } from '@firebase/component';
// settings() defaults:
const DEFAULT_HOST = 'firestore.googleapis.com';
const DEFAULT_SSL = true;
const DEFAULT_TIMESTAMPS_IN_SNAPSHOTS = true;
const DEFAULT_FORCE_LONG_POLLING = false;
const DEFAULT_IGNORE_UNDEFINED_PROPERTIES = false;

Expand Down Expand Up @@ -185,8 +184,6 @@ class FirestoreSettings {
/** Whether to use SSL when connecting. */
readonly ssl: boolean;

readonly timestampsInSnapshots: boolean;

readonly cacheSizeBytes: number;

readonly experimentalForceLongPolling: boolean;
Expand Down Expand Up @@ -218,7 +215,6 @@ class FirestoreSettings {
'host',
'ssl',
'credentials',
'timestampsInSnapshots',
'cacheSizeBytes',
'experimentalForceLongPolling',
'ignoreUndefinedProperties'
Expand All @@ -232,35 +228,13 @@ class FirestoreSettings {
);
this.credentials = settings.credentials;

validateNamedOptionalType(
'settings',
'boolean',
'timestampsInSnapshots',
settings.timestampsInSnapshots
);

validateNamedOptionalType(
'settings',
'boolean',
'ignoreUndefinedProperties',
settings.ignoreUndefinedProperties
);

// Nobody should set timestampsInSnapshots anymore, but the error depends on
// whether they set it to true or false...
if (settings.timestampsInSnapshots === true) {
logError(
"The setting 'timestampsInSnapshots: true' is no longer required " +
'and should be removed.'
);
} else if (settings.timestampsInSnapshots === false) {
logError(
"Support for 'timestampsInSnapshots: false' will be removed soon. " +
'You must update your code to handle Timestamp objects.'
);
}
this.timestampsInSnapshots =
settings.timestampsInSnapshots ?? DEFAULT_TIMESTAMPS_IN_SNAPSHOTS;
this.ignoreUndefinedProperties =
settings.ignoreUndefinedProperties ?? DEFAULT_IGNORE_UNDEFINED_PROPERTIES;

Expand Down Expand Up @@ -300,7 +274,6 @@ class FirestoreSettings {
return (
this.host === other.host &&
this.ssl === other.ssl &&
this.timestampsInSnapshots === other.timestampsInSnapshots &&
this.credentials === other.credentials &&
this.cacheSizeBytes === other.cacheSizeBytes &&
this.experimentalForceLongPolling ===
Expand Down Expand Up @@ -712,12 +685,6 @@ export class Firestore implements PublicFirestore, FirebaseService {
setLogLevel(level);
}

// Note: this is not a property because the minifier can't work correctly with
// the way TypeScript compiler outputs properties.
_areTimestampsInSnapshotsEnabled(): boolean {
return this._settings.timestampsInSnapshots;
}

// Visible for testing.
_getSettings(): PublicSettings {
return this._settings;
Expand Down Expand Up @@ -1400,7 +1367,6 @@ export class DocumentSnapshot<T = DocumentData>
} else {
const userDataWriter = new UserDataWriter(
this._firestore._databaseId,
this._firestore._areTimestampsInSnapshotsEnabled(),
options.serverTimestamps || 'none',
key =>
new DocumentReference(key, this._firestore, /* converter= */ null),
Expand All @@ -1426,7 +1392,6 @@ export class DocumentSnapshot<T = DocumentData>
if (value !== null) {
const userDataWriter = new UserDataWriter(
this._firestore._databaseId,
this._firestore._areTimestampsInSnapshotsEnabled(),
options.serverTimestamps || 'none',
key => new DocumentReference(key, this._firestore, this._converter),
bytes => new Blob(bytes)
Expand Down
13 changes: 2 additions & 11 deletions packages/firestore/src/api/user_data_writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ export type ServerTimestampBehavior = 'estimate' | 'previous' | 'none';
export class UserDataWriter {
constructor(
private readonly databaseId: DatabaseId,
private readonly timestampsInSnapshots: boolean,
private readonly serverTimestampBehavior: ServerTimestampBehavior,
private readonly referenceFactory: (
key: DocumentKey
Expand Down Expand Up @@ -128,17 +127,9 @@ export class UserDataWriter {
}
}

private convertTimestamp(value: ProtoTimestamp): Timestamp | Date {
private convertTimestamp(value: ProtoTimestamp): Timestamp {
const normalizedValue = normalizeTimestamp(value);
const timestamp = new Timestamp(
normalizedValue.seconds,
normalizedValue.nanos
);
if (this.timestampsInSnapshots) {
return timestamp;
} else {
return timestamp.toDate();
}
return new Timestamp(normalizedValue.seconds, normalizedValue.nanos);
}

private convertReference(name: string): _DocumentKeyReference<DocumentData> {
Expand Down
71 changes: 0 additions & 71 deletions packages/firestore/test/integration/api/fields.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ import {
import { DEFAULT_SETTINGS } from '../util/settings';

const FieldPath = firebaseExport.FieldPath;
const FieldValue = firebaseExport.FieldValue;
const Timestamp = firebaseExport.Timestamp;
const usesFunctionalApi = firebaseExport.usesFunctionalApi;

// Allow custom types for testing.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Expand Down Expand Up @@ -354,44 +352,6 @@ apiDescribe('Timestamp Fields in snapshots', (persistence: boolean) => {
return { timestamp: ts, nested: { timestamp2: ts } };
};

// timestampInSnapshots is not support in the modular API.
// eslint-disable-next-line no-restricted-properties
(usesFunctionalApi() ? it.skip : it)(
'are returned as native dates if timestampsInSnapshots set to false',
() => {
const settings = { ...DEFAULT_SETTINGS };
settings['timestampsInSnapshots'] = false;

const timestamp = new Timestamp(100, 123456789);
const testDocs = { a: testDataWithTimestamps(timestamp) };
return withTestCollectionSettings(
persistence,
settings,
testDocs,
coll => {
return coll
.doc('a')
.get()
.then(docSnap => {
expect(docSnap.get('timestamp'))
.to.be.a('date')
.that.deep.equals(timestamp.toDate());
expect(docSnap.data()!['timestamp'])
.to.be.a('date')
.that.deep.equals(timestamp.toDate());

expect(docSnap.get('nested.timestamp2'))
.to.be.a('date')
.that.deep.equals(timestamp.toDate());
expect(docSnap.data()!['nested']['timestamp2'])
.to.be.a('date')
.that.deep.equals(timestamp.toDate());
});
}
);
}
);

it('are returned as Timestamps', () => {
const timestamp = new Timestamp(100, 123456000);
// Timestamps are currently truncated to microseconds after being written to
Expand Down Expand Up @@ -423,37 +383,6 @@ apiDescribe('Timestamp Fields in snapshots', (persistence: boolean) => {
});
});
});

// timestampInSnapshots is not support in the modular API.
// eslint-disable-next-line no-restricted-properties
(usesFunctionalApi() ? it.skip : it)(
'timestampsInSnapshots affects server timestamps',
() => {
const settings = { ...DEFAULT_SETTINGS };
settings['timestampsInSnapshots'] = false;
const testDocs = {
a: { timestamp: FieldValue.serverTimestamp() }
};

return withTestCollectionSettings(
persistence,
settings,
testDocs,
coll => {
return coll
.doc('a')
.get()
.then(docSnap => {
expect(
docSnap.get('timestamp', {
serverTimestamps: 'estimate'
})
).to.be.an.instanceof(Date);
});
}
);
}
);
});

apiDescribe('`undefined` properties', (persistence: boolean) => {
Expand Down
12 changes: 6 additions & 6 deletions packages/firestore/test/unit/remote/serializer.helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,8 @@ export function serializerTest(

it('converts TimestampValue from proto', () => {
const examples = [
new Date(Date.UTC(2016, 0, 2, 10, 20, 50, 850)),
new Date(Date.UTC(2016, 5, 17, 10, 50, 15, 0))
new Timestamp(1451730050, 850000000),
new Timestamp(1466160615, 0)
];

const expectedJson = [
Expand Down Expand Up @@ -339,25 +339,25 @@ export function serializerTest(
userDataWriter.convertValue({
timestampValue: '2017-03-07T07:42:58.916123456Z'
})
).to.deep.equal(new Timestamp(1488872578, 916123456).toDate());
).to.deep.equal(new Timestamp(1488872578, 916123456));

expect(
userDataWriter.convertValue({
timestampValue: '2017-03-07T07:42:58.916123Z'
})
).to.deep.equal(new Timestamp(1488872578, 916123000).toDate());
).to.deep.equal(new Timestamp(1488872578, 916123000));

expect(
userDataWriter.convertValue({
timestampValue: '2017-03-07T07:42:58.916Z'
})
).to.deep.equal(new Timestamp(1488872578, 916000000).toDate());
).to.deep.equal(new Timestamp(1488872578, 916000000));

expect(
userDataWriter.convertValue({
timestampValue: '2017-03-07T07:42:58Z'
})
).to.deep.equal(new Timestamp(1488872578, 0).toDate());
).to.deep.equal(new Timestamp(1488872578, 0));
});

it('converts TimestampValue to string (useProto3Json=true)', () => {
Expand Down
1 change: 0 additions & 1 deletion packages/firestore/test/util/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ export type TestSnapshotVersion = number;
export function testUserDataWriter(): UserDataWriter {
return new UserDataWriter(
TEST_DATABASE_ID,
/* timestampsInSnapshots= */ false,
'none',
key => new DocumentReference(key, FIRESTORE, /* converter= */ null),
bytes => new Blob(bytes)
Expand Down