-
Notifications
You must be signed in to change notification settings - Fork 93
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
feat(c/driver/postgresql): TimestampTz write #868
Changes from 5 commits
6e3d660
038e55c
936fc58
0fafff0
cff949c
57cd91f
87ed7a0
e6e59a7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -145,6 +145,9 @@ struct BindStream { | |
// XXX: this assumes fixed-length fields only - will need more | ||
// consideration to deal with variable-length fields | ||
|
||
bool has_tz_field = false; | ||
std::string tz_setting; | ||
|
||
struct ArrowError na_error; | ||
|
||
explicit BindStream(struct ArrowArrayStream&& bind) { | ||
|
@@ -217,12 +220,6 @@ struct BindStream { | |
param_lengths[i] = 0; | ||
break; | ||
case ArrowType::NANOARROW_TYPE_TIMESTAMP: | ||
if (strcmp("", bind_schema_fields[i].timezone)) { | ||
SetError(error, "[libpq] Field #%" PRIi64 "%s%s%s", | ||
static_cast<int64_t>(i + 1), " (\"", bind_schema->children[i]->name, | ||
"\") has unsupported type code timestamp with timezone"); | ||
return ADBC_STATUS_NOT_IMPLEMENTED; | ||
} | ||
type_id = PostgresTypeId::kTimestamp; | ||
param_lengths[i] = 8; | ||
break; | ||
|
@@ -255,6 +252,45 @@ struct BindStream { | |
|
||
AdbcStatusCode Prepare(PGconn* conn, const std::string& query, | ||
struct AdbcError* error) { | ||
// tz-aware timestamps require special handling to set the timezone to UTC | ||
// prior to sending over the binary protocol; must be reset after execute | ||
for (int64_t col = 0; col < bind_schema->n_children; col++) { | ||
if ((bind_schema_fields[col].type == ArrowType::NANOARROW_TYPE_TIMESTAMP) && | ||
(strcmp("", bind_schema_fields[col].timezone))) { | ||
has_tz_field = true; | ||
|
||
PGresult* begin_result = PQexec(conn, "BEGIN"); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we need to hoist this out of the loop? Or else, only do this if (Also: I wonder if we shouldn't always begin/end a transaction when dealing with multiple bind parameters...) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. +how does this interact with auto-commit off? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As far as autocommit goes I do think you are right about that being murky. I'm not sure how to best resolve that yet either - beginning / ending the transaction here within the Are there tests already for autocommit in the ADBC suite? I was looking for a reference on how those are expected to behave. Wasn't sure if I was overlooking those of if it is something that hasn't been heavily implemented yet There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ah, I missed that little break at the end. There are some simple tests that test the options, but not really the semantics. At least here, I think you 'just' need to check if autocommit is off, if so, there's no need to BEGIN (you're already in a transaction), and you can still commit at the end (~though maybe semantically we shouldn't? This isn't a feature that's normally available in other APIs so I'm not sure what people expect) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any ideas on how to best forward the autocommit setting to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A statement already holds a connection, so we can just add a getter to the PostgresConnection class |
||
if (PQresultStatus(begin_result) != PGRES_COMMAND_OK) { | ||
SetError(error, "[libpq] Failed to begin transaction for timezone data: %s", | ||
PQerrorMessage(conn)); | ||
PQclear(begin_result); | ||
return ADBC_STATUS_IO; | ||
} | ||
PQclear(begin_result); | ||
|
||
PGresult* get_tz_result = PQexec(conn, "SELECT current_setting('TIMEZONE')"); | ||
if (PQresultStatus(get_tz_result) != PGRES_TUPLES_OK) { | ||
SetError(error, "[libpq] Could not query current timezone: %s", | ||
PQerrorMessage(conn)); | ||
PQclear(get_tz_result); | ||
return ADBC_STATUS_IO; | ||
} | ||
|
||
tz_setting = std::string(PQgetvalue(get_tz_result, 0, 0)); | ||
PQclear(get_tz_result); | ||
|
||
PGresult* set_utc_result = PQexec(conn, "SET TIME ZONE 'UTC'"); | ||
if (PQresultStatus(set_utc_result) != PGRES_COMMAND_OK) { | ||
SetError(error, "[libpq] Failed to set time zone to UTC: %s", | ||
PQerrorMessage(conn)); | ||
PQclear(set_utc_result); | ||
return ADBC_STATUS_IO; | ||
} | ||
PQclear(set_utc_result); | ||
break; | ||
} | ||
} | ||
|
||
PGresult* result = PQprepare(conn, /*stmtName=*/"", query.c_str(), | ||
/*nParams=*/bind_schema->n_children, param_types.data()); | ||
if (PQresultStatus(result) != PGRES_COMMAND_OK) { | ||
|
@@ -349,12 +385,6 @@ struct BindStream { | |
} | ||
case ArrowType::NANOARROW_TYPE_TIMESTAMP: { | ||
int64_t val = array_view->children[col]->buffer_views[1].data.as_int64[row]; | ||
if (strcmp("", bind_schema_fields[col].timezone)) { | ||
SetError(error, "[libpq] Column #%" PRIi64 "%s%s%s", col + 1, " (\"", | ||
PQfname(result, col), | ||
"\") has unsupported type code timestamp with timezone"); | ||
return ADBC_STATUS_NOT_IMPLEMENTED; | ||
} | ||
|
||
// 2000-01-01 00:00:00.000000 in microseconds | ||
constexpr int64_t kPostgresTimestampEpoch = 946684800000000; | ||
|
@@ -418,6 +448,26 @@ struct BindStream { | |
PQclear(result); | ||
} | ||
if (rows_affected) *rows_affected += array->length; | ||
|
||
if (has_tz_field) { | ||
std::string reset_query = "SET TIME ZONE '" + tz_setting + "'"; | ||
PGresult* reset_tz_result = PQexec(conn, reset_query.c_str()); | ||
if (PQresultStatus(reset_tz_result) != PGRES_COMMAND_OK) { | ||
SetError(error, "[libpq] Failed to reset time zone: %s", PQerrorMessage(conn)); | ||
PQclear(reset_tz_result); | ||
return ADBC_STATUS_IO; | ||
} | ||
PQclear(reset_tz_result); | ||
|
||
PGresult* commit_result = PQexec(conn, "COMMIT"); | ||
if (PQresultStatus(commit_result) != PGRES_COMMAND_OK) { | ||
SetError(error, "[libpq] Failed to commit transaction: %s", | ||
PQerrorMessage(conn)); | ||
PQclear(commit_result); | ||
return ADBC_STATUS_IO; | ||
} | ||
PQclear(commit_result); | ||
} | ||
} | ||
return ADBC_STATUS_OK; | ||
} | ||
|
@@ -664,12 +714,10 @@ AdbcStatusCode PostgresStatement::CreateBulkTable( | |
break; | ||
case ArrowType::NANOARROW_TYPE_TIMESTAMP: | ||
if (strcmp("", source_schema_fields[i].timezone)) { | ||
SetError(error, "[libpq] Field #%" PRIi64 "%s%s%s", static_cast<int64_t>(i + 1), | ||
" (\"", source_schema.children[i]->name, | ||
"\") has unsupported type for ingestion timestamp with timezone"); | ||
return ADBC_STATUS_NOT_IMPLEMENTED; | ||
create += " TIMESTAMPTZ"; | ||
} else { | ||
create += " TIMESTAMP"; | ||
} | ||
create += " TIMESTAMP"; | ||
break; | ||
default: | ||
SetError(error, "%s%" PRIu64 "%s%s%s%s", "[libpq] Field #", | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agh, I was going to ask for this to be
std::optional
...but we can't do that