Skip to content

Commit 52f41b6

Browse files
[#23867] YSQL: Support postgres timestamp in YSQL clone AS OF syntax
Summary: This diff adds support for PG's timestamptz type as the clone time parameter when cloning a database as of a point in time: ``` CREATE DATABASE db2 TEMPLATE db1 AS OF '2024-09-10 21:52:46.782360'; ``` The Unix microseconds timestamp format is still supported. Jira: DB-12773 Test Plan: `./yb_build.sh release --cxx-test integration-tests_minicluster-snapshot-test --gtest_filter *CloneYsqlSyntax/*` Reviewers: mhaddad, kfranz Reviewed By: kfranz Subscribers: jason, yql, ybase Differential Revision: https://phorge.dev.yugabyte.com/D42966
1 parent 391903b commit 52f41b6

File tree

5 files changed

+79
-21
lines changed

5 files changed

+79
-21
lines changed

docs/content/preview/manage/backup-restore/instant-db-cloning.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -71,10 +71,12 @@ CREATE DATABASE clone_db TEMPLATE original_db;
7171

7272
In this example, `clone_db` is created as a clone of `original_db`, and contains the latest schema and data of `original_db` as of current time.
7373

74-
To create a clone of the original database at a specific point in time (within the history retention period specified when creating the snapshot schedule), you can specify the [Unix timestamp](https://www.unixtimestamp.com/) in microseconds using the `AS OF` option as follows:
74+
To create a clone of the original database at a specific point in time (within the history retention period specified when creating the snapshot schedule), you can specify a timestamp using the `AS OF` option. The timestamp may be either a [Unix timestamp](https://www.unixtimestamp.com/) in microseconds (as below), or a [PostgreSQL TIMESTAMP](https://www.postgresql.org/docs/current/datatype-datetime.html) in single quotes.
7575

7676
```sql
7777
CREATE DATABASE clone_db TEMPLATE original_db AS OF 1723146703674480;
78+
# Alternatively:
79+
CREATE DATABASE clone_db TEMPLATE original_db AS OF '2024-08-08 19:51:43.674480';
7880
```
7981

8082
### Clone a YCQL keyspace

src/postgres/src/backend/commands/dbcommands.c

+27-1
Original file line numberDiff line numberDiff line change
@@ -982,7 +982,33 @@ createdb(ParseState *pstate, const CreatedbStmt *stmt)
982982
else
983983
dbcolocated = YBColocateDatabaseByDefault();
984984
if (dclonetime && dclonetime->arg)
985-
dbclonetime = defGetInt64(dclonetime);
985+
{
986+
/*
987+
* This is a Float and not an Integer because Integer is too small to
988+
* contain a Unix epoch timestamp in microseconds (which is the format we
989+
* require).
990+
*/
991+
if (IsA(dclonetime->arg, Float))
992+
dbclonetime = defGetInt64(dclonetime);
993+
else if (IsA(dclonetime->arg, String))
994+
{
995+
const char *clone_time_str = defGetString(dclonetime);
996+
TimestampTz clone_time = DirectFunctionCall3(timestamptz_in,
997+
CStringGetDatum(clone_time_str),
998+
ObjectIdGetDatum(InvalidOid),
999+
Int32GetDatum(-1));
1000+
dbclonetime =
1001+
yb_timestamptz_to_micros_time_t(DatumGetTimestampTz(clone_time));
1002+
}
1003+
else
1004+
{
1005+
ereport(ERROR,
1006+
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
1007+
errmsg("invalid clone time type: %s (must be a Unix microseconds "
1008+
"timestamp or a timestamptz-formatted string).",
1009+
nodeToString(dclonetime->arg))));
1010+
}
1011+
}
9861012
if (dcollversion)
9871013
dbcollversion = defGetString(dcollversion);
9881014

src/postgres/src/backend/utils/adt/timestamp.c

+14
Original file line numberDiff line numberDiff line change
@@ -5940,3 +5940,17 @@ generate_series_timestamptz(PG_FUNCTION_ARGS)
59405940
SRF_RETURN_DONE(funcctx);
59415941
}
59425942
}
5943+
5944+
/*
5945+
* Same as timestamptz_to_time_t, but with microsecond precision.
5946+
*/
5947+
pg_time_t
5948+
yb_timestamptz_to_micros_time_t(TimestampTz t)
5949+
{
5950+
pg_time_t result;
5951+
5952+
result = (pg_time_t) (t +
5953+
((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * USECS_PER_DAY));
5954+
5955+
return result;
5956+
}

src/postgres/src/include/utils/timestamp.h

+2
Original file line numberDiff line numberDiff line change
@@ -114,4 +114,6 @@ extern int date2isoyearday(int year, int mon, int mday);
114114

115115
extern bool TimestampTimestampTzRequiresRewrite(void);
116116

117+
extern pg_time_t yb_timestamptz_to_micros_time_t(TimestampTz t);
118+
117119
#endif /* TIMESTAMP_H */

src/yb/integration-tests/minicluster-snapshot-test.cc

+33-19
Original file line numberDiff line numberDiff line change
@@ -718,42 +718,56 @@ TEST_P(PgCloneTestWithColocatedDBParam, YB_DISABLE_TEST_IN_SANITIZERS(CloneYsqlS
718718
const std::vector<std::tuple<int32_t, int32_t>> kRows = {{1, 10}, {2, 20}};
719719
ASSERT_OK(source_conn_->ExecuteFormat(
720720
"INSERT INTO t1 VALUES ($0, $1)", std::get<0>(kRows[0]), std::get<1>(kRows[0])));
721-
722721
// Write a second row after recording the hybrid time.
723-
auto ht = ASSERT_RESULT(GetCurrentTime()).ToInt64();
722+
auto timestamp = ASSERT_RESULT(GetCurrentTime());
724723
ASSERT_OK(source_conn_->ExecuteFormat(
725724
"INSERT INTO t1 VALUES ($0, $1)", std::get<0>(kRows[1]), std::get<1>(kRows[1])));
726725

727-
// Perform the first clone operation to ht.
728-
ASSERT_OK(source_conn_->ExecuteFormat(
729-
"CREATE DATABASE $0 TEMPLATE $1 AS OF $2", kTargetNamespaceName1, kSourceNamespaceName, ht));
730-
731-
// Perform the second clone operation to clone the source DB using the current timestamp (AS OF is
732-
// not specified)
726+
// Clone to ht and verify clone only has the first row.
733727
ASSERT_OK(source_conn_->ExecuteFormat(
734-
"CREATE DATABASE $0 TEMPLATE $1", kTargetNamespaceName2, kSourceNamespaceName));
735-
736-
// Verify source rows are unchanged.
737-
auto rows = ASSERT_RESULT((source_conn_->FetchRows<int32_t, int32_t>("SELECT * FROM t1")));
738-
ASSERT_VECTORS_EQ(rows, kRows);
739-
740-
// Verify first clone only has the first row.
728+
"CREATE DATABASE $0 TEMPLATE $1 AS OF $2", kTargetNamespaceName1, kSourceNamespaceName,
729+
timestamp.ToInt64()));
741730
// Use a scope here and below so we can drop the cloned databases after.
742731
{
743732
auto target_conn1 = ASSERT_RESULT(ConnectToDB(kTargetNamespaceName1));
744733
auto row = ASSERT_RESULT((target_conn1.FetchRow<int32_t, int32_t>("SELECT * FROM t1")));
745734
ASSERT_EQ(row, kRows[0]);
746735
}
736+
ASSERT_OK(source_conn_->ExecuteFormat("DROP DATABASE $0", kTargetNamespaceName1));
747737

748-
// Verify second clone has both rows.
738+
// Clone using the current timestamp (AS OF is not specified) and check that it has both rows.
739+
ASSERT_OK(source_conn_->ExecuteFormat(
740+
"CREATE DATABASE $0 TEMPLATE $1", kTargetNamespaceName2, kSourceNamespaceName));
749741
{
750742
auto target_conn2 = ASSERT_RESULT(ConnectToDB(kTargetNamespaceName2));
751-
rows = ASSERT_RESULT((target_conn2.FetchRows<int32_t, int32_t>("SELECT * FROM t1")));
743+
auto rows = ASSERT_RESULT((target_conn2.FetchRows<int32_t, int32_t>("SELECT * FROM t1")));
752744
ASSERT_VECTORS_EQ(rows, kRows);
753745
}
754-
755-
ASSERT_OK(source_conn_->ExecuteFormat("DROP DATABASE $0", kTargetNamespaceName1));
756746
ASSERT_OK(source_conn_->ExecuteFormat("DROP DATABASE $0", kTargetNamespaceName2));
747+
748+
// Clone using a timestamp in PG's timestamptz format. It should only have the first row.
749+
const auto kTargetNamespaceName3 = "testdb_clone3";
750+
ASSERT_OK(source_conn_->ExecuteFormat(
751+
"CREATE DATABASE $0 TEMPLATE $1 AS OF '$2'", kTargetNamespaceName3, kSourceNamespaceName,
752+
timestamp.ToHumanReadableTime()));
753+
{
754+
auto target_conn3 = ASSERT_RESULT(ConnectToDB(kTargetNamespaceName3));
755+
auto row = ASSERT_RESULT((target_conn3.FetchRow<int32_t, int32_t>("SELECT * FROM t1")));
756+
ASSERT_EQ(row, kRows[0]);
757+
}
758+
ASSERT_OK(source_conn_->ExecuteFormat("DROP DATABASE $0", kTargetNamespaceName3));
759+
760+
// Cloning using a timestamp in Unix seconds should not work.
761+
const auto kTargetNamespaceName4 = "testdb_clone4";
762+
auto status = source_conn_->ExecuteFormat(
763+
"CREATE DATABASE $0 TEMPLATE $1 AS OF $2", kTargetNamespaceName4, kSourceNamespaceName,
764+
timestamp.ToInt64() / 1000000);
765+
ASSERT_NOK(status);
766+
ASSERT_STR_CONTAINS(status.message().ToBuffer(), "invalid clone time type");
767+
768+
// Verify source rows are unchanged.
769+
auto rows = ASSERT_RESULT((source_conn_->FetchRows<int32_t, int32_t>("SELECT * FROM t1")));
770+
ASSERT_VECTORS_EQ(rows, kRows);
757771
}
758772

759773
class TsDataSizeMetricsTest : public PgCloneTest {

0 commit comments

Comments
 (0)