Skip to content

Commit f518cd0

Browse files
erimatnorRobAtticus
authored andcommitted
Add support for pre-release version checks
Version checks fail on pre-release versions (e.g., `1.0.0-rc1`) since the version parsing didn't expect a pre-release tag at the end. This change adds support for pre-release tags, using lexicographical comparison in case of pre-release tags. Note that having a pre-release tag vs not having one makes the version strictly "smaller", given otherwise identical versions.
1 parent 3670a87 commit f518cd0

File tree

9 files changed

+347
-114
lines changed

9 files changed

+347
-114
lines changed

sql/version.sql

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
CREATE OR REPLACE FUNCTION _timescaledb_internal.get_git_commit() RETURNS TEXT
22
AS '@MODULE_PATHNAME@', 'get_git_commit' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
33

4-
CREATE OR REPLACE FUNCTION _timescaledb_internal.get_os_info() RETURNS TABLE(sysname TEXT, version TEXT, release TEXT)
4+
CREATE OR REPLACE FUNCTION _timescaledb_internal.get_os_info()
5+
RETURNS TABLE(sysname TEXT, version TEXT, release TEXT)
56
AS '@MODULE_PATHNAME@', 'ts_get_os_info' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
67

8+
CREATE OR REPLACE FUNCTION _timescaledb_internal.get_version()
9+
RETURNS TABLE(major INTEGER, minor INTEGER, patch INTEGER, modtag TEXT)
10+
AS '@MODULE_PATHNAME@', 'ts_version_get_info' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;
11+
712
CREATE OR REPLACE FUNCTION get_telemetry_report() RETURNS TEXT
813
AS '@MODULE_PATHNAME@', 'ts_get_telemetry_report' LANGUAGE C IMMUTABLE STRICT PARALLEL SAFE;

src/telemetry/telemetry.c

Lines changed: 33 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -43,40 +43,15 @@
4343
#define POSTGIS "postgis"
4444

4545
static const char *related_extensions[] = {PG_PROMETHEUS, POSTGIS};
46-
static const char *version_delimiter[3] = {".", ".", ""};
47-
48-
static bool
49-
parse_version_string(const char *version, long version_result[3])
50-
{
51-
char *parse_version = pstrdup(version);
52-
int i;
53-
54-
for (i = 0; i < 3; i++)
55-
{
56-
const char *subversion;
57-
char *endptr;
58-
59-
subversion = strtok(i == 0 ? parse_version : NULL, version_delimiter[i]);
60-
61-
if (subversion == NULL)
62-
return i > 0;
63-
64-
version_result[i] = strtol(subversion, &endptr, 10);
65-
66-
if (endptr != NULL && *endptr != '.' && *endptr != '\0')
67-
return false;
68-
}
69-
70-
return true;
71-
}
7246

