mirror of https://github.com/citusdata/citus.git
Merge pull request #3318 from citusdata/fetch_intermediate_results
Implement fetch_intermediate_resultspull/3325/head
commit
c9ceff7d78
|
@ -2735,7 +2735,14 @@ ProcessCopyStmt(CopyStmt *copyStatement, char *completionTag, const char *queryS
|
||||||
{
|
{
|
||||||
const char *resultId = copyStatement->relation->relname;
|
const char *resultId = copyStatement->relation->relname;
|
||||||
|
|
||||||
|
if (copyStatement->is_from)
|
||||||
|
{
|
||||||
ReceiveQueryResultViaCopy(resultId);
|
ReceiveQueryResultViaCopy(resultId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendQueryResultViaCopy(resultId);
|
||||||
|
}
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,7 @@
|
||||||
#include "distributed/intermediate_results.h"
|
#include "distributed/intermediate_results.h"
|
||||||
#include "distributed/master_metadata_utility.h"
|
#include "distributed/master_metadata_utility.h"
|
||||||
#include "distributed/metadata_cache.h"
|
#include "distributed/metadata_cache.h"
|
||||||
|
#include "distributed/multi_client_executor.h"
|
||||||
#include "distributed/multi_executor.h"
|
#include "distributed/multi_executor.h"
|
||||||
#include "distributed/remote_commands.h"
|
#include "distributed/remote_commands.h"
|
||||||
#include "distributed/transmit.h"
|
#include "distributed/transmit.h"
|
||||||
|
@ -96,12 +97,17 @@ static void ReadIntermediateResultsIntoFuncOutput(FunctionCallInfo fcinfo,
|
||||||
char *copyFormat,
|
char *copyFormat,
|
||||||
Datum *resultIdArray,
|
Datum *resultIdArray,
|
||||||
int resultCount);
|
int resultCount);
|
||||||
|
static uint64 FetchRemoteIntermediateResult(MultiConnection *connection, char *resultId);
|
||||||
|
static CopyStatus CopyDataFromConnection(MultiConnection *connection,
|
||||||
|
FileCompat *fileCompat,
|
||||||
|
uint64 *bytesReceived);
|
||||||
|
|
||||||
/* exports for SQL callable functions */
|
/* exports for SQL callable functions */
|
||||||
PG_FUNCTION_INFO_V1(read_intermediate_result);
|
PG_FUNCTION_INFO_V1(read_intermediate_result);
|
||||||
PG_FUNCTION_INFO_V1(read_intermediate_result_array);
|
PG_FUNCTION_INFO_V1(read_intermediate_result_array);
|
||||||
PG_FUNCTION_INFO_V1(broadcast_intermediate_result);
|
PG_FUNCTION_INFO_V1(broadcast_intermediate_result);
|
||||||
PG_FUNCTION_INFO_V1(create_intermediate_result);
|
PG_FUNCTION_INFO_V1(create_intermediate_result);
|
||||||
|
PG_FUNCTION_INFO_V1(fetch_intermediate_results);
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -511,6 +517,20 @@ RemoteFileDestReceiverDestroy(DestReceiver *destReceiver)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* SendQueryResultViaCopy is called when a COPY "resultid" TO STDOUT
|
||||||
|
* WITH (format result) command is received from the client. The
|
||||||
|
* contents of the file are sent directly to the client.
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
SendQueryResultViaCopy(const char *resultId)
|
||||||
|
{
|
||||||
|
const char *resultFileName = QueryResultFileName(resultId);
|
||||||
|
|
||||||
|
SendRegularFile(resultFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ReceiveQueryResultViaCopy is called when a COPY "resultid" FROM
|
* ReceiveQueryResultViaCopy is called when a COPY "resultid" FROM
|
||||||
* STDIN WITH (format result) command is received from the client.
|
* STDIN WITH (format result) command is received from the client.
|
||||||
|
@ -769,3 +789,226 @@ ReadIntermediateResultsIntoFuncOutput(FunctionCallInfo fcinfo, char *copyFormat,
|
||||||
|
|
||||||
tuplestore_donestoring(tupleStore);
|
tuplestore_donestoring(tupleStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* fetch_intermediate_results fetches a set of intermediate results defined in an
|
||||||
|
* array of result IDs from a remote node and writes them to a local intermediate
|
||||||
|
* result with the same ID.
|
||||||
|
*/
|
||||||
|
Datum
|
||||||
|
fetch_intermediate_results(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
ArrayType *resultIdObject = PG_GETARG_ARRAYTYPE_P(0);
|
||||||
|
Datum *resultIdArray = DeconstructArrayObject(resultIdObject);
|
||||||
|
int32 resultCount = ArrayObjectCount(resultIdObject);
|
||||||
|
text *remoteHostText = PG_GETARG_TEXT_P(1);
|
||||||
|
char *remoteHost = text_to_cstring(remoteHostText);
|
||||||
|
int remotePort = PG_GETARG_INT32(2);
|
||||||
|
|
||||||
|
int connectionFlags = 0;
|
||||||
|
int resultIndex = 0;
|
||||||
|
int64 totalBytesWritten = 0L;
|
||||||
|
|
||||||
|
CheckCitusVersion(ERROR);
|
||||||
|
|
||||||
|
if (resultCount == 0)
|
||||||
|
{
|
||||||
|
PG_RETURN_INT64(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!IsMultiStatementTransaction())
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errmsg("fetch_intermediate_results can only be used in a "
|
||||||
|
"distributed transaction")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make sure that this transaction has a distributed transaction ID.
|
||||||
|
*
|
||||||
|
* Intermediate results will be stored in a directory that is derived
|
||||||
|
* from the distributed transaction ID.
|
||||||
|
*/
|
||||||
|
UseCoordinatedTransaction();
|
||||||
|
|
||||||
|
MultiConnection *connection = GetNodeConnection(connectionFlags, remoteHost,
|
||||||
|
remotePort);
|
||||||
|
|
||||||
|
if (PQstatus(connection->pgConn) != CONNECTION_OK)
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errmsg("cannot connect to %s:%d to fetch intermediate "
|
||||||
|
"results",
|
||||||
|
remoteHost, remotePort)));
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteTransactionBegin(connection);
|
||||||
|
|
||||||
|
for (resultIndex = 0; resultIndex < resultCount; resultIndex++)
|
||||||
|
{
|
||||||
|
char *resultId = TextDatumGetCString(resultIdArray[resultIndex]);
|
||||||
|
|
||||||
|
totalBytesWritten += FetchRemoteIntermediateResult(connection, resultId);
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoteTransactionCommit(connection);
|
||||||
|
CloseConnection(connection);
|
||||||
|
|
||||||
|
PG_RETURN_INT64(totalBytesWritten);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* FetchRemoteIntermediateResult fetches a remote intermediate result over
|
||||||
|
* the given connection.
|
||||||
|
*/
|
||||||
|
static uint64
|
||||||
|
FetchRemoteIntermediateResult(MultiConnection *connection, char *resultId)
|
||||||
|
{
|
||||||
|
uint64 totalBytesWritten = 0;
|
||||||
|
|
||||||
|
StringInfo copyCommand = makeStringInfo();
|
||||||
|
const int fileFlags = (O_APPEND | O_CREAT | O_RDWR | O_TRUNC | PG_BINARY);
|
||||||
|
const int fileMode = (S_IRUSR | S_IWUSR);
|
||||||
|
|
||||||
|
PGconn *pgConn = connection->pgConn;
|
||||||
|
int socket = PQsocket(pgConn);
|
||||||
|
bool raiseErrors = true;
|
||||||
|
|
||||||
|
CreateIntermediateResultsDirectory();
|
||||||
|
|
||||||
|
appendStringInfo(copyCommand, "COPY \"%s\" TO STDOUT WITH (format result)",
|
||||||
|
resultId);
|
||||||
|
|
||||||
|
if (!SendRemoteCommand(connection, copyCommand->data))
|
||||||
|
{
|
||||||
|
ReportConnectionError(connection, ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
PGresult *result = GetRemoteCommandResult(connection, raiseErrors);
|
||||||
|
if (PQresultStatus(result) != PGRES_COPY_OUT)
|
||||||
|
{
|
||||||
|
ReportResultError(connection, result, ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
PQclear(result);
|
||||||
|
|
||||||
|
char *localPath = QueryResultFileName(resultId);
|
||||||
|
File fileDesc = FileOpenForTransmit(localPath, fileFlags, fileMode);
|
||||||
|
FileCompat fileCompat = FileCompatFromFileStart(fileDesc);
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
int waitFlags = WL_SOCKET_READABLE;
|
||||||
|
|
||||||
|
CopyStatus copyStatus = CopyDataFromConnection(connection, &fileCompat,
|
||||||
|
&totalBytesWritten);
|
||||||
|
if (copyStatus == CLIENT_COPY_FAILED)
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errmsg("failed to read result \"%s\" from node %s:%d",
|
||||||
|
resultId, connection->hostname, connection->port)));
|
||||||
|
}
|
||||||
|
else if (copyStatus == CLIENT_COPY_DONE)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Assert(copyStatus == CLIENT_COPY_MORE);
|
||||||
|
|
||||||
|
int rc = WaitLatchOrSocket(MyLatch, waitFlags, socket, 0, PG_WAIT_EXTENSION);
|
||||||
|
if (rc & WL_POSTMASTER_DEATH)
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errmsg("postmaster was shut down, exiting")));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rc & WL_LATCH_SET)
|
||||||
|
{
|
||||||
|
ResetLatch(MyLatch);
|
||||||
|
CHECK_FOR_INTERRUPTS();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FileClose(fileDesc);
|
||||||
|
|
||||||
|
ClearResults(connection, raiseErrors);
|
||||||
|
|
||||||
|
return totalBytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CopyDataFromConnection reads a row of copy data from connection and writes it
|
||||||
|
* to the given file.
|
||||||
|
*/
|
||||||
|
static CopyStatus
|
||||||
|
CopyDataFromConnection(MultiConnection *connection, FileCompat *fileCompat,
|
||||||
|
uint64 *bytesReceived)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Consume input to handle the case where previous copy operation might have
|
||||||
|
* received zero bytes.
|
||||||
|
*/
|
||||||
|
int consumed = PQconsumeInput(connection->pgConn);
|
||||||
|
if (consumed == 0)
|
||||||
|
{
|
||||||
|
return CLIENT_COPY_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* receive copy data message in an asynchronous manner */
|
||||||
|
char *receiveBuffer = NULL;
|
||||||
|
bool asynchronous = true;
|
||||||
|
int receiveLength = PQgetCopyData(connection->pgConn, &receiveBuffer, asynchronous);
|
||||||
|
while (receiveLength > 0)
|
||||||
|
{
|
||||||
|
/* received copy data; append these data to file */
|
||||||
|
errno = 0;
|
||||||
|
|
||||||
|
int bytesWritten = FileWriteCompat(fileCompat, receiveBuffer,
|
||||||
|
receiveLength, PG_WAIT_IO);
|
||||||
|
if (bytesWritten != receiveLength)
|
||||||
|
{
|
||||||
|
ereport(ERROR, (errcode_for_file_access(),
|
||||||
|
errmsg("could not append to file: %m")));
|
||||||
|
}
|
||||||
|
|
||||||
|
*bytesReceived += receiveLength;
|
||||||
|
PQfreemem(receiveBuffer);
|
||||||
|
receiveLength = PQgetCopyData(connection->pgConn, &receiveBuffer, asynchronous);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receiveLength == 0)
|
||||||
|
{
|
||||||
|
/* we cannot read more data without blocking */
|
||||||
|
return CLIENT_COPY_MORE;
|
||||||
|
}
|
||||||
|
else if (receiveLength == -1)
|
||||||
|
{
|
||||||
|
/* received copy done message */
|
||||||
|
bool raiseInterrupts = true;
|
||||||
|
PGresult *result = GetRemoteCommandResult(connection, raiseInterrupts);
|
||||||
|
ExecStatusType resultStatus = PQresultStatus(result);
|
||||||
|
CopyStatus copyStatus = 0;
|
||||||
|
|
||||||
|
if (resultStatus == PGRES_COMMAND_OK)
|
||||||
|
{
|
||||||
|
copyStatus = CLIENT_COPY_DONE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
copyStatus = CLIENT_COPY_FAILED;
|
||||||
|
|
||||||
|
ReportResultError(connection, result, WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
PQclear(result);
|
||||||
|
ForgetResults(connection);
|
||||||
|
|
||||||
|
return copyStatus;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Assert(receiveLength == -2);
|
||||||
|
ReportConnectionError(connection, WARNING);
|
||||||
|
|
||||||
|
return CLIENT_COPY_FAILED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
#include "udfs/read_intermediate_results/9.2-1.sql"
|
#include "udfs/read_intermediate_results/9.2-1.sql"
|
||||||
|
#include "udfs/fetch_intermediate_results/9.2-1.sql"
|
||||||
|
|
||||||
ALTER TABLE pg_catalog.pg_dist_colocation ADD distributioncolumncollation oid;
|
ALTER TABLE pg_catalog.pg_dist_colocation ADD distributioncolumncollation oid;
|
||||||
UPDATE pg_catalog.pg_dist_colocation dc SET distributioncolumncollation = t.typcollation
|
UPDATE pg_catalog.pg_dist_colocation dc SET distributioncolumncollation = t.typcollation
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
CREATE OR REPLACE FUNCTION pg_catalog.fetch_intermediate_results(
|
||||||
|
result_ids text[],
|
||||||
|
node_name text,
|
||||||
|
node_port int)
|
||||||
|
RETURNS bigint
|
||||||
|
LANGUAGE C STRICT VOLATILE
|
||||||
|
AS 'MODULE_PATHNAME', $$fetch_intermediate_results$$;
|
||||||
|
COMMENT ON FUNCTION pg_catalog.fetch_intermediate_results(text[],text,int)
|
||||||
|
IS 'fetch array of intermediate results from a remote node. returns number of bytes read.';
|
|
@ -0,0 +1,9 @@
|
||||||
|
CREATE OR REPLACE FUNCTION pg_catalog.fetch_intermediate_results(
|
||||||
|
result_ids text[],
|
||||||
|
node_name text,
|
||||||
|
node_port int)
|
||||||
|
RETURNS bigint
|
||||||
|
LANGUAGE C STRICT VOLATILE
|
||||||
|
AS 'MODULE_PATHNAME', $$fetch_intermediate_results$$;
|
||||||
|
COMMENT ON FUNCTION pg_catalog.fetch_intermediate_results(text[],text,int)
|
||||||
|
IS 'fetch array of intermediate results from a remote node. returns number of bytes read.';
|
|
@ -0,0 +1,70 @@
|
||||||
|
/*-------------------------------------------------------------------------
|
||||||
|
*
|
||||||
|
* test/src/intermediate_results.c
|
||||||
|
*
|
||||||
|
* This file contains functions to test functions related to
|
||||||
|
* src/backend/distributed/executor/intermediate_results.c.
|
||||||
|
*
|
||||||
|
* Copyright (c) Citus Data, Inc.
|
||||||
|
*
|
||||||
|
*-------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "postgres.h"
|
||||||
|
#include "funcapi.h"
|
||||||
|
#include "libpq-fe.h"
|
||||||
|
#include "miscadmin.h"
|
||||||
|
#include "pgstat.h"
|
||||||
|
|
||||||
|
#include "distributed/commands/multi_copy.h"
|
||||||
|
#include "distributed/connection_management.h"
|
||||||
|
#include "distributed/intermediate_results.h"
|
||||||
|
#include "distributed/multi_executor.h"
|
||||||
|
#include "distributed/remote_commands.h"
|
||||||
|
|
||||||
|
PG_FUNCTION_INFO_V1(store_intermediate_result_on_node);
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* store_intermediate_result_on_node executes a query and streams the results
|
||||||
|
* into a file on the given node.
|
||||||
|
*/
|
||||||
|
Datum
|
||||||
|
store_intermediate_result_on_node(PG_FUNCTION_ARGS)
|
||||||
|
{
|
||||||
|
text *nodeNameText = PG_GETARG_TEXT_P(0);
|
||||||
|
char *nodeNameString = text_to_cstring(nodeNameText);
|
||||||
|
int nodePort = PG_GETARG_INT32(1);
|
||||||
|
text *resultIdText = PG_GETARG_TEXT_P(2);
|
||||||
|
char *resultIdString = text_to_cstring(resultIdText);
|
||||||
|
text *queryText = PG_GETARG_TEXT_P(3);
|
||||||
|
char *queryString = text_to_cstring(queryText);
|
||||||
|
bool writeLocalFile = false;
|
||||||
|
ParamListInfo paramListInfo = NULL;
|
||||||
|
|
||||||
|
CheckCitusVersion(ERROR);
|
||||||
|
|
||||||
|
WorkerNode *workerNode = FindWorkerNode(nodeNameString, nodePort);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make sure that this transaction has a distributed transaction ID.
|
||||||
|
*
|
||||||
|
* Intermediate results will be stored in a directory that is derived
|
||||||
|
* from the distributed transaction ID.
|
||||||
|
*/
|
||||||
|
UseCoordinatedTransaction();
|
||||||
|
|
||||||
|
EState *estate = CreateExecutorState();
|
||||||
|
DestReceiver *resultDest = CreateRemoteFileDestReceiver(resultIdString, estate,
|
||||||
|
list_make1(workerNode),
|
||||||
|
writeLocalFile);
|
||||||
|
|
||||||
|
ExecuteQueryStringIntoDestReceiver(queryString, paramListInfo,
|
||||||
|
(DestReceiver *) resultDest);
|
||||||
|
|
||||||
|
FreeExecutorState(estate);
|
||||||
|
|
||||||
|
PG_RETURN_VOID();
|
||||||
|
}
|
|
@ -25,6 +25,7 @@
|
||||||
extern DestReceiver * CreateRemoteFileDestReceiver(char *resultId, EState *executorState,
|
extern DestReceiver * CreateRemoteFileDestReceiver(char *resultId, EState *executorState,
|
||||||
List *initialNodeList, bool
|
List *initialNodeList, bool
|
||||||
writeLocalFile);
|
writeLocalFile);
|
||||||
|
extern void SendQueryResultViaCopy(const char *resultId);
|
||||||
extern void ReceiveQueryResultViaCopy(const char *resultId);
|
extern void ReceiveQueryResultViaCopy(const char *resultId);
|
||||||
extern void RemoveIntermediateResultsDirectory(void);
|
extern void RemoveIntermediateResultsDirectory(void);
|
||||||
extern int64 IntermediateResultSize(char *resultId);
|
extern int64 IntermediateResultSize(char *resultId);
|
||||||
|
|
|
@ -73,3 +73,6 @@ s/_id_ref_id_fkey/_id_fkey/g
|
||||||
s/_ref_id_id_fkey_/_ref_id_fkey_/g
|
s/_ref_id_id_fkey_/_ref_id_fkey_/g
|
||||||
s/fk_test_2_col1_col2_fkey/fk_test_2_col1_fkey/g
|
s/fk_test_2_col1_col2_fkey/fk_test_2_col1_fkey/g
|
||||||
s/_id_other_column_ref_fkey/_id_fkey/g
|
s/_id_other_column_ref_fkey/_id_fkey/g
|
||||||
|
|
||||||
|
# intermediate_results
|
||||||
|
s/(ERROR.*)pgsql_job_cache\/([0-9]+_[0-9]+_[0-9]+)\/(.*).data/\1pgsql_job_cache\/xx_x_xxx\/\3.data/g
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
-- Test functions for copying intermediate results
|
-- Test functions for copying intermediate results
|
||||||
CREATE SCHEMA intermediate_results;
|
CREATE SCHEMA intermediate_results;
|
||||||
SET search_path TO 'intermediate_results';
|
SET search_path TO 'intermediate_results';
|
||||||
|
-- helper udfs
|
||||||
|
CREATE OR REPLACE FUNCTION pg_catalog.store_intermediate_result_on_node(nodename text, nodeport int, result_id text, query text)
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE C STRICT VOLATILE
|
||||||
|
AS 'citus', $$store_intermediate_result_on_node$$;
|
||||||
-- in the same transaction we can read a result
|
-- in the same transaction we can read a result
|
||||||
BEGIN;
|
BEGIN;
|
||||||
SELECT create_intermediate_result('squares', 'SELECT s, s*s FROM generate_series(1,5) s');
|
SELECT create_intermediate_result('squares', 'SELECT s, s*s FROM generate_series(1,5) s');
|
||||||
|
@ -413,6 +418,127 @@ EXPLAIN (COSTS ON) SELECT * FROM read_intermediate_results(ARRAY['stored_squares
|
||||||
(1 row)
|
(1 row)
|
||||||
|
|
||||||
END;
|
END;
|
||||||
|
--
|
||||||
|
-- fetch_intermediate_results
|
||||||
|
--
|
||||||
|
-- straightforward, single-result case
|
||||||
|
BEGIN;
|
||||||
|
SELECT broadcast_intermediate_result('squares_1', 'SELECT s, s*s FROM generate_series(1, 5) s');
|
||||||
|
broadcast_intermediate_result
|
||||||
|
-------------------------------
|
||||||
|
5
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM fetch_intermediate_results(ARRAY['squares_1']::text[], 'localhost', :worker_2_port);
|
||||||
|
fetch_intermediate_results
|
||||||
|
----------------------------
|
||||||
|
111
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM read_intermediate_result('squares_1', 'binary') AS res (x int, x2 int);
|
||||||
|
x | x2
|
||||||
|
---+----
|
||||||
|
1 | 1
|
||||||
|
2 | 4
|
||||||
|
3 | 9
|
||||||
|
4 | 16
|
||||||
|
5 | 25
|
||||||
|
(5 rows)
|
||||||
|
|
||||||
|
SELECT * FROM fetch_intermediate_results(ARRAY['squares_1']::text[], 'localhost', :worker_1_port);
|
||||||
|
fetch_intermediate_results
|
||||||
|
----------------------------
|
||||||
|
111
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM read_intermediate_result('squares_1', 'binary') AS res (x int, x2 int);
|
||||||
|
x | x2
|
||||||
|
---+----
|
||||||
|
1 | 1
|
||||||
|
2 | 4
|
||||||
|
3 | 9
|
||||||
|
4 | 16
|
||||||
|
5 | 25
|
||||||
|
(5 rows)
|
||||||
|
|
||||||
|
END;
|
||||||
|
-- multiple results, and some error cases
|
||||||
|
BEGIN;
|
||||||
|
SELECT store_intermediate_result_on_node('localhost', :worker_1_port,
|
||||||
|
'squares_1', 'SELECT s, s*s FROM generate_series(1, 2) s');
|
||||||
|
store_intermediate_result_on_node
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT store_intermediate_result_on_node('localhost', :worker_1_port,
|
||||||
|
'squares_2', 'SELECT s, s*s FROM generate_series(3, 4) s');
|
||||||
|
store_intermediate_result_on_node
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SAVEPOINT s1;
|
||||||
|
-- results aren't available on coordinator yet
|
||||||
|
SELECT * FROM read_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'binary') AS res (x int, x2 int);
|
||||||
|
ERROR: result "squares_1" does not exist
|
||||||
|
ROLLBACK TO SAVEPOINT s1;
|
||||||
|
-- fetch from worker 2 should fail
|
||||||
|
SELECT * FROM fetch_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'localhost', :worker_2_port);
|
||||||
|
ERROR: could not open file "base/pgsql_job_cache/10_0_200/squares_1.data": No such file or directory
|
||||||
|
CONTEXT: while executing command on localhost:57638
|
||||||
|
ROLLBACK TO SAVEPOINT s1;
|
||||||
|
-- still, results aren't available on coordinator yet
|
||||||
|
SELECT * FROM read_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'binary') AS res (x int, x2 int);
|
||||||
|
ERROR: result "squares_1" does not exist
|
||||||
|
ROLLBACK TO SAVEPOINT s1;
|
||||||
|
-- fetch from worker 1 should succeed
|
||||||
|
SELECT * FROM fetch_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'localhost', :worker_1_port);
|
||||||
|
fetch_intermediate_results
|
||||||
|
----------------------------
|
||||||
|
114
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM read_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'binary') AS res (x int, x2 int);
|
||||||
|
x | x2
|
||||||
|
---+----
|
||||||
|
1 | 1
|
||||||
|
2 | 4
|
||||||
|
3 | 9
|
||||||
|
4 | 16
|
||||||
|
(4 rows)
|
||||||
|
|
||||||
|
-- fetching again should succeed
|
||||||
|
SELECT * FROM fetch_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'localhost', :worker_1_port);
|
||||||
|
fetch_intermediate_results
|
||||||
|
----------------------------
|
||||||
|
114
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
SELECT * FROM read_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'binary') AS res (x int, x2 int);
|
||||||
|
x | x2
|
||||||
|
---+----
|
||||||
|
1 | 1
|
||||||
|
2 | 4
|
||||||
|
3 | 9
|
||||||
|
4 | 16
|
||||||
|
(4 rows)
|
||||||
|
|
||||||
|
ROLLBACK TO SAVEPOINT s1;
|
||||||
|
-- empty result id list should succeed
|
||||||
|
SELECT * FROM fetch_intermediate_results(ARRAY[]::text[], 'localhost', :worker_1_port);
|
||||||
|
fetch_intermediate_results
|
||||||
|
----------------------------
|
||||||
|
0
|
||||||
|
(1 row)
|
||||||
|
|
||||||
|
-- null in result id list should error gracefully
|
||||||
|
SELECT * FROM fetch_intermediate_results(ARRAY[NULL, 'squares_1', 'squares_2']::text[], 'localhost', :worker_1_port);
|
||||||
|
ERROR: worker array object cannot contain null values
|
||||||
|
END;
|
||||||
|
-- results should have been deleted after transaction commit
|
||||||
|
SELECT * FROM read_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'binary') AS res (x int, x2 int);
|
||||||
|
ERROR: result "squares_1" does not exist
|
||||||
DROP SCHEMA intermediate_results CASCADE;
|
DROP SCHEMA intermediate_results CASCADE;
|
||||||
NOTICE: drop cascades to 5 other objects
|
NOTICE: drop cascades to 5 other objects
|
||||||
DETAIL: drop cascades to table interesting_squares
|
DETAIL: drop cascades to table interesting_squares
|
||||||
|
|
|
@ -2,6 +2,12 @@
|
||||||
CREATE SCHEMA intermediate_results;
|
CREATE SCHEMA intermediate_results;
|
||||||
SET search_path TO 'intermediate_results';
|
SET search_path TO 'intermediate_results';
|
||||||
|
|
||||||
|
-- helper udfs
|
||||||
|
CREATE OR REPLACE FUNCTION pg_catalog.store_intermediate_result_on_node(nodename text, nodeport int, result_id text, query text)
|
||||||
|
RETURNS void
|
||||||
|
LANGUAGE C STRICT VOLATILE
|
||||||
|
AS 'citus', $$store_intermediate_result_on_node$$;
|
||||||
|
|
||||||
-- in the same transaction we can read a result
|
-- in the same transaction we can read a result
|
||||||
BEGIN;
|
BEGIN;
|
||||||
SELECT create_intermediate_result('squares', 'SELECT s, s*s FROM generate_series(1,5) s');
|
SELECT create_intermediate_result('squares', 'SELECT s, s*s FROM generate_series(1,5) s');
|
||||||
|
@ -218,4 +224,49 @@ SELECT create_intermediate_result('stored_squares', 'SELECT square FROM stored_s
|
||||||
EXPLAIN (COSTS ON) SELECT * FROM read_intermediate_results(ARRAY['stored_squares'], 'text') AS res (s intermediate_results.square_type);
|
EXPLAIN (COSTS ON) SELECT * FROM read_intermediate_results(ARRAY['stored_squares'], 'text') AS res (s intermediate_results.square_type);
|
||||||
END;
|
END;
|
||||||
|
|
||||||
|
--
|
||||||
|
-- fetch_intermediate_results
|
||||||
|
--
|
||||||
|
|
||||||
|
-- straightforward, single-result case
|
||||||
|
BEGIN;
|
||||||
|
SELECT broadcast_intermediate_result('squares_1', 'SELECT s, s*s FROM generate_series(1, 5) s');
|
||||||
|
SELECT * FROM fetch_intermediate_results(ARRAY['squares_1']::text[], 'localhost', :worker_2_port);
|
||||||
|
SELECT * FROM read_intermediate_result('squares_1', 'binary') AS res (x int, x2 int);
|
||||||
|
SELECT * FROM fetch_intermediate_results(ARRAY['squares_1']::text[], 'localhost', :worker_1_port);
|
||||||
|
SELECT * FROM read_intermediate_result('squares_1', 'binary') AS res (x int, x2 int);
|
||||||
|
END;
|
||||||
|
|
||||||
|
-- multiple results, and some error cases
|
||||||
|
BEGIN;
|
||||||
|
SELECT store_intermediate_result_on_node('localhost', :worker_1_port,
|
||||||
|
'squares_1', 'SELECT s, s*s FROM generate_series(1, 2) s');
|
||||||
|
SELECT store_intermediate_result_on_node('localhost', :worker_1_port,
|
||||||
|
'squares_2', 'SELECT s, s*s FROM generate_series(3, 4) s');
|
||||||
|
SAVEPOINT s1;
|
||||||
|
-- results aren't available on coordinator yet
|
||||||
|
SELECT * FROM read_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'binary') AS res (x int, x2 int);
|
||||||
|
ROLLBACK TO SAVEPOINT s1;
|
||||||
|
-- fetch from worker 2 should fail
|
||||||
|
SELECT * FROM fetch_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'localhost', :worker_2_port);
|
||||||
|
ROLLBACK TO SAVEPOINT s1;
|
||||||
|
-- still, results aren't available on coordinator yet
|
||||||
|
SELECT * FROM read_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'binary') AS res (x int, x2 int);
|
||||||
|
ROLLBACK TO SAVEPOINT s1;
|
||||||
|
-- fetch from worker 1 should succeed
|
||||||
|
SELECT * FROM fetch_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'localhost', :worker_1_port);
|
||||||
|
SELECT * FROM read_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'binary') AS res (x int, x2 int);
|
||||||
|
-- fetching again should succeed
|
||||||
|
SELECT * FROM fetch_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'localhost', :worker_1_port);
|
||||||
|
SELECT * FROM read_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'binary') AS res (x int, x2 int);
|
||||||
|
ROLLBACK TO SAVEPOINT s1;
|
||||||
|
-- empty result id list should succeed
|
||||||
|
SELECT * FROM fetch_intermediate_results(ARRAY[]::text[], 'localhost', :worker_1_port);
|
||||||
|
-- null in result id list should error gracefully
|
||||||
|
SELECT * FROM fetch_intermediate_results(ARRAY[NULL, 'squares_1', 'squares_2']::text[], 'localhost', :worker_1_port);
|
||||||
|
END;
|
||||||
|
|
||||||
|
-- results should have been deleted after transaction commit
|
||||||
|
SELECT * FROM read_intermediate_results(ARRAY['squares_1', 'squares_2']::text[], 'binary') AS res (x int, x2 int);
|
||||||
|
|
||||||
DROP SCHEMA intermediate_results CASCADE;
|
DROP SCHEMA intermediate_results CASCADE;
|
||||||
|
|
Loading…
Reference in New Issue