7347
bool
74-
telemetry_parse_version(const char *json, const long installed_version[3], VersionResult *result)
48+
telemetry_parse_version(const char *json, VersionInfo *installed_version, VersionResult *result)
7549
{
7650
Datum version = DirectFunctionCall2(json_object_field_text,
7751
CStringGetTextDatum(json),
7852
PointerGetDatum(cstring_to_text(TS_VERSION_JSON_FIELD)));
79-
int i;
53+
54+
memset(result, 0, sizeof(VersionResult));
8055

8156
result->versionstr = text_to_cstring(DatumGetTextPP(version));
8257

@@ -89,25 +64,17 @@ telemetry_parse_version(const char *json, const long installed_version[3], Versi
8964
}
9065

9166
/*
92-
* Now parse the version string. We expect format to be XX.XX.XX, and if
93-
* not, we error out
67+
* Now parse the version string. We expect format to be
68+
* XX.XX.XX-<prerelease_tag>, and if not, we error out
9469
*/
95-
if (!parse_version_string(result->versionstr, result->version))
70+
if (!version_parse(result->versionstr, &result->vinfo))
9671
{
97-
result->errhint = "could not parse version string";
72+
result->errhint = psprintf("parsing failed for version string \"%s\"", result->versionstr);
9873
return false;
9974
}
10075

101-
for (i = 0; i < 3; i++)
102-
{
103-
if (installed_version[i] < result->version[i])
104-
return true;
105-
106-
if (installed_version[i] > result->version[i])
107-
break;
108-
}
109-
110-
result->is_up_to_date = true;
76+
if (version_cmp(installed_version, &result->vinfo) >= 0)
77+
result->is_up_to_date = true;
11178

11279
return true;
11380
}
@@ -117,21 +84,18 @@ telemetry_parse_version(const char *json, const long installed_version[3], Versi
11784
* called "current_timescaledb_version". Check this against the local
11885
* version, and notify the user if it is behind.
11986
*/
120-
static void
87+
static bool
12188
process_response(const char *json)
12289
{
123-
const long installed_version[3] = {
124-
strtol(TIMESCALEDB_MAJOR_VERSION, NULL, 10),
125-
strtol(TIMESCALEDB_MINOR_VERSION, NULL, 10),
126-
strtol(TIMESCALEDB_PATCH_VERSION, NULL, 10)};
127-
VersionResult result = {0};
90+
VersionInfo installed_version;
91+
VersionResult result;
12892

129-
if (!telemetry_parse_version(json, installed_version, &result))
130-
{
131-
if (NULL == result.versionstr)
132-
elog(ERROR, "could not get TimescaleDB version from server response");
93+
version_get_info(&installed_version);
13394

134-
elog(ERROR, "ill-formatted TimescaleDB version in server response");
95+
if (!telemetry_parse_version(json, &installed_version, &result))
96+
{
97+
elog(WARNING, "could not get TimescaleDB version from server response: %s", result.errhint);
98+
return false;
13599
}
136100

137101
if (result.is_up_to_date)
@@ -141,6 +105,8 @@ process_response(const char *json)
141105
(errmsg("the \"%s\" extension is not up-to-date", EXTENSION_NAME),
142106
errhint("The most up-to-date version is %s, the installed version is %s",
143107
result.versionstr, TIMESCALEDB_VERSION_MOD)));
108+
109+
return true;
144110
}
145111

146112
static char *
@@ -286,7 +252,10 @@ telemetry_connect(void)
286252
conn = connection_create(CONNECTION_SSL);
287253

288254
if (conn == NULL)
289-
elog(ERROR, "could not create telemetry connection");
255+
{
256+
elog(WARNING, "could not create telemetry connection");
257+
return NULL;
258+
}
290259

291260
ret = connection_connect(conn, TELEMETRY_HOST, TELEMETRY_SCHEME, 0);
292261

@@ -295,8 +264,9 @@ telemetry_connect(void)
295264
const char *errstr = connection_get_and_clear_error(conn);
296265

297266
connection_destroy(conn);
267+
conn = NULL;
298268

299-
ereport(ERROR,
269+
ereport(WARNING,
300270
(errcode(ERRCODE_INTERNAL_ERROR),
301271
errmsg("could not make a connection to %s", TELEMETRY_ENDPOINT),
302272
errdetail("%s", errstr)));
@@ -335,11 +305,17 @@ telemetry_main()
335305
connection_destroy(conn);
336306

337307
if (err != HTTP_ERROR_NONE)
338-
elog(ERROR, "telemetry error: %s", http_strerror(err));
308+
{
309+
elog(WARNING, "telemetry error: %s", http_strerror(err));
310+
return;
311+
}
339312

340313
if (!http_response_state_valid_status(rsp))
341-
elog(ERROR, "telemetry got unexpected HTTP response status: %d",
314+
{
315+
elog(WARNING, "telemetry got unexpected HTTP response status: %d",
342316
http_response_state_status_code(rsp));
317+
return;
318+
}
343319

344320
/*
345321
* Do the version-check. Response is the body of a well-formed HTTP

src/telemetry/telemetry.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,15 @@
2222

2323
typedef struct VersionResult
2424
{
25+
VersionInfo vinfo;
2526
const char *versionstr;
26-
long version[3];
2727
bool is_up_to_date;
2828
const char *errhint;
2929
} VersionResult;
3030

3131
HttpRequest *build_version_request(const char *host, const char *path);
3232
Connection *telemetry_connect(void);
33-
bool telemetry_parse_version(const char *json, const long installed_version[3], VersionResult *result);
33+
bool telemetry_parse_version(const char *json, VersionInfo *vinfo, VersionResult *result);
3434

3535
/*
3636
* This function is intended as the main function for a BGW.

src/version.c

Lines changed: 148 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,152 @@
99
#include "compat.h"
1010
#include "gitcommit.h"
1111
#include "version.h"
12+
#include "config.h"
1213

1314
#define STR_EXPAND(x) #x
1415
#define STR(x) STR_EXPAND(x)
1516

17+
void
18+
version_get_info(VersionInfo *vinfo)
19+
{
20+
memset(vinfo, 0, sizeof(VersionInfo));
21+
vinfo->version[0] = strtol(TIMESCALEDB_MAJOR_VERSION, NULL, 10);
22+
vinfo->version[1] = strtol(TIMESCALEDB_MINOR_VERSION, NULL, 10);
23+
vinfo->version[2] = strtol(TIMESCALEDB_PATCH_VERSION, NULL, 10);
24+
25+
if (strlen(TIMESCALEDB_MOD_VERSION) > 0)
26+
{
27+
StrNCpy(vinfo->version_mod, TIMESCALEDB_MOD_VERSION, sizeof(vinfo->version_mod));
28+
vinfo->has_version_mod = true;
29+
}
30+
}
31+
32+
/*
33+
* Compare two versions.
34+
*
35+
* It returns an integer less than, equal to, or greater than zero if version
36+
* v1 is found, respectively, to be less than, to match, or be greater than
37+
* version v2.
38+
*/
39+
int
40+
version_cmp(VersionInfo *v1, VersionInfo *v2)
41+
{
42+
int i;
43+
44+
for (i = 0; i < 3; i++)
45+
{
46+
if (v1->version[i] > v2->version[i])
47+
return 1;
48+
49+
if (v1->version[i] < v2->version[i])
50+
return -1;
51+
}
52+
53+
/*
54+
* Note that the version mod signifies a pre-release version, so having a
55+
* version mod is "less" than not having one with otherwise identical
56+
* versions
57+
*/
58+
if (v1->has_version_mod && !v2->has_version_mod)
59+
return -1;
60+
61+
if (!v1->has_version_mod && v2->has_version_mod)
62+
return 1;
63+
64+
/* Compare the version mod lexicographically */
65+
if (v1->has_version_mod && v2->has_version_mod)
66+
return strncmp(v1->version_mod, v2->version_mod, sizeof(v1->version_mod));
67+
68+
return 0;
69+
}
70+
71+
#define NUM_VERSION_DELIMS 4
72+
73+
static const char *version_delimiter[NUM_VERSION_DELIMS] = {".", ".", "-", ""};
74+
75+
bool
76+
version_parse(const char *version, VersionInfo *result)
77+
{
78+
char *parse_version = pstrdup(version);
79+
int i;
80+
81+
memset(result, 0, sizeof(VersionInfo));
82+
83+
for (i = 0; i < NUM_VERSION_DELIMS; i++)
84+
{
85+
const char *subversion;
86+
87+
subversion = strtok(i == 0 ? parse_version : NULL, version_delimiter[i]);
88+
89+
if (subversion == NULL)
90+
return i > 0;
91+
92+
/*
93+
* If we are past the '-' delimiter, we've found the mod/pre-release
94+
* tag
95+
*/
96+
if (i > 0 && version_delimiter[i - 1][0] == '-')
97+
{
98+
int len = snprintf(result->version_mod, sizeof(result->version_mod) - 1, "%s", subversion);
99+
100+
if (len > (sizeof(result->version_mod) - 1))
101+
return false;
102+
103+
if (len > 0)
104+
result->has_version_mod = true;
105+
}
106+
else
107+
{
108+
char *endptr;
109+
110+
result->version[i] = strtol(subversion, &endptr, 10);
111+
112+
/*
113+
* We expect the parsing of the version num to end at a '\0' since
114+
* strtok() should have replaced the delimiter with a '\0' if the
115+
* delimiter was found
116+
*/
117+
if (endptr != NULL && *endptr != '\0')
118+
return false;
119+
}
120+
}
121+
122+
return true;
123+
}
124+
125+
TS_FUNCTION_INFO_V1(ts_version_get_info);
126+
127+
Datum
128+
ts_version_get_info(PG_FUNCTION_ARGS)
129+
{
130+
VersionInfo info;
131+
TupleDesc tupdesc;
132+
Datum values[4];
133+
bool nulls[4] = {false};
134+
HeapTuple tuple;
135+
136+
version_get_info(&info);
137+
138+
if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
139+
ereport(ERROR,
140+
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
141+
errmsg("function returning record called in context "
142+
"that cannot accept type record")));
143+
144+
values[0] = Int32GetDatum((int32) info.version[0]);
145+
values[1] = Int32GetDatum((int32) info.version[1]);
146+
values[2] = Int32GetDatum((int32) info.version[2]);
147+
148+
if (info.has_version_mod)
149+
values[3] = CStringGetTextDatum(info.version_mod);
150+
else
151+
nulls[3] = true;
152+
153+
tuple = heap_form_tuple(tupdesc, values, nulls);
154+
155+
return HeapTupleGetDatum(tuple);
156+
}
157+
16158
const char *git_commit = STR(EXT_GIT_COMMIT);
17159

18160
TS_FUNCTION_INFO_V1(get_git_commit);
@@ -59,9 +201,9 @@ version_get_os_info(VersionOSInfo *info)
59201
if (!VerQueryValueA(buffer, TEXT("\\"), &vinfo, &vinfo_len))
60202
goto error;
61203

62-
snprintf(info->sysname, VERSION_OS_INFO_LEN - 1, "Windows");
63-
snprintf(info->version, VERSION_OS_INFO_LEN - 1, "%u", HIWORD(vinfo->dwProductVersionMS));
64-
snprintf(info->release, VERSION_OS_INFO_LEN - 1, "%u", LOWORD(vinfo->dwProductVersionMS));
204+
snprintf(info->sysname, VERSION_INFO_LEN - 1, "Windows");
205+
snprintf(info->version, VERSION_INFO_LEN - 1, "%u", HIWORD(vinfo->dwProductVersionMS));
206+
snprintf(info->release, VERSION_INFO_LEN - 1, "%u", LOWORD(vinfo->dwProductVersionMS));
65207

66208
pfree(buffer);
67209

@@ -85,9 +227,9 @@ version_get_os_info(VersionOSInfo *info)
85227
uname(&os_info);
86228

87229
memset(info, 0, sizeof(VersionOSInfo));
88-
strncpy(info->sysname, os_info.sysname, VERSION_OS_INFO_LEN - 1);
89-
strncpy(info->version, os_info.version, VERSION_OS_INFO_LEN - 1);
90-
strncpy(info->release, os_info.release, VERSION_OS_INFO_LEN - 1);
230+
strncpy(info->sysname, os_info.sysname, VERSION_INFO_LEN - 1);
231+
strncpy(info->version, os_info.version, VERSION_INFO_LEN - 1);
232+
strncpy(info->release, os_info.release, VERSION_INFO_LEN - 1);
91233

92234
return true;
93235
}

0 commit comments

Comments
 (0)