Merge pull request #4311 from citusdata/merge-cstore

Merge cstore into the citus repo
pull/4319/head
Nils Dijk 2020-11-17 19:10:32 +01:00 committed by GitHub
commit 2e09116b30
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
136 changed files with 25033 additions and 8 deletions

View File

@ -199,6 +199,37 @@ jobs:
flags: 'test_11,follower-cluster' flags: 'test_11,follower-cluster'
- store_artifacts: - store_artifacts:
path: '/tmp/core_dumps' path: '/tmp/core_dumps'
test-11_check-columnar:
docker:
- image: 'citus/exttester:11.9'
working_directory: /home/circleci/project
steps:
- checkout
- attach_workspace:
at: .
- run:
name: 'Install and Test (check-columnar)'
command: 'chown -R circleci:circleci /home/circleci && install-and-test-ext check-columnar'
no_output_timeout: 2m
- codecov/upload:
flags: 'test_11,columnar'
test-11_check-columnar-isolation:
docker:
- image: 'citus/exttester:11.9'
working_directory: /home/circleci/project
steps:
- checkout
- attach_workspace:
at: .
- run:
name: 'Install and Test (check-columnar-isolation)'
command: 'chown -R circleci:circleci /home/circleci && install-and-test-ext check-columnar-isolation'
no_output_timeout: 2m
- codecov/upload:
flags: 'test_11,columnar,isolation'
test-11_check-failure: test-11_check-failure:
docker: docker:
- image: 'citus/failtester:11.9' - image: 'citus/failtester:11.9'
@ -337,6 +368,36 @@ jobs:
- store_artifacts: - store_artifacts:
path: '/tmp/core_dumps' path: '/tmp/core_dumps'
test-12_check-columnar:
docker:
- image: 'citus/exttester:12.4'
working_directory: /home/circleci/project
steps:
- checkout
- attach_workspace:
at: .
- run:
name: 'Install and Test (check-columnar)'
command: 'chown -R circleci:circleci /home/circleci && install-and-test-ext check-columnar'
no_output_timeout: 2m
- codecov/upload:
flags: 'test_12,columnar'
test-12_check-columnar-isolation:
docker:
- image: 'citus/exttester:12.4'
working_directory: /home/circleci/project
steps:
- checkout
- attach_workspace:
at: .
- run:
name: 'Install and Test (check-columnar-isolation)'
command: 'chown -R circleci:circleci /home/circleci && install-and-test-ext check-columnar-isolation'
no_output_timeout: 2m
- codecov/upload:
flags: 'test_12,columnar,isolation'
test-12_check-failure: test-12_check-failure:
docker: docker:
- image: 'citus/failtester:12.4' - image: 'citus/failtester:12.4'
@ -473,6 +534,36 @@ jobs:
- store_artifacts: - store_artifacts:
path: '/tmp/core_dumps' path: '/tmp/core_dumps'
test-13_check-columnar:
docker:
- image: 'citus/exttester:13.0'
working_directory: /home/circleci/project
steps:
- checkout
- attach_workspace:
at: .
- run:
name: 'Install and Test (check-columnar)'
command: 'chown -R circleci:circleci /home/circleci && install-and-test-ext check-columnar'
no_output_timeout: 2m
- codecov/upload:
flags: 'test_13,columnar'
test-13_check-columnar-isolation:
docker:
- image: 'citus/exttester:13.0'
working_directory: /home/circleci/project
steps:
- checkout
- attach_workspace:
at: .
- run:
name: 'Install and Test (check-columnar-isolation)'
command: 'chown -R circleci:circleci /home/circleci && install-and-test-ext check-columnar-isolation'
no_output_timeout: 2m
- codecov/upload:
flags: 'test_13,columnar,isolation'
test-13_check-failure: test-13_check-failure:
docker: docker:
- image: 'citus/failtester:13.0' - image: 'citus/failtester:13.0'
@ -556,6 +647,10 @@ workflows:
requires: [build-11] requires: [build-11]
- test-11_check-follower-cluster: - test-11_check-follower-cluster:
requires: [build-11] requires: [build-11]
- test-11_check-columnar:
requires: [build-11]
- test-11_check-columnar-isolation:
requires: [build-11]
- test-11_check-failure: - test-11_check-failure:
requires: [build-11] requires: [build-11]
@ -571,6 +666,10 @@ workflows:
requires: [build-12] requires: [build-12]
- test-12_check-follower-cluster: - test-12_check-follower-cluster:
requires: [build-12] requires: [build-12]
- test-12_check-columnar:
requires: [build-12]
- test-12_check-columnar-isolation:
requires: [build-12]
- test-12_check-failure: - test-12_check-failure:
requires: [build-12] requires: [build-12]
@ -586,6 +685,10 @@ workflows:
requires: [build-13] requires: [build-13]
- test-13_check-follower-cluster: - test-13_check-follower-cluster:
requires: [build-13] requires: [build-13]
- test-13_check-columnar:
requires: [build-13]
- test-13_check-columnar-isolation:
requires: [build-13]
- test-13_check-failure: - test-13_check-failure:
requires: [build-13] requires: [build-13]

View File

@ -92,5 +92,7 @@ endif
override CPPFLAGS := @CPPFLAGS@ @CITUS_CPPFLAGS@ -I '${citus_abs_top_srcdir}/src/include' -I'${citus_top_builddir}/src/include' $(CPPFLAGS) override CPPFLAGS := @CPPFLAGS@ @CITUS_CPPFLAGS@ -I '${citus_abs_top_srcdir}/src/include' -I'${citus_top_builddir}/src/include' $(CPPFLAGS)
override LDFLAGS += @LDFLAGS@ @CITUS_LDFLAGS@ override LDFLAGS += @LDFLAGS@ @CITUS_LDFLAGS@
HAS_TABLEAM:=@HAS_TABLEAM@
# optional file with user defined, additional, rules # optional file with user defined, additional, rules
-include ${citus_abs_srcdir}/src/Makefile.custom -include ${citus_abs_srcdir}/src/Makefile.custom

View File

@ -4,7 +4,7 @@ set -euo pipefail
# shellcheck disable=SC1091 # shellcheck disable=SC1091
source ci/ci_helpers.sh source ci/ci_helpers.sh
for udf_dir in src/backend/distributed/sql/udfs/*; do for udf_dir in src/backend/distributed/sql/udfs/* src/backend/columnar/sql/udfs/*; do
# We want to find the last snapshotted sql file, to make sure it's the same # We want to find the last snapshotted sql file, to make sure it's the same
# as "latest.sql". This is done by: # as "latest.sql". This is done by:
# 1. Getting the filenames in the UDF directory (using find instead of ls, to keep shellcheck happy) # 1. Getting the filenames in the UDF directory (using find instead of ls, to keep shellcheck happy)

13
configure vendored
View File

@ -622,6 +622,7 @@ ac_includes_default="\
ac_subst_vars='LTLIBOBJS ac_subst_vars='LTLIBOBJS
LIBOBJS LIBOBJS
HAS_TABLEAM
HAS_DOTGIT HAS_DOTGIT
POSTGRES_BUILDDIR POSTGRES_BUILDDIR
POSTGRES_SRCDIR POSTGRES_SRCDIR
@ -4468,6 +4469,16 @@ cat >>confdefs.h <<_ACEOF
_ACEOF _ACEOF
if test "$version_num" != '11'; then
HAS_TABLEAM=yes
$as_echo "#define HAS_TABLEAM 1" >>confdefs.h
else
{ $as_echo "$as_me:${as_lineno-$LINENO}: postgres version does not support table access methodds" >&5
$as_echo "$as_me: postgres version does not support table access methodds" >&6;}
fi;
# Check if git is installed, when installed the gitref of the checkout will be baked in the application # Check if git is installed, when installed the gitref of the checkout will be baked in the application
# Extract the first word of "git", so it can be a program name with args. # Extract the first word of "git", so it can be a program name with args.
set dummy git; ac_word=$2 set dummy git; ac_word=$2
@ -4543,6 +4554,8 @@ POSTGRES_BUILDDIR="$POSTGRES_BUILDDIR"
HAS_DOTGIT="$HAS_DOTGIT" HAS_DOTGIT="$HAS_DOTGIT"
HAS_TABLEAM="$HAS_TABLEAM"
ac_config_files="$ac_config_files Makefile.global" ac_config_files="$ac_config_files Makefile.global"

View File

@ -212,6 +212,13 @@ PGAC_ARG_REQ(with, reports-hostname, [HOSTNAME],
AC_DEFINE_UNQUOTED(REPORTS_BASE_URL, "$REPORTS_BASE_URL", AC_DEFINE_UNQUOTED(REPORTS_BASE_URL, "$REPORTS_BASE_URL",
[Base URL for statistics collection and update checks]) [Base URL for statistics collection and update checks])
if test "$version_num" != '11'; then
HAS_TABLEAM=yes
AC_DEFINE([HAS_TABLEAM], 1, [Define to 1 to build with table access method support, pg12 and up])
else
AC_MSG_NOTICE([postgres version does not support table access methodds])
fi;
# Check if git is installed, when installed the gitref of the checkout will be baked in the application # Check if git is installed, when installed the gitref of the checkout will be baked in the application
AC_PATH_PROG(GIT_BIN, git) AC_PATH_PROG(GIT_BIN, git)
AC_CHECK_FILE(.git,[HAS_DOTGIT=yes], [HAS_DOTGIT=]) AC_CHECK_FILE(.git,[HAS_DOTGIT=yes], [HAS_DOTGIT=])
@ -222,6 +229,7 @@ AC_SUBST(CITUS_LDFLAGS, "$LIBS $CITUS_LDFLAGS")
AC_SUBST(POSTGRES_SRCDIR, "$POSTGRES_SRCDIR") AC_SUBST(POSTGRES_SRCDIR, "$POSTGRES_SRCDIR")
AC_SUBST(POSTGRES_BUILDDIR, "$POSTGRES_BUILDDIR") AC_SUBST(POSTGRES_BUILDDIR, "$POSTGRES_BUILDDIR")
AC_SUBST(HAS_DOTGIT, "$HAS_DOTGIT") AC_SUBST(HAS_DOTGIT, "$HAS_DOTGIT")
AC_SUBST(HAS_TABLEAM, "$HAS_TABLEAM")
AC_CONFIG_FILES([Makefile.global]) AC_CONFIG_FILES([Makefile.global])
AC_CONFIG_HEADERS([src/include/citus_config.h] [src/include/citus_version.h]) AC_CONFIG_HEADERS([src/include/citus_config.h] [src/include/citus_version.h])

26
src/backend/columnar/.gitattributes vendored Normal file
View File

@ -0,0 +1,26 @@
* whitespace=space-before-tab,trailing-space
*.[chly] whitespace=space-before-tab,trailing-space,indent-with-non-tab,tabwidth=4
*.dsl whitespace=space-before-tab,trailing-space,tab-in-indent
*.patch -whitespace
*.pl whitespace=space-before-tab,trailing-space,tabwidth=4
*.po whitespace=space-before-tab,trailing-space,tab-in-indent,-blank-at-eof
*.sgml whitespace=space-before-tab,trailing-space,tab-in-indent,-blank-at-eol
*.x[ms]l whitespace=space-before-tab,trailing-space,tab-in-indent
# Avoid confusing ASCII underlines with leftover merge conflict markers
README conflict-marker-size=32
README.* conflict-marker-size=32
# Certain data files that contain special whitespace, and other special cases
*.data -whitespace
# Test output files that contain extra whitespace
*.out -whitespace
src/test/regress/output/*.source -whitespace
# These files are maintained or generated elsewhere. We take them as is.
configure -whitespace
# all C files (implementation and header) use our style...
*.[ch] citus-style

68
src/backend/columnar/.gitignore vendored Normal file
View File

@ -0,0 +1,68 @@
# =====
# = C =
# =====
# Object files
*.o
*.ko
*.obj
*.elf
*.bc
# Libraries
*.lib
*.a
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.app
*.i*86
*.x86_64
*.hex
# ========
# = Gcov =
# ========
# gcc coverage testing tool files
*.gcno
*.gcda
*.gcov
# ====================
# = Project-Specific =
# ====================
/data/*.cstore
/data/*.footer
/sql/*block_filtering.sql
/sql/*copyto.sql
/sql/*create.sql
/sql/*data_types.sql
/sql/*load.sql
/expected/*block_filtering.out
/expected/*copyto.out
/expected/*create.out
/expected/*data_types.out
/expected/*load.out
/results/*
/.deps/*
/regression.diffs
/regression.out
.vscode
*.pb-c.*
# ignore files that could be created by circleci automation
files.lst
install-*.tar
install-*/

View File

@ -0,0 +1,101 @@
/*-------------------------------------------------------------------------
*
* cstore.c
*
* This file contains...
*
* Copyright (c) 2016, Citus Data, Inc.
*
* $Id$
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <sys/stat.h>
#include <unistd.h>
#include "miscadmin.h"
#include "utils/guc.h"
#include "utils/rel.h"
#include "columnar/cstore.h"
/* Default values for option parameters */
#define DEFAULT_COMPRESSION_TYPE COMPRESSION_NONE
#define DEFAULT_STRIPE_ROW_COUNT 150000
#define DEFAULT_BLOCK_ROW_COUNT 10000
int cstore_compression = DEFAULT_COMPRESSION_TYPE;
int cstore_stripe_row_count = DEFAULT_STRIPE_ROW_COUNT;
int cstore_block_row_count = DEFAULT_BLOCK_ROW_COUNT;
static const struct config_enum_entry cstore_compression_options[] =
{
{ "none", COMPRESSION_NONE, false },
{ "pglz", COMPRESSION_PG_LZ, false },
{ NULL, 0, false }
};
void
cstore_init()
{
DefineCustomEnumVariable("cstore.compression",
"Compression type for cstore.",
NULL,
&cstore_compression,
DEFAULT_COMPRESSION_TYPE,
cstore_compression_options,
PGC_USERSET,
0,
NULL,
NULL,
NULL);
DefineCustomIntVariable("cstore.stripe_row_count",
"Maximum number of tuples per stripe.",
NULL,
&cstore_stripe_row_count,
DEFAULT_STRIPE_ROW_COUNT,
STRIPE_ROW_COUNT_MINIMUM,
STRIPE_ROW_COUNT_MAXIMUM,
PGC_USERSET,
0,
NULL,
NULL,
NULL);
DefineCustomIntVariable("cstore.block_row_count",
"Maximum number of rows per block.",
NULL,
&cstore_block_row_count,
DEFAULT_BLOCK_ROW_COUNT,
BLOCK_ROW_COUNT_MINIMUM,
BLOCK_ROW_COUNT_MAXIMUM,
PGC_USERSET,
0,
NULL,
NULL,
NULL);
}
/* ParseCompressionType converts a string to a compression type. */
CompressionType
ParseCompressionType(const char *compressionTypeString)
{
CompressionType compressionType = COMPRESSION_TYPE_INVALID;
Assert(compressionTypeString != NULL);
if (strncmp(compressionTypeString, COMPRESSION_STRING_NONE, NAMEDATALEN) == 0)
{
compressionType = COMPRESSION_NONE;
}
else if (strncmp(compressionTypeString, COMPRESSION_STRING_PG_LZ, NAMEDATALEN) == 0)
{
compressionType = COMPRESSION_PG_LZ;
}
return compressionType;
}

View File

@ -0,0 +1,196 @@
/*-------------------------------------------------------------------------
*
* cstore_compression.c
*
* This file contains compression/decompression functions definitions
* used in cstore_fdw.
*
* Copyright (c) 2016, Citus Data, Inc.
*
* $Id$
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#if PG_VERSION_NUM >= 90500
#include "common/pg_lzcompress.h"
#else
#include "utils/pg_lzcompress.h"
#endif
#include "columnar/cstore.h"
#if PG_VERSION_NUM >= 90500
/*
* The information at the start of the compressed data. This decription is taken
* from pg_lzcompress in pre-9.5 version of PostgreSQL.
*/
typedef struct CStoreCompressHeader
{
int32 vl_len_; /* varlena header (do not touch directly!) */
int32 rawsize;
} CStoreCompressHeader;
/*
* Utilities for manipulation of header information for compressed data
*/
#define CSTORE_COMPRESS_HDRSZ ((int32) sizeof(CStoreCompressHeader))
#define CSTORE_COMPRESS_RAWSIZE(ptr) (((CStoreCompressHeader *) (ptr))->rawsize)
#define CSTORE_COMPRESS_RAWDATA(ptr) (((char *) (ptr)) + CSTORE_COMPRESS_HDRSZ)
#define CSTORE_COMPRESS_SET_RAWSIZE(ptr, len) (((CStoreCompressHeader *) (ptr))->rawsize = \
(len))
#else
#define CSTORE_COMPRESS_HDRSZ (0)
#define CSTORE_COMPRESS_RAWSIZE(ptr) (PGLZ_RAW_SIZE((PGLZ_Header *) buffer->data))
#define CSTORE_COMPRESS_RAWDATA(ptr) (((PGLZ_Header *) (ptr)))
#define CSTORE_COMPRESS_SET_RAWSIZE(ptr, len) (((CStoreCompressHeader *) (ptr))->rawsize = \
(len))
#endif
/*
* CompressBuffer compresses the given buffer with the given compression type
* outputBuffer enlarged to contain compressed data. The function returns true
* if compression is done, returns false if compression is not done.
* outputBuffer is valid only if the function returns true.
*/
bool
CompressBuffer(StringInfo inputBuffer, StringInfo outputBuffer,
CompressionType compressionType)
{
uint64 maximumLength = PGLZ_MAX_OUTPUT(inputBuffer->len) + CSTORE_COMPRESS_HDRSZ;
bool compressionResult = false;
#if PG_VERSION_NUM >= 90500
int32 compressedByteCount = 0;
#endif
if (compressionType != COMPRESSION_PG_LZ)
{
return false;
}
resetStringInfo(outputBuffer);
enlargeStringInfo(outputBuffer, maximumLength);
#if PG_VERSION_NUM >= 90500
compressedByteCount = pglz_compress((const char *) inputBuffer->data,
inputBuffer->len,
CSTORE_COMPRESS_RAWDATA(outputBuffer->data),
PGLZ_strategy_always);
if (compressedByteCount >= 0)
{
CSTORE_COMPRESS_SET_RAWSIZE(outputBuffer->data, inputBuffer->len);
SET_VARSIZE_COMPRESSED(outputBuffer->data,
compressedByteCount + CSTORE_COMPRESS_HDRSZ);
compressionResult = true;
}
#else
compressionResult = pglz_compress(inputBuffer->data, inputBuffer->len,
CSTORE_COMPRESS_RAWDATA(outputBuffer->data),
PGLZ_strategy_always);
#endif
if (compressionResult)
{
outputBuffer->len = VARSIZE(outputBuffer->data);
}
return compressionResult;
}
/*
* DecompressBuffer decompresses the given buffer with the given compression
* type. This function returns the buffer as-is when no compression is applied.
*/
StringInfo
DecompressBuffer(StringInfo buffer, CompressionType compressionType)
{
StringInfo decompressedBuffer = NULL;
Assert(compressionType == COMPRESSION_NONE || compressionType == COMPRESSION_PG_LZ);
if (compressionType == COMPRESSION_NONE)
{
/* in case of no compression, return buffer */
decompressedBuffer = buffer;
}
else if (compressionType == COMPRESSION_PG_LZ)
{
uint32 compressedDataSize = VARSIZE(buffer->data) - CSTORE_COMPRESS_HDRSZ;
uint32 decompressedDataSize = CSTORE_COMPRESS_RAWSIZE(buffer->data);
char *decompressedData = NULL;
#if PG_VERSION_NUM >= 90500
int32 decompressedByteCount = 0;
#endif
if (compressedDataSize + CSTORE_COMPRESS_HDRSZ != buffer->len)
{
ereport(ERROR, (errmsg("cannot decompress the buffer"),
errdetail("Expected %u bytes, but received %u bytes",
compressedDataSize, buffer->len)));
}
decompressedData = palloc0(decompressedDataSize);
#if PG_VERSION_NUM >= 90500
#if PG_VERSION_NUM >= 120000
decompressedByteCount = pglz_decompress(CSTORE_COMPRESS_RAWDATA(buffer->data),
compressedDataSize, decompressedData,
decompressedDataSize, true);
#else
decompressedByteCount = pglz_decompress(CSTORE_COMPRESS_RAWDATA(buffer->data),
compressedDataSize, decompressedData,
decompressedDataSize);
#endif
if (decompressedByteCount < 0)
{
ereport(ERROR, (errmsg("cannot decompress the buffer"),
errdetail("compressed data is corrupted")));
}
#else
pglz_decompress((PGLZ_Header *) buffer->data, decompressedData);
#endif
decompressedBuffer = palloc0(sizeof(StringInfoData));
decompressedBuffer->data = decompressedData;
decompressedBuffer->len = decompressedDataSize;
decompressedBuffer->maxlen = decompressedDataSize;
}
return decompressedBuffer;
}
/*
* CompressionTypeStr returns string representation of a compression type.
*/
char *
CompressionTypeStr(CompressionType type)
{
switch (type)
{
case COMPRESSION_NONE:
{
return "none";
}
case COMPRESSION_PG_LZ:
{
return "pglz";
}
default:
return "unknown";
}
}

View File

@ -0,0 +1,429 @@
/*-------------------------------------------------------------------------
*
* cstore_customscan.c
*
* This file contains the implementation of a postgres custom scan that
* we use to push down the projections into the table access methods.
*
* $Id$
*
*-------------------------------------------------------------------------
*/
#include "citus_version.h"
#if HAS_TABLEAM
#include "postgres.h"
#include "access/skey.h"
#include "nodes/extensible.h"
#include "nodes/pg_list.h"
#include "nodes/plannodes.h"
#include "optimizer/optimizer.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/restrictinfo.h"
#include "utils/relcache.h"
#include "columnar/cstore.h"
#include "columnar/cstore_customscan.h"
#include "columnar/cstore_tableam.h"
typedef struct CStoreScanPath
{
CustomPath custom_path;
/* place for local state during planning */
} CStoreScanPath;
typedef struct CStoreScanScan
{
CustomScan custom_scan;
/* place for local state during execution */
} CStoreScanScan;
typedef struct CStoreScanState
{
CustomScanState custom_scanstate;
List *qual;
} CStoreScanState;
static void CStoreSetRelPathlistHook(PlannerInfo *root, RelOptInfo *rel, Index rti,
RangeTblEntry *rte);
static Path * CreateCStoreScanPath(RelOptInfo *rel, RangeTblEntry *rte);
static Cost CStoreScanCost(RangeTblEntry *rte);
static Plan * CStoreScanPath_PlanCustomPath(PlannerInfo *root,
RelOptInfo *rel,
struct CustomPath *best_path,
List *tlist,
List *clauses,
List *custom_plans);
static Node * CStoreScan_CreateCustomScanState(CustomScan *cscan);
static void CStoreScan_BeginCustomScan(CustomScanState *node, EState *estate, int eflags);
static TupleTableSlot * CStoreScan_ExecCustomScan(CustomScanState *node);
static void CStoreScan_EndCustomScan(CustomScanState *node);
static void CStoreScan_ReScanCustomScan(CustomScanState *node);
/* saved hook value in case of unload */
static set_rel_pathlist_hook_type PreviousSetRelPathlistHook = NULL;
static bool EnableCStoreCustomScan = true;
const struct CustomPathMethods CStoreScanPathMethods = {
.CustomName = "CStoreScan",
.PlanCustomPath = CStoreScanPath_PlanCustomPath,
};
const struct CustomScanMethods CStoreScanScanMethods = {
.CustomName = "CStoreScan",
.CreateCustomScanState = CStoreScan_CreateCustomScanState,
};
const struct CustomExecMethods CStoreExecuteMethods = {
.CustomName = "CStoreScan",
.BeginCustomScan = CStoreScan_BeginCustomScan,
.ExecCustomScan = CStoreScan_ExecCustomScan,
.EndCustomScan = CStoreScan_EndCustomScan,
.ReScanCustomScan = CStoreScan_ReScanCustomScan,
.ExplainCustomScan = NULL,
};
/*
* cstore_customscan_init installs the hook required to intercept the postgres planner and
* provide extra paths for cstore tables
*/
void
cstore_customscan_init()
{
PreviousSetRelPathlistHook = set_rel_pathlist_hook;
set_rel_pathlist_hook = CStoreSetRelPathlistHook;
/* register customscan specific GUC's */
DefineCustomBoolVariable(
"cstore.enable_custom_scan",
gettext_noop("Enables the use of a custom scan to push projections and quals "
"into the storage layer"),
NULL,
&EnableCStoreCustomScan,
true,
PGC_USERSET,
GUC_NO_SHOW_ALL,
NULL, NULL, NULL);
}
static void
clear_paths(RelOptInfo *rel)
{
rel->pathlist = NULL;
rel->partial_pathlist = NULL;
rel->cheapest_startup_path = NULL;
rel->cheapest_total_path = NULL;
rel->cheapest_unique_path = NULL;
}
static void
CStoreSetRelPathlistHook(PlannerInfo *root, RelOptInfo *rel, Index rti,
RangeTblEntry *rte)
{
/* call into previous hook if assigned */
if (PreviousSetRelPathlistHook)
{
PreviousSetRelPathlistHook(root, rel, rti, rte);
}
if (!EnableCStoreCustomScan)
{
/* custon scans are disabled, use normal table access method api instead */
return;
}
if (!OidIsValid(rte->relid) || rte->rtekind != RTE_RELATION)
{
/* some calls to the pathlist hook don't have a valid relation set. Do nothing */
return;
}
/*
* Here we want to inspect if this relation pathlist hook is accessing a cstore table.
* If that is the case we want to insert an extra path that pushes down the projection
* into the scan of the table to minimize the data read.
*/
Relation relation = RelationIdGetRelation(rte->relid);
if (relation->rd_tableam == GetCstoreTableAmRoutine())
{
Path *customPath = CreateCStoreScanPath(rel, rte);
ereport(DEBUG1, (errmsg("pathlist hook for cstore table am")));
/* we propose a new path that will be the only path for scanning this relation */
clear_paths(rel);
add_path(rel, customPath);
}
RelationClose(relation);
}
static Path *
CreateCStoreScanPath(RelOptInfo *rel, RangeTblEntry *rte)
{
CStoreScanPath *cspath = (CStoreScanPath *) newNode(sizeof(CStoreScanPath),
T_CustomPath);
/*
* popuate custom path information
*/
CustomPath *cpath = &cspath->custom_path;
cpath->methods = &CStoreScanPathMethods;
/*
* populate generic path information
*/
Path *path = &cpath->path;
path->pathtype = T_CustomScan;
path->parent = rel;
path->pathtarget = rel->reltarget;
/*
* Add cost estimates for a cstore table scan, row count is the rows estimated by
* postgres' planner.
*/
path->rows = rel->rows;
path->startup_cost = 0;
path->total_cost = path->startup_cost + CStoreScanCost(rte);
return (Path *) cspath;
}
/*
* CStoreScanCost calculates the cost of scanning the cstore table. The cost is estimated
* by using all stripe metadata to estimate based on the columns to read how many pages
* need to be read.
*/
static Cost
CStoreScanCost(RangeTblEntry *rte)
{
Relation rel = RelationIdGetRelation(rte->relid);
DataFileMetadata *metadata = ReadDataFileMetadata(rel->rd_node.relNode, false);
uint32 maxColumnCount = 0;
uint64 totalStripeSize = 0;
ListCell *stripeMetadataCell = NULL;
RelationClose(rel);
rel = NULL;
foreach(stripeMetadataCell, metadata->stripeMetadataList)
{
StripeMetadata *stripeMetadata = (StripeMetadata *) lfirst(stripeMetadataCell);
totalStripeSize += stripeMetadata->dataLength;
maxColumnCount = Max(maxColumnCount, stripeMetadata->columnCount);
}
{
Bitmapset *attr_needed = rte->selectedCols;
double numberOfColumnsRead = bms_num_members(attr_needed);
double selectionRatio = numberOfColumnsRead / (double) maxColumnCount;
Cost scanCost = (double) totalStripeSize / BLCKSZ * selectionRatio;
return scanCost;
}
}
static Plan *
CStoreScanPath_PlanCustomPath(PlannerInfo *root,
RelOptInfo *rel,
struct CustomPath *best_path,
List *tlist,
List *clauses,
List *custom_plans)
{
CStoreScanScan *plan = (CStoreScanScan *) newNode(sizeof(CStoreScanScan),
T_CustomScan);
CustomScan *cscan = &plan->custom_scan;
cscan->methods = &CStoreScanScanMethods;
/* Reduce RestrictInfo list to bare expressions; ignore pseudoconstants */
clauses = extract_actual_clauses(clauses, false);
cscan->scan.plan.targetlist = list_copy(tlist);
cscan->scan.plan.qual = clauses;
cscan->scan.scanrelid = best_path->path.parent->relid;
return (Plan *) plan;
}
static Node *
CStoreScan_CreateCustomScanState(CustomScan *cscan)
{
CStoreScanState *cstorescanstate = (CStoreScanState *) newNode(
sizeof(CStoreScanState), T_CustomScanState);
CustomScanState *cscanstate = &cstorescanstate->custom_scanstate;
cscanstate->methods = &CStoreExecuteMethods;
cstorescanstate->qual = cscan->scan.plan.qual;
return (Node *) cscanstate;
}
static void
CStoreScan_BeginCustomScan(CustomScanState *cscanstate, EState *estate, int eflags)
{
/* scan slot is already initialized */
}
static Bitmapset *
CStoreAttrNeeded(ScanState *ss)
{
TupleTableSlot *slot = ss->ss_ScanTupleSlot;
int natts = slot->tts_tupleDescriptor->natts;
Bitmapset *attr_needed = NULL;
Plan *plan = ss->ps.plan;
int flags = PVC_RECURSE_AGGREGATES |
PVC_RECURSE_WINDOWFUNCS | PVC_RECURSE_PLACEHOLDERS;
List *vars = list_concat(pull_var_clause((Node *) plan->targetlist, flags),
pull_var_clause((Node *) plan->qual, flags));
ListCell *lc;
foreach(lc, vars)
{
Var *var = lfirst(lc);
if (var->varattno == 0)
{
elog(DEBUG1, "Need attribute: all");
/* all attributes are required, we don't need to add more so break*/
attr_needed = bms_add_range(attr_needed, 0, natts - 1);
break;
}
elog(DEBUG1, "Need attribute: %d", var->varattno);
attr_needed = bms_add_member(attr_needed, var->varattno - 1);
}
return attr_needed;
}
static TupleTableSlot *
CStoreScanNext(CStoreScanState *cstorescanstate)
{
CustomScanState *node = (CustomScanState *) cstorescanstate;
/*
* get information from the estate and scan state
*/
TableScanDesc scandesc = node->ss.ss_currentScanDesc;
EState *estate = node->ss.ps.state;
ScanDirection direction = estate->es_direction;
TupleTableSlot *slot = node->ss.ss_ScanTupleSlot;
if (scandesc == NULL)
{
/* the cstore access method does not use the flags, they are specific to heap */
uint32 flags = 0;
Bitmapset *attr_needed = CStoreAttrNeeded(&node->ss);
/*
* We reach here if the scan is not parallel, or if we're serially
* executing a scan that was planned to be parallel.
*/
scandesc = cstore_beginscan_extended(node->ss.ss_currentRelation,
estate->es_snapshot,
0, NULL, NULL, flags, attr_needed,
cstorescanstate->qual);
bms_free(attr_needed);
node->ss.ss_currentScanDesc = scandesc;
}
/*
* get the next tuple from the table
*/
if (table_scan_getnextslot(scandesc, direction, slot))
{
return slot;
}
return NULL;
}
/*
* SeqRecheck -- access method routine to recheck a tuple in EvalPlanQual
*/
static bool
CStoreScanRecheck(CStoreScanState *node, TupleTableSlot *slot)
{
return true;
}
static TupleTableSlot *
CStoreScan_ExecCustomScan(CustomScanState *node)
{
return ExecScan(&node->ss,
(ExecScanAccessMtd) CStoreScanNext,
(ExecScanRecheckMtd) CStoreScanRecheck);
}
static void
CStoreScan_EndCustomScan(CustomScanState *node)
{
/*
* get information from node
*/
TableScanDesc scanDesc = node->ss.ss_currentScanDesc;
/*
* Free the exprcontext
*/
ExecFreeExprContext(&node->ss.ps);
/*
* clean out the tuple table
*/
if (node->ss.ps.ps_ResultTupleSlot)
{
ExecClearTuple(node->ss.ps.ps_ResultTupleSlot);
}
ExecClearTuple(node->ss.ss_ScanTupleSlot);
/*
* close heap scan
*/
if (scanDesc != NULL)
{
table_endscan(scanDesc);
}
}
static void
CStoreScan_ReScanCustomScan(CustomScanState *node)
{
TableScanDesc scanDesc = node->ss.ss_currentScanDesc;
if (scanDesc != NULL)
{
table_rescan(node->ss.ss_currentScanDesc, NULL);
}
}
#endif /* HAS_TABLEAM */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,997 @@
/*-------------------------------------------------------------------------
*
* cstore_metadata_tables.c
*
* Copyright (c), Citus Data, Inc.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "safe_lib.h"
#include "columnar/cstore.h"
#include "columnar/cstore_version_compat.h"
#include <sys/stat.h>
#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
#include "access/xact.h"
#include "catalog/indexing.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_type.h"
#include "catalog/namespace.h"
#include "commands/defrem.h"
#include "commands/trigger.h"
#include "executor/executor.h"
#include "executor/spi.h"
#include "miscadmin.h"
#include "nodes/execnodes.h"
#include "lib/stringinfo.h"
#include "port.h"
#include "storage/fd.h"
#include "storage/lmgr.h"
#include "storage/smgr.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/memutils.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
typedef struct
{
Relation rel;
EState *estate;
} ModifyState;
static void InsertStripeMetadataRow(Oid relfilenode, StripeMetadata *stripe);
static void GetHighestUsedAddressAndId(Oid relfilenode,
uint64 *highestUsedAddress,
uint64 *highestUsedId);
static List * ReadDataFileStripeList(Oid relfilenode, Snapshot snapshot);
static Oid CStoreStripesRelationId(void);
static Oid CStoreStripesIndexRelationId(void);
static Oid CStoreDataFilesRelationId(void);
static Oid CStoreDataFilesIndexRelationId(void);
static Oid CStoreSkipNodesRelationId(void);
static Oid CStoreSkipNodesIndexRelationId(void);
static Oid CStoreNamespaceId(void);
static bool ReadCStoreDataFiles(Oid relfilenode, DataFileMetadata *metadata);
static ModifyState * StartModifyRelation(Relation rel);
static void InsertTupleAndEnforceConstraints(ModifyState *state, Datum *values,
bool *nulls);
static void DeleteTupleAndEnforceConstraints(ModifyState *state, HeapTuple heapTuple);
static void FinishModifyRelation(ModifyState *state);
static EState * create_estate_for_relation(Relation rel);
static bytea * DatumToBytea(Datum value, Form_pg_attribute attrForm);
static Datum ByteaToDatum(bytea *bytes, Form_pg_attribute attrForm);
/* constants for cstore_table */
#define Natts_cstore_data_files 6
#define Anum_cstore_data_files_relfilenode 1
#define Anum_cstore_data_files_block_row_count 2
#define Anum_cstore_data_files_stripe_row_count 3
#define Anum_cstore_data_files_compression 4
#define Anum_cstore_data_files_version_major 5
#define Anum_cstore_data_files_version_minor 6
/* ----------------
* cstore.cstore_data_files definition.
* ----------------
*/
typedef struct FormData_cstore_data_files
{
Oid relfilenode;
int32 block_row_count;
int32 stripe_row_count;
NameData compression;
int64 version_major;
int64 version_minor;
#ifdef CATALOG_VARLEN /* variable-length fields start here */
#endif
} FormData_cstore_data_files;
typedef FormData_cstore_data_files *Form_cstore_data_files;
/* constants for cstore_stripe */
#define Natts_cstore_stripes 8
#define Anum_cstore_stripes_relfilenode 1
#define Anum_cstore_stripes_stripe 2
#define Anum_cstore_stripes_file_offset 3
#define Anum_cstore_stripes_data_length 4
#define Anum_cstore_stripes_column_count 5
#define Anum_cstore_stripes_block_count 6
#define Anum_cstore_stripes_block_row_count 7
#define Anum_cstore_stripes_row_count 8
/* constants for cstore_skipnodes */
#define Natts_cstore_skipnodes 12
#define Anum_cstore_skipnodes_relfilenode 1
#define Anum_cstore_skipnodes_stripe 2
#define Anum_cstore_skipnodes_attr 3
#define Anum_cstore_skipnodes_block 4
#define Anum_cstore_skipnodes_row_count 5
#define Anum_cstore_skipnodes_minimum_value 6
#define Anum_cstore_skipnodes_maximum_value 7
#define Anum_cstore_skipnodes_value_stream_offset 8
#define Anum_cstore_skipnodes_value_stream_length 9
#define Anum_cstore_skipnodes_exists_stream_offset 10
#define Anum_cstore_skipnodes_exists_stream_length 11
#define Anum_cstore_skipnodes_value_compression_type 12
/*
* InitCStoreDataFileMetadata adds a record for the given relfilenode
* in cstore_data_files.
*/
void
InitCStoreDataFileMetadata(Oid relfilenode, int blockRowCount, int stripeRowCount,
CompressionType compression)
{
NameData compressionName = { 0 };
namestrcpy(&compressionName, CompressionTypeStr(compression));
bool nulls[Natts_cstore_data_files] = { 0 };
Datum values[Natts_cstore_data_files] = {
ObjectIdGetDatum(relfilenode),
Int32GetDatum(blockRowCount),
Int32GetDatum(stripeRowCount),
NameGetDatum(&compressionName),
Int32GetDatum(CSTORE_VERSION_MAJOR),
Int32GetDatum(CSTORE_VERSION_MINOR)
};
DeleteDataFileMetadataRowIfExists(relfilenode);
Oid cstoreDataFilesOid = CStoreDataFilesRelationId();
Relation cstoreDataFiles = heap_open(cstoreDataFilesOid, RowExclusiveLock);
ModifyState *modifyState = StartModifyRelation(cstoreDataFiles);
InsertTupleAndEnforceConstraints(modifyState, values, nulls);
FinishModifyRelation(modifyState);
CommandCounterIncrement();
heap_close(cstoreDataFiles, NoLock);
}
void
UpdateCStoreDataFileMetadata(Oid relfilenode, int blockRowCount, int stripeRowCount,
CompressionType compression)
{
const int scanKeyCount = 1;
ScanKeyData scanKey[1];
bool indexOK = true;
Datum values[Natts_cstore_data_files] = { 0 };
bool isnull[Natts_cstore_data_files] = { 0 };
bool replace[Natts_cstore_data_files] = { 0 };
Relation cstoreDataFiles = heap_open(CStoreDataFilesRelationId(), RowExclusiveLock);
TupleDesc tupleDescriptor = RelationGetDescr(cstoreDataFiles);
ScanKeyInit(&scanKey[0], Anum_cstore_data_files_relfilenode, BTEqualStrategyNumber,
F_INT8EQ, ObjectIdGetDatum(relfilenode));
SysScanDesc scanDescriptor = systable_beginscan(cstoreDataFiles,
CStoreDataFilesIndexRelationId(),
indexOK,
NULL, scanKeyCount, scanKey);
HeapTuple heapTuple = systable_getnext(scanDescriptor);
if (heapTuple == NULL)
{
ereport(ERROR, (errmsg("relfilenode %d doesn't belong to a cstore table",
relfilenode)));
}
Form_cstore_data_files metadata = (Form_cstore_data_files) GETSTRUCT(heapTuple);
bool changed = false;
if (metadata->block_row_count != blockRowCount)
{
values[Anum_cstore_data_files_block_row_count - 1] = Int32GetDatum(blockRowCount);
isnull[Anum_cstore_data_files_block_row_count - 1] = false;
replace[Anum_cstore_data_files_block_row_count - 1] = true;
changed = true;
}
if (metadata->stripe_row_count != stripeRowCount)
{
values[Anum_cstore_data_files_stripe_row_count - 1] = Int32GetDatum(
stripeRowCount);
isnull[Anum_cstore_data_files_stripe_row_count - 1] = false;
replace[Anum_cstore_data_files_stripe_row_count - 1] = true;
changed = true;
}
if (ParseCompressionType(NameStr(metadata->compression)) != compression)
{
Name compressionName = palloc0(sizeof(NameData));
namestrcpy(compressionName, CompressionTypeStr(compression));
values[Anum_cstore_data_files_compression - 1] = NameGetDatum(compressionName);
isnull[Anum_cstore_data_files_compression - 1] = false;
replace[Anum_cstore_data_files_compression - 1] = true;
changed = true;
}
if (changed)
{
heapTuple = heap_modify_tuple(heapTuple, tupleDescriptor, values, isnull,
replace);
CatalogTupleUpdate(cstoreDataFiles, &heapTuple->t_self, heapTuple);
CommandCounterIncrement();
}
systable_endscan(scanDescriptor);
heap_close(cstoreDataFiles, NoLock);
}
/*
* SaveStripeSkipList saves StripeSkipList for a given stripe as rows
* of cstore_skipnodes.
*/
void
SaveStripeSkipList(Oid relfilenode, uint64 stripe, StripeSkipList *stripeSkipList,
TupleDesc tupleDescriptor)
{
uint32 columnIndex = 0;
uint32 blockIndex = 0;
uint32 columnCount = stripeSkipList->columnCount;
Oid cstoreSkipNodesOid = CStoreSkipNodesRelationId();
Relation cstoreSkipNodes = heap_open(cstoreSkipNodesOid, RowExclusiveLock);
ModifyState *modifyState = StartModifyRelation(cstoreSkipNodes);
for (columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
for (blockIndex = 0; blockIndex < stripeSkipList->blockCount; blockIndex++)
{
ColumnBlockSkipNode *skipNode =
&stripeSkipList->blockSkipNodeArray[columnIndex][blockIndex];
Datum values[Natts_cstore_skipnodes] = {
ObjectIdGetDatum(relfilenode),
Int64GetDatum(stripe),
Int32GetDatum(columnIndex + 1),
Int32GetDatum(blockIndex),
Int64GetDatum(skipNode->rowCount),
0, /* to be filled below */
0, /* to be filled below */
Int64GetDatum(skipNode->valueBlockOffset),
Int64GetDatum(skipNode->valueLength),
Int64GetDatum(skipNode->existsBlockOffset),
Int64GetDatum(skipNode->existsLength),
Int32GetDatum(skipNode->valueCompressionType)
};
bool nulls[Natts_cstore_skipnodes] = { false };
if (skipNode->hasMinMax)
{
values[Anum_cstore_skipnodes_minimum_value - 1] =
PointerGetDatum(DatumToBytea(skipNode->minimumValue,
&tupleDescriptor->attrs[columnIndex]));
values[Anum_cstore_skipnodes_maximum_value - 1] =
PointerGetDatum(DatumToBytea(skipNode->maximumValue,
&tupleDescriptor->attrs[columnIndex]));
}
else
{
nulls[Anum_cstore_skipnodes_minimum_value - 1] = true;
nulls[Anum_cstore_skipnodes_maximum_value - 1] = true;
}
InsertTupleAndEnforceConstraints(modifyState, values, nulls);
}
}
FinishModifyRelation(modifyState);
heap_close(cstoreSkipNodes, NoLock);
CommandCounterIncrement();
}
/*
* ReadStripeSkipList fetches StripeSkipList for a given stripe.
*/
StripeSkipList *
ReadStripeSkipList(Oid relfilenode, uint64 stripe, TupleDesc tupleDescriptor,
uint32 blockCount)
{
int32 columnIndex = 0;
HeapTuple heapTuple = NULL;
uint32 columnCount = tupleDescriptor->natts;
ScanKeyData scanKey[2];
Oid cstoreSkipNodesOid = CStoreSkipNodesRelationId();
Relation cstoreSkipNodes = heap_open(cstoreSkipNodesOid, AccessShareLock);
Relation index = index_open(CStoreSkipNodesIndexRelationId(), AccessShareLock);
ScanKeyInit(&scanKey[0], Anum_cstore_skipnodes_relfilenode,
BTEqualStrategyNumber, F_OIDEQ, Int32GetDatum(relfilenode));
ScanKeyInit(&scanKey[1], Anum_cstore_skipnodes_stripe,
BTEqualStrategyNumber, F_OIDEQ, Int32GetDatum(stripe));
SysScanDesc scanDescriptor = systable_beginscan_ordered(cstoreSkipNodes, index, NULL,
2, scanKey);
StripeSkipList *skipList = palloc0(sizeof(StripeSkipList));
skipList->blockCount = blockCount;
skipList->columnCount = columnCount;
skipList->blockSkipNodeArray = palloc0(columnCount * sizeof(ColumnBlockSkipNode *));
for (columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
skipList->blockSkipNodeArray[columnIndex] =
palloc0(blockCount * sizeof(ColumnBlockSkipNode));
}
while (HeapTupleIsValid(heapTuple = systable_getnext(scanDescriptor)))
{
Datum datumArray[Natts_cstore_skipnodes];
bool isNullArray[Natts_cstore_skipnodes];
heap_deform_tuple(heapTuple, RelationGetDescr(cstoreSkipNodes), datumArray,
isNullArray);
int32 attr = DatumGetInt32(datumArray[Anum_cstore_skipnodes_attr - 1]);
int32 blockIndex = DatumGetInt32(datumArray[Anum_cstore_skipnodes_block - 1]);
if (attr <= 0 || attr > columnCount)
{
ereport(ERROR, (errmsg("invalid stripe skipnode entry"),
errdetail("Attribute number out of range: %d", attr)));
}
if (blockIndex < 0 || blockIndex >= blockCount)
{
ereport(ERROR, (errmsg("invalid stripe skipnode entry"),
errdetail("Block number out of range: %d", blockIndex)));
}
columnIndex = attr - 1;
ColumnBlockSkipNode *skipNode =
&skipList->blockSkipNodeArray[columnIndex][blockIndex];
skipNode->rowCount = DatumGetInt64(datumArray[Anum_cstore_skipnodes_row_count -
1]);
skipNode->valueBlockOffset =
DatumGetInt64(datumArray[Anum_cstore_skipnodes_value_stream_offset - 1]);
skipNode->valueLength =
DatumGetInt64(datumArray[Anum_cstore_skipnodes_value_stream_length - 1]);
skipNode->existsBlockOffset =
DatumGetInt64(datumArray[Anum_cstore_skipnodes_exists_stream_offset - 1]);
skipNode->existsLength =
DatumGetInt64(datumArray[Anum_cstore_skipnodes_exists_stream_length - 1]);
skipNode->valueCompressionType =
DatumGetInt32(datumArray[Anum_cstore_skipnodes_value_compression_type - 1]);
if (isNullArray[Anum_cstore_skipnodes_minimum_value - 1] ||
isNullArray[Anum_cstore_skipnodes_maximum_value - 1])
{
skipNode->hasMinMax = false;
}
else
{
bytea *minValue = DatumGetByteaP(
datumArray[Anum_cstore_skipnodes_minimum_value - 1]);
bytea *maxValue = DatumGetByteaP(
datumArray[Anum_cstore_skipnodes_maximum_value - 1]);
skipNode->minimumValue =
ByteaToDatum(minValue, &tupleDescriptor->attrs[columnIndex]);
skipNode->maximumValue =
ByteaToDatum(maxValue, &tupleDescriptor->attrs[columnIndex]);
skipNode->hasMinMax = true;
}
}
systable_endscan_ordered(scanDescriptor);
index_close(index, NoLock);
heap_close(cstoreSkipNodes, NoLock);
return skipList;
}
/*
* InsertStripeMetadataRow adds a row to cstore_stripes.
*/
static void
InsertStripeMetadataRow(Oid relfilenode, StripeMetadata *stripe)
{
bool nulls[Natts_cstore_stripes] = { 0 };
Datum values[Natts_cstore_stripes] = {
ObjectIdGetDatum(relfilenode),
Int64GetDatum(stripe->id),
Int64GetDatum(stripe->fileOffset),
Int64GetDatum(stripe->dataLength),
Int32GetDatum(stripe->columnCount),
Int32GetDatum(stripe->blockCount),
Int32GetDatum(stripe->blockRowCount),
Int64GetDatum(stripe->rowCount)
};
Oid cstoreStripesOid = CStoreStripesRelationId();
Relation cstoreStripes = heap_open(cstoreStripesOid, RowExclusiveLock);
ModifyState *modifyState = StartModifyRelation(cstoreStripes);
InsertTupleAndEnforceConstraints(modifyState, values, nulls);
FinishModifyRelation(modifyState);
CommandCounterIncrement();
heap_close(cstoreStripes, NoLock);
}
/*
* ReadDataFileMetadata constructs DataFileMetadata for a given relfilenode by reading
* from cstore_data_files and cstore_stripes.
*/
DataFileMetadata *
ReadDataFileMetadata(Oid relfilenode, bool missingOk)
{
DataFileMetadata *datafileMetadata = palloc0(sizeof(DataFileMetadata));
bool found = ReadCStoreDataFiles(relfilenode, datafileMetadata);
if (!found)
{
if (!missingOk)
{
ereport(ERROR, (errmsg("Relfilenode %d doesn't belong to a cstore table.",
relfilenode)));
}
else
{
return NULL;
}
}
datafileMetadata->stripeMetadataList =
ReadDataFileStripeList(relfilenode, GetTransactionSnapshot());
return datafileMetadata;
}
/*
* GetHighestUsedAddress returns the highest used address for the given
* relfilenode across all active and inactive transactions.
*/
uint64
GetHighestUsedAddress(Oid relfilenode)
{
uint64 highestUsedAddress = 0;
uint64 highestUsedId = 0;
GetHighestUsedAddressAndId(relfilenode, &highestUsedAddress, &highestUsedId);
return highestUsedAddress;
}
/*
* GetHighestUsedAddressAndId returns the highest used address and id for
* the given relfilenode across all active and inactive transactions.
*/
static void
GetHighestUsedAddressAndId(Oid relfilenode,
uint64 *highestUsedAddress,
uint64 *highestUsedId)
{
ListCell *stripeMetadataCell = NULL;
SnapshotData SnapshotDirty;
InitDirtySnapshot(SnapshotDirty);
List *stripeMetadataList = ReadDataFileStripeList(relfilenode, &SnapshotDirty);
*highestUsedId = 0;
*highestUsedAddress = 0;
foreach(stripeMetadataCell, stripeMetadataList)
{
StripeMetadata *stripe = lfirst(stripeMetadataCell);
uint64 lastByte = stripe->fileOffset + stripe->dataLength - 1;
*highestUsedAddress = Max(*highestUsedAddress, lastByte);
*highestUsedId = Max(*highestUsedId, stripe->id);
}
}
/*
* ReserveStripe reserves and stripe of given size for the given relation,
* and inserts it into cstore_stripes. It is guaranteed that concurrent
* writes won't overwrite the returned stripe.
*/
StripeMetadata
ReserveStripe(Relation rel, uint64 sizeBytes,
uint64 rowCount, uint64 columnCount,
uint64 blockCount, uint64 blockRowCount)
{
StripeMetadata stripe = { 0 };
uint64 currLogicalHigh = 0;
uint64 highestId = 0;
/*
* We take ShareUpdateExclusiveLock here, so two space
* reservations conflict, space reservation <-> vacuum
* conflict, but space reservation doesn't conflict with
* reads & writes.
*/
LockRelation(rel, ShareUpdateExclusiveLock);
Oid relfilenode = rel->rd_node.relNode;
GetHighestUsedAddressAndId(relfilenode, &currLogicalHigh, &highestId);
SmgrAddr currSmgrHigh = logical_to_smgr(currLogicalHigh);
SmgrAddr resSmgrStart = next_block_start(currSmgrHigh);
uint64 resLogicalStart = smgr_to_logical(resSmgrStart);
uint64 resLogicalEnd = resLogicalStart + sizeBytes - 1;
SmgrAddr resSmgrEnd = logical_to_smgr(resLogicalEnd);
RelationOpenSmgr(rel);
uint64 nblocks = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
while (resSmgrEnd.blockno >= nblocks)
{
Buffer newBuffer = ReadBuffer(rel, P_NEW);
ReleaseBuffer(newBuffer);
nblocks = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
}
RelationCloseSmgr(rel);
stripe.fileOffset = resLogicalStart;
stripe.dataLength = sizeBytes;
stripe.blockCount = blockCount;
stripe.blockRowCount = blockRowCount;
stripe.columnCount = columnCount;
stripe.rowCount = rowCount;
stripe.id = highestId + 1;
InsertStripeMetadataRow(relfilenode, &stripe);
UnlockRelation(rel, ShareUpdateExclusiveLock);
return stripe;
}
/*
* ReadDataFileStripeList reads the stripe list for a given relfilenode
* in the given snapshot.
*/
static List *
ReadDataFileStripeList(Oid relfilenode, Snapshot snapshot)
{
List *stripeMetadataList = NIL;
ScanKeyData scanKey[1];
HeapTuple heapTuple;
ScanKeyInit(&scanKey[0], Anum_cstore_stripes_relfilenode,
BTEqualStrategyNumber, F_OIDEQ, Int32GetDatum(relfilenode));
Oid cstoreStripesOid = CStoreStripesRelationId();
Relation cstoreStripes = heap_open(cstoreStripesOid, AccessShareLock);
Relation index = index_open(CStoreStripesIndexRelationId(), AccessShareLock);
TupleDesc tupleDescriptor = RelationGetDescr(cstoreStripes);
SysScanDesc scanDescriptor = systable_beginscan_ordered(cstoreStripes, index,
snapshot, 1,
scanKey);
while (HeapTupleIsValid(heapTuple = systable_getnext(scanDescriptor)))
{
Datum datumArray[Natts_cstore_stripes];
bool isNullArray[Natts_cstore_stripes];
heap_deform_tuple(heapTuple, tupleDescriptor, datumArray, isNullArray);
StripeMetadata *stripeMetadata = palloc0(sizeof(StripeMetadata));
stripeMetadata->id = DatumGetInt64(datumArray[Anum_cstore_stripes_stripe - 1]);
stripeMetadata->fileOffset = DatumGetInt64(
datumArray[Anum_cstore_stripes_file_offset - 1]);
stripeMetadata->dataLength = DatumGetInt64(
datumArray[Anum_cstore_stripes_data_length - 1]);
stripeMetadata->columnCount = DatumGetInt32(
datumArray[Anum_cstore_stripes_column_count - 1]);
stripeMetadata->blockCount = DatumGetInt32(
datumArray[Anum_cstore_stripes_block_count - 1]);
stripeMetadata->blockRowCount = DatumGetInt32(
datumArray[Anum_cstore_stripes_block_row_count - 1]);
stripeMetadata->rowCount = DatumGetInt64(
datumArray[Anum_cstore_stripes_row_count - 1]);
stripeMetadataList = lappend(stripeMetadataList, stripeMetadata);
}
systable_endscan_ordered(scanDescriptor);
index_close(index, NoLock);
heap_close(cstoreStripes, NoLock);
return stripeMetadataList;
}
/*
* ReadCStoreDataFiles reads corresponding record from cstore_data_files. Returns
* false if table was not found in cstore_data_files.
*/
static bool
ReadCStoreDataFiles(Oid relfilenode, DataFileMetadata *metadata)
{
bool found = false;
ScanKeyData scanKey[1];
ScanKeyInit(&scanKey[0], Anum_cstore_data_files_relfilenode,
BTEqualStrategyNumber, F_OIDEQ, Int32GetDatum(relfilenode));
Oid cstoreDataFilesOid = CStoreDataFilesRelationId();
Relation cstoreDataFiles = try_relation_open(cstoreDataFilesOid, AccessShareLock);
if (cstoreDataFiles == NULL)
{
/*
* Extension has been dropped. This can be called while
* dropping extension or database via ObjectAccess().
*/
return false;
}
Relation index = try_relation_open(CStoreDataFilesIndexRelationId(), AccessShareLock);
if (index == NULL)
{
heap_close(cstoreDataFiles, NoLock);
/* extension has been dropped */
return false;
}
TupleDesc tupleDescriptor = RelationGetDescr(cstoreDataFiles);
SysScanDesc scanDescriptor = systable_beginscan_ordered(cstoreDataFiles, index, NULL,
1, scanKey);
HeapTuple heapTuple = systable_getnext(scanDescriptor);
if (HeapTupleIsValid(heapTuple))
{
Datum datumArray[Natts_cstore_data_files];
bool isNullArray[Natts_cstore_data_files];
heap_deform_tuple(heapTuple, tupleDescriptor, datumArray, isNullArray);
if (metadata)
{
metadata->blockRowCount = DatumGetInt32(
datumArray[Anum_cstore_data_files_block_row_count - 1]);
metadata->stripeRowCount = DatumGetInt32(
datumArray[Anum_cstore_data_files_stripe_row_count - 1]);
Name compressionName = DatumGetName(
datumArray[Anum_cstore_data_files_compression - 1]);
metadata->compression = ParseCompressionType(NameStr(*compressionName));
}
found = true;
}
systable_endscan_ordered(scanDescriptor);
index_close(index, NoLock);
heap_close(cstoreDataFiles, NoLock);
return found;
}
/*
* DeleteDataFileMetadataRowIfExists removes the row with given relfilenode from cstore_stripes.
*/
void
DeleteDataFileMetadataRowIfExists(Oid relfilenode)
{
ScanKeyData scanKey[1];
/*
* During a restore for binary upgrade, metadata tables and indexes may or
* may not exist.
*/
if (IsBinaryUpgrade)
{
return;
}
ScanKeyInit(&scanKey[0], Anum_cstore_data_files_relfilenode,
BTEqualStrategyNumber, F_OIDEQ, Int32GetDatum(relfilenode));
Oid cstoreDataFilesOid = CStoreDataFilesRelationId();
Relation cstoreDataFiles = try_relation_open(cstoreDataFilesOid, AccessShareLock);
if (cstoreDataFiles == NULL)
{
/* extension has been dropped */
return;
}
Relation index = index_open(CStoreDataFilesIndexRelationId(), AccessShareLock);
SysScanDesc scanDescriptor = systable_beginscan_ordered(cstoreDataFiles, index, NULL,
1, scanKey);
HeapTuple heapTuple = systable_getnext(scanDescriptor);
if (HeapTupleIsValid(heapTuple))
{
ModifyState *modifyState = StartModifyRelation(cstoreDataFiles);
DeleteTupleAndEnforceConstraints(modifyState, heapTuple);
FinishModifyRelation(modifyState);
}
systable_endscan_ordered(scanDescriptor);
index_close(index, NoLock);
heap_close(cstoreDataFiles, NoLock);
}
/*
* StartModifyRelation allocates resources for modifications.
*/
static ModifyState *
StartModifyRelation(Relation rel)
{
EState *estate = create_estate_for_relation(rel);
/* ExecSimpleRelationInsert, ... require caller to open indexes */
ExecOpenIndices(estate->es_result_relation_info, false);
ModifyState *modifyState = palloc(sizeof(ModifyState));
modifyState->rel = rel;
modifyState->estate = estate;
return modifyState;
}
/*
* InsertTupleAndEnforceConstraints inserts a tuple into a relation and makes
* sure constraints are enforced and indexes are updated.
*/
static void
InsertTupleAndEnforceConstraints(ModifyState *state, Datum *values, bool *nulls)
{
TupleDesc tupleDescriptor = RelationGetDescr(state->rel);
HeapTuple tuple = heap_form_tuple(tupleDescriptor, values, nulls);
#if PG_VERSION_NUM >= 120000
TupleTableSlot *slot = ExecInitExtraTupleSlot(state->estate, tupleDescriptor,
&TTSOpsHeapTuple);
ExecStoreHeapTuple(tuple, slot, false);
#else
TupleTableSlot *slot = ExecInitExtraTupleSlot(state->estate, tupleDescriptor);
ExecStoreTuple(tuple, slot, InvalidBuffer, false);
#endif
/* use ExecSimpleRelationInsert to enforce constraints */
ExecSimpleRelationInsert(state->estate, slot);
}
/*
* DeleteTupleAndEnforceConstraints deletes a tuple from a relation and
* makes sure constraints (e.g. FK constraints) are enforced.
*/
static void
DeleteTupleAndEnforceConstraints(ModifyState *state, HeapTuple heapTuple)
{
EState *estate = state->estate;
ResultRelInfo *resultRelInfo = estate->es_result_relation_info;
ItemPointer tid = &(heapTuple->t_self);
simple_heap_delete(state->rel, tid);
/* execute AFTER ROW DELETE Triggers to enforce constraints */
ExecARDeleteTriggers(estate, resultRelInfo, tid, NULL, NULL);
}
/*
* FinishModifyRelation cleans up resources after modifications are done.
*/
static void
FinishModifyRelation(ModifyState *state)
{
ExecCloseIndices(state->estate->es_result_relation_info);
AfterTriggerEndQuery(state->estate);
ExecCleanUpTriggerState(state->estate);
ExecResetTupleTable(state->estate->es_tupleTable, false);
FreeExecutorState(state->estate);
}
/*
* Based on a similar function from
* postgres/src/backend/replication/logical/worker.c.
*
* Executor state preparation for evaluation of constraint expressions,
* indexes and triggers.
*
* This is based on similar code in copy.c
*/
static EState *
create_estate_for_relation(Relation rel)
{
ResultRelInfo *resultRelInfo;
EState *estate = CreateExecutorState();
RangeTblEntry *rte = makeNode(RangeTblEntry);
rte->rtekind = RTE_RELATION;
rte->relid = RelationGetRelid(rel);
rte->relkind = rel->rd_rel->relkind;
#if PG_VERSION_NUM >= 120000
rte->rellockmode = AccessShareLock;
ExecInitRangeTable(estate, list_make1(rte));
#endif
resultRelInfo = makeNode(ResultRelInfo);
InitResultRelInfo(resultRelInfo, rel, 1, NULL, 0);
estate->es_result_relations = resultRelInfo;
estate->es_num_result_relations = 1;
estate->es_result_relation_info = resultRelInfo;
estate->es_output_cid = GetCurrentCommandId(true);
#if PG_VERSION_NUM < 120000
/* Triggers might need a slot */
if (resultRelInfo->ri_TrigDesc)
{
estate->es_trig_tuple_slot = ExecInitExtraTupleSlot(estate, NULL);
}
#endif
/* Prepare to catch AFTER triggers. */
AfterTriggerBeginQuery();
return estate;
}
/*
* DatumToBytea serializes a datum into a bytea value.
*/
static bytea *
DatumToBytea(Datum value, Form_pg_attribute attrForm)
{
int datumLength = att_addlength_datum(0, attrForm->attlen, value);
bytea *result = palloc0(datumLength + VARHDRSZ);
SET_VARSIZE(result, datumLength + VARHDRSZ);
if (attrForm->attlen > 0)
{
if (attrForm->attbyval)
{
store_att_byval(VARDATA(result), value, attrForm->attlen);
}
else
{
memcpy_s(VARDATA(result), datumLength + VARHDRSZ,
DatumGetPointer(value), attrForm->attlen);
}
}
else
{
memcpy_s(VARDATA(result), datumLength + VARHDRSZ,
DatumGetPointer(value), datumLength);
}
return result;
}
/*
* ByteaToDatum deserializes a value which was previously serialized using
* DatumToBytea.
*/
static Datum
ByteaToDatum(bytea *bytes, Form_pg_attribute attrForm)
{
/*
* We copy the data so the result of this function lives even
* after the byteaDatum is freed.
*/
char *binaryDataCopy = palloc0(VARSIZE_ANY_EXHDR(bytes));
memcpy_s(binaryDataCopy, VARSIZE_ANY_EXHDR(bytes),
VARDATA_ANY(bytes), VARSIZE_ANY_EXHDR(bytes));
return fetch_att(binaryDataCopy, attrForm->attbyval, attrForm->attlen);
}
/*
* CStoreStripesRelationId returns relation id of cstore_stripes.
* TODO: should we cache this similar to citus?
*/
static Oid
CStoreStripesRelationId(void)
{
return get_relname_relid("cstore_stripes", CStoreNamespaceId());
}
/*
* CStoreStripesIndexRelationId returns relation id of cstore_stripes_idx.
* TODO: should we cache this similar to citus?
*/
static Oid
CStoreStripesIndexRelationId(void)
{
return get_relname_relid("cstore_stripes_pkey", CStoreNamespaceId());
}
/*
* CStoreDataFilesRelationId returns relation id of cstore_data_files.
* TODO: should we cache this similar to citus?
*/
static Oid
CStoreDataFilesRelationId(void)
{
return get_relname_relid("cstore_data_files", CStoreNamespaceId());
}
/*
* CStoreDataFilesIndexRelationId returns relation id of cstore_data_files_pkey.
* TODO: should we cache this similar to citus?
*/
static Oid
CStoreDataFilesIndexRelationId(void)
{
return get_relname_relid("cstore_data_files_pkey", CStoreNamespaceId());
}
/*
* CStoreSkipNodesRelationId returns relation id of cstore_skipnodes.
* TODO: should we cache this similar to citus?
*/
static Oid
CStoreSkipNodesRelationId(void)
{
return get_relname_relid("cstore_skipnodes", CStoreNamespaceId());
}
/*
* CStoreSkipNodesIndexRelationId returns relation id of cstore_skipnodes_pkey.
* TODO: should we cache this similar to citus?
*/
static Oid
CStoreSkipNodesIndexRelationId(void)
{
return get_relname_relid("cstore_skipnodes_pkey", CStoreNamespaceId());
}
/*
* CStoreNamespaceId returns namespace id of the schema we store cstore
* related tables.
*/
static Oid
CStoreNamespaceId(void)
{
return get_namespace_oid("cstore", false);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,749 @@
/*-------------------------------------------------------------------------
*
* cstore_writer.c
*
* This file contains function definitions for writing cstore files. This
* includes the logic for writing file level metadata, writing row stripes,
* and calculating block skip nodes.
*
* Copyright (c) 2016, Citus Data, Inc.
*
* $Id$
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "safe_lib.h"
#include "access/nbtree.h"
#include "catalog/pg_am.h"
#include "miscadmin.h"
#include "storage/fd.h"
#include "storage/smgr.h"
#include "utils/memutils.h"
#include "utils/rel.h"
#include "columnar/cstore.h"
#include "columnar/cstore_version_compat.h"
static StripeBuffers * CreateEmptyStripeBuffers(uint32 stripeMaxRowCount,
uint32 blockRowCount,
uint32 columnCount);
static StripeSkipList * CreateEmptyStripeSkipList(uint32 stripeMaxRowCount,
uint32 blockRowCount,
uint32 columnCount);
static void FlushStripe(TableWriteState *writeState);
static StringInfo SerializeBoolArray(bool *boolArray, uint32 boolArrayLength);
static void SerializeSingleDatum(StringInfo datumBuffer, Datum datum,
bool datumTypeByValue, int datumTypeLength,
char datumTypeAlign);
static void SerializeBlockData(TableWriteState *writeState, uint32 blockIndex,
uint32 rowCount);
static void UpdateBlockSkipNodeMinMax(ColumnBlockSkipNode *blockSkipNode,
Datum columnValue, bool columnTypeByValue,
int columnTypeLength, Oid columnCollation,
FmgrInfo *comparisonFunction);
static Datum DatumCopy(Datum datum, bool datumTypeByValue, int datumTypeLength);
static StringInfo CopyStringInfo(StringInfo sourceString);
/*
* CStoreBeginWrite initializes a cstore data load operation and returns a table
* handle. This handle should be used for adding the row values and finishing the
* data load operation. If the cstore footer file already exists, we read the
* footer and then seek to right after the last stripe where the new stripes
* will be added.
*/
TableWriteState *
CStoreBeginWrite(Relation relation,
CompressionType compressionType,
uint64 stripeMaxRowCount, uint32 blockRowCount,
TupleDesc tupleDescriptor)
{
uint32 columnIndex = 0;
/* get comparison function pointers for each of the columns */
uint32 columnCount = tupleDescriptor->natts;
FmgrInfo **comparisonFunctionArray = palloc0(columnCount * sizeof(FmgrInfo *));
for (columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
FmgrInfo *comparisonFunction = NULL;
FormData_pg_attribute *attributeForm = TupleDescAttr(tupleDescriptor,
columnIndex);
if (!attributeForm->attisdropped)
{
Oid typeId = attributeForm->atttypid;
comparisonFunction = GetFunctionInfoOrNull(typeId, BTREE_AM_OID,
BTORDER_PROC);
}
comparisonFunctionArray[columnIndex] = comparisonFunction;
}
/*
* We allocate all stripe specific data in the stripeWriteContext, and
* reset this memory context once we have flushed the stripe to the file.
* This is to avoid memory leaks.
*/
MemoryContext stripeWriteContext = AllocSetContextCreate(CurrentMemoryContext,
"Stripe Write Memory Context",
ALLOCSET_DEFAULT_SIZES);
bool *columnMaskArray = palloc(columnCount * sizeof(bool));
memset(columnMaskArray, true, columnCount);
BlockData *blockData = CreateEmptyBlockData(columnCount, columnMaskArray,
blockRowCount);
TableWriteState *writeState = palloc0(sizeof(TableWriteState));
writeState->relation = relation;
writeState->compressionType = compressionType;
writeState->stripeMaxRowCount = stripeMaxRowCount;
writeState->blockRowCount = blockRowCount;
writeState->tupleDescriptor = tupleDescriptor;
writeState->comparisonFunctionArray = comparisonFunctionArray;
writeState->stripeBuffers = NULL;
writeState->stripeSkipList = NULL;
writeState->stripeWriteContext = stripeWriteContext;
writeState->blockData = blockData;
writeState->compressionBuffer = NULL;
return writeState;
}
/*
* CStoreWriteRow adds a row to the cstore file. If the stripe is not initialized,
* we create structures to hold stripe data and skip list. Then, we serialize and
* append data to serialized value buffer for each of the columns and update
* corresponding skip nodes. Then, whole block data is compressed at every
* rowBlockCount insertion. Then, if row count exceeds stripeMaxRowCount, we flush
* the stripe, and add its metadata to the table footer.
*/
void
CStoreWriteRow(TableWriteState *writeState, Datum *columnValues, bool *columnNulls)
{
uint32 columnIndex = 0;
StripeBuffers *stripeBuffers = writeState->stripeBuffers;
StripeSkipList *stripeSkipList = writeState->stripeSkipList;
uint32 columnCount = writeState->tupleDescriptor->natts;
const uint32 blockRowCount = writeState->blockRowCount;
BlockData *blockData = writeState->blockData;
MemoryContext oldContext = MemoryContextSwitchTo(writeState->stripeWriteContext);
if (stripeBuffers == NULL)
{
stripeBuffers = CreateEmptyStripeBuffers(writeState->stripeMaxRowCount,
blockRowCount, columnCount);
stripeSkipList = CreateEmptyStripeSkipList(writeState->stripeMaxRowCount,
blockRowCount, columnCount);
writeState->stripeBuffers = stripeBuffers;
writeState->stripeSkipList = stripeSkipList;
writeState->compressionBuffer = makeStringInfo();
/*
* serializedValueBuffer lives in stripe write memory context so it needs to be
* initialized when the stripe is created.
*/
for (columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
blockData->valueBufferArray[columnIndex] = makeStringInfo();
}
}
uint32 blockIndex = stripeBuffers->rowCount / blockRowCount;
uint32 blockRowIndex = stripeBuffers->rowCount % blockRowCount;
for (columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
ColumnBlockSkipNode **blockSkipNodeArray = stripeSkipList->blockSkipNodeArray;
ColumnBlockSkipNode *blockSkipNode =
&blockSkipNodeArray[columnIndex][blockIndex];
if (columnNulls[columnIndex])
{
blockData->existsArray[columnIndex][blockRowIndex] = false;
}
else
{
FmgrInfo *comparisonFunction =
writeState->comparisonFunctionArray[columnIndex];
Form_pg_attribute attributeForm =
TupleDescAttr(writeState->tupleDescriptor, columnIndex);
bool columnTypeByValue = attributeForm->attbyval;
int columnTypeLength = attributeForm->attlen;
Oid columnCollation = attributeForm->attcollation;
char columnTypeAlign = attributeForm->attalign;
blockData->existsArray[columnIndex][blockRowIndex] = true;
SerializeSingleDatum(blockData->valueBufferArray[columnIndex],
columnValues[columnIndex], columnTypeByValue,
columnTypeLength, columnTypeAlign);
UpdateBlockSkipNodeMinMax(blockSkipNode, columnValues[columnIndex],
columnTypeByValue, columnTypeLength,
columnCollation, comparisonFunction);
}
blockSkipNode->rowCount++;
}
stripeSkipList->blockCount = blockIndex + 1;
/* last row of the block is inserted serialize the block */
if (blockRowIndex == blockRowCount - 1)
{
SerializeBlockData(writeState, blockIndex, blockRowCount);
}
stripeBuffers->rowCount++;
if (stripeBuffers->rowCount >= writeState->stripeMaxRowCount)
{
FlushStripe(writeState);
/* set stripe data and skip list to NULL so they are recreated next time */
writeState->stripeBuffers = NULL;
writeState->stripeSkipList = NULL;
}
MemoryContextSwitchTo(oldContext);
}
/*
* CStoreEndWrite finishes a cstore data load operation. If we have an unflushed
* stripe, we flush it. Then, we sync and close the cstore data file. Last, we
* flush the footer to a temporary file, and atomically rename this temporary
* file to the original footer file.
*/
void
CStoreEndWrite(TableWriteState *writeState)
{
StripeBuffers *stripeBuffers = writeState->stripeBuffers;
if (stripeBuffers != NULL)
{
MemoryContext oldContext = MemoryContextSwitchTo(writeState->stripeWriteContext);
FlushStripe(writeState);
MemoryContextReset(writeState->stripeWriteContext);
MemoryContextSwitchTo(oldContext);
}
MemoryContextDelete(writeState->stripeWriteContext);
pfree(writeState->comparisonFunctionArray);
FreeBlockData(writeState->blockData);
pfree(writeState);
}
/*
* CreateEmptyStripeBuffers allocates an empty StripeBuffers structure with the given
* column count.
*/
static StripeBuffers *
CreateEmptyStripeBuffers(uint32 stripeMaxRowCount, uint32 blockRowCount,
uint32 columnCount)
{
uint32 columnIndex = 0;
uint32 maxBlockCount = (stripeMaxRowCount / blockRowCount) + 1;
ColumnBuffers **columnBuffersArray = palloc0(columnCount * sizeof(ColumnBuffers *));
for (columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
uint32 blockIndex = 0;
ColumnBlockBuffers **blockBuffersArray =
palloc0(maxBlockCount * sizeof(ColumnBlockBuffers *));
for (blockIndex = 0; blockIndex < maxBlockCount; blockIndex++)
{
blockBuffersArray[blockIndex] = palloc0(sizeof(ColumnBlockBuffers));
blockBuffersArray[blockIndex]->existsBuffer = NULL;
blockBuffersArray[blockIndex]->valueBuffer = NULL;
blockBuffersArray[blockIndex]->valueCompressionType = COMPRESSION_NONE;
}
columnBuffersArray[columnIndex] = palloc0(sizeof(ColumnBuffers));
columnBuffersArray[columnIndex]->blockBuffersArray = blockBuffersArray;
}
StripeBuffers *stripeBuffers = palloc0(sizeof(StripeBuffers));
stripeBuffers->columnBuffersArray = columnBuffersArray;
stripeBuffers->columnCount = columnCount;
stripeBuffers->rowCount = 0;
return stripeBuffers;
}
/*
* CreateEmptyStripeSkipList allocates an empty StripeSkipList structure with
* the given column count. This structure has enough blocks to hold statistics
* for stripeMaxRowCount rows.
*/
static StripeSkipList *
CreateEmptyStripeSkipList(uint32 stripeMaxRowCount, uint32 blockRowCount,
uint32 columnCount)
{
uint32 columnIndex = 0;
uint32 maxBlockCount = (stripeMaxRowCount / blockRowCount) + 1;
ColumnBlockSkipNode **blockSkipNodeArray =
palloc0(columnCount * sizeof(ColumnBlockSkipNode *));
for (columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
blockSkipNodeArray[columnIndex] =
palloc0(maxBlockCount * sizeof(ColumnBlockSkipNode));
}
StripeSkipList *stripeSkipList = palloc0(sizeof(StripeSkipList));
stripeSkipList->columnCount = columnCount;
stripeSkipList->blockCount = 0;
stripeSkipList->blockSkipNodeArray = blockSkipNodeArray;
return stripeSkipList;
}
static void
WriteToSmgr(Relation rel, uint64 logicalOffset, char *data, uint32 dataLength)
{
uint64 remaining = dataLength;
Buffer buffer;
while (remaining > 0)
{
SmgrAddr addr = logical_to_smgr(logicalOffset);
RelationOpenSmgr(rel);
BlockNumber nblocks = smgrnblocks(rel->rd_smgr, MAIN_FORKNUM);
Assert(addr.blockno < nblocks);
(void) nblocks; /* keep compiler quiet */
RelationCloseSmgr(rel);
buffer = ReadBuffer(rel, addr.blockno);
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
Page page = BufferGetPage(buffer);
PageHeader phdr = (PageHeader) page;
if (PageIsNew(page))
{
PageInit(page, BLCKSZ, 0);
}
/*
* After a transaction has been rolled-back, we might be
* over-writing the rolledback write, so phdr->pd_lower can be
* different from addr.offset.
*
* We reset pd_lower to reset the rolledback write.
*/
if (phdr->pd_lower > addr.offset)
{
ereport(DEBUG1, (errmsg("over-writing page %u", addr.blockno),
errdetail("This can happen after a roll-back.")));
phdr->pd_lower = addr.offset;
}
Assert(phdr->pd_lower == addr.offset);
START_CRIT_SECTION();
uint64 to_write = Min(phdr->pd_upper - phdr->pd_lower, remaining);
memcpy_s(page + phdr->pd_lower, phdr->pd_upper - phdr->pd_lower, data, to_write);
phdr->pd_lower += to_write;
MarkBufferDirty(buffer);
if (RelationNeedsWAL(rel))
{
XLogBeginInsert();
/*
* Since cstore will mostly write whole pages we force the transmission of the
* whole image in the buffer
*/
XLogRegisterBuffer(0, buffer, REGBUF_FORCE_IMAGE);
XLogRecPtr recptr = XLogInsert(RM_GENERIC_ID, 0);
PageSetLSN(page, recptr);
}
END_CRIT_SECTION();
UnlockReleaseBuffer(buffer);
data += to_write;
remaining -= to_write;
logicalOffset += to_write;
}
}
/*
* FlushStripe flushes current stripe data into the file. The function first ensures
* the last data block for each column is properly serialized and compressed. Then,
* the function creates the skip list and footer buffers. Finally, the function
* flushes the skip list, data, and footer buffers to the file.
*/
static void
FlushStripe(TableWriteState *writeState)
{
StripeMetadata stripeMetadata = { 0 };
uint32 columnIndex = 0;
uint32 blockIndex = 0;
StripeBuffers *stripeBuffers = writeState->stripeBuffers;
StripeSkipList *stripeSkipList = writeState->stripeSkipList;
ColumnBlockSkipNode **columnSkipNodeArray = stripeSkipList->blockSkipNodeArray;
TupleDesc tupleDescriptor = writeState->tupleDescriptor;
uint32 columnCount = tupleDescriptor->natts;
uint32 blockCount = stripeSkipList->blockCount;
uint32 blockRowCount = writeState->blockRowCount;
uint32 lastBlockIndex = stripeBuffers->rowCount / blockRowCount;
uint32 lastBlockRowCount = stripeBuffers->rowCount % blockRowCount;
uint64 stripeSize = 0;
uint64 stripeRowCount = 0;
/*
* check if the last block needs serialization , the last block was not serialized
* if it was not full yet, e.g. (rowCount > 0)
*/
if (lastBlockRowCount > 0)
{
SerializeBlockData(writeState, lastBlockIndex, lastBlockRowCount);
}
/* update buffer sizes in stripe skip list */
for (columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
ColumnBlockSkipNode *blockSkipNodeArray = columnSkipNodeArray[columnIndex];
ColumnBuffers *columnBuffers = stripeBuffers->columnBuffersArray[columnIndex];
for (blockIndex = 0; blockIndex < blockCount; blockIndex++)
{
ColumnBlockBuffers *blockBuffers =
columnBuffers->blockBuffersArray[blockIndex];
uint64 existsBufferSize = blockBuffers->existsBuffer->len;
ColumnBlockSkipNode *blockSkipNode = &blockSkipNodeArray[blockIndex];
blockSkipNode->existsBlockOffset = stripeSize;
blockSkipNode->existsLength = existsBufferSize;
stripeSize += existsBufferSize;
}
for (blockIndex = 0; blockIndex < blockCount; blockIndex++)
{
ColumnBlockBuffers *blockBuffers =
columnBuffers->blockBuffersArray[blockIndex];
uint64 valueBufferSize = blockBuffers->valueBuffer->len;
CompressionType valueCompressionType = blockBuffers->valueCompressionType;
ColumnBlockSkipNode *blockSkipNode = &blockSkipNodeArray[blockIndex];
blockSkipNode->valueBlockOffset = stripeSize;
blockSkipNode->valueLength = valueBufferSize;
blockSkipNode->valueCompressionType = valueCompressionType;
stripeSize += valueBufferSize;
}
}
for (blockIndex = 0; blockIndex < blockCount; blockIndex++)
{
stripeRowCount +=
stripeSkipList->blockSkipNodeArray[0][blockIndex].rowCount;
}
stripeMetadata = ReserveStripe(writeState->relation, stripeSize,
stripeRowCount, columnCount, blockCount,
blockRowCount);
uint64 currentFileOffset = stripeMetadata.fileOffset;
/*
* Each stripe has only one section:
* Data section, in which we store data for each column continuously.
* We store data for each for each column in blocks. For each block, we
* store two buffers: "exists" buffer, and "value" buffer. "exists" buffer
* tells which values are not NULL. "value" buffer contains values for
* present values. For each column, we first store all "exists" buffers,
* and then all "value" buffers.
*/
/* flush the data buffers */
for (columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
ColumnBuffers *columnBuffers = stripeBuffers->columnBuffersArray[columnIndex];
for (blockIndex = 0; blockIndex < stripeSkipList->blockCount; blockIndex++)
{
ColumnBlockBuffers *blockBuffers =
columnBuffers->blockBuffersArray[blockIndex];
StringInfo existsBuffer = blockBuffers->existsBuffer;
WriteToSmgr(writeState->relation, currentFileOffset,
existsBuffer->data, existsBuffer->len);
currentFileOffset += existsBuffer->len;
}
for (blockIndex = 0; blockIndex < stripeSkipList->blockCount; blockIndex++)
{
ColumnBlockBuffers *blockBuffers =
columnBuffers->blockBuffersArray[blockIndex];
StringInfo valueBuffer = blockBuffers->valueBuffer;
WriteToSmgr(writeState->relation, currentFileOffset,
valueBuffer->data, valueBuffer->len);
currentFileOffset += valueBuffer->len;
}
}
/* create skip list and footer buffers */
SaveStripeSkipList(writeState->relation->rd_node.relNode,
stripeMetadata.id,
stripeSkipList, tupleDescriptor);
}
/*
* SerializeBoolArray serializes the given boolean array and returns the result
* as a StringInfo. This function packs every 8 boolean values into one byte.
*/
static StringInfo
SerializeBoolArray(bool *boolArray, uint32 boolArrayLength)
{
uint32 boolArrayIndex = 0;
uint32 byteCount = (boolArrayLength + 7) / 8;
StringInfo boolArrayBuffer = makeStringInfo();
enlargeStringInfo(boolArrayBuffer, byteCount);
boolArrayBuffer->len = byteCount;
memset(boolArrayBuffer->data, 0, byteCount);
for (boolArrayIndex = 0; boolArrayIndex < boolArrayLength; boolArrayIndex++)
{
if (boolArray[boolArrayIndex])
{
uint32 byteIndex = boolArrayIndex / 8;
uint32 bitIndex = boolArrayIndex % 8;
boolArrayBuffer->data[byteIndex] |= (1 << bitIndex);
}
}
return boolArrayBuffer;
}
/*
* SerializeSingleDatum serializes the given datum value and appends it to the
* provided string info buffer.
*/
static void
SerializeSingleDatum(StringInfo datumBuffer, Datum datum, bool datumTypeByValue,
int datumTypeLength, char datumTypeAlign)
{
uint32 datumLength = att_addlength_datum(0, datumTypeLength, datum);
uint32 datumLengthAligned = att_align_nominal(datumLength, datumTypeAlign);
enlargeStringInfo(datumBuffer, datumLengthAligned);
char *currentDatumDataPointer = datumBuffer->data + datumBuffer->len;
memset(currentDatumDataPointer, 0, datumLengthAligned);
if (datumTypeLength > 0)
{
if (datumTypeByValue)
{
store_att_byval(currentDatumDataPointer, datum, datumTypeLength);
}
else
{
memcpy_s(currentDatumDataPointer, datumBuffer->maxlen - datumBuffer->len,
DatumGetPointer(datum), datumTypeLength);
}
}
else
{
Assert(!datumTypeByValue);
memcpy_s(currentDatumDataPointer, datumBuffer->maxlen - datumBuffer->len,
DatumGetPointer(datum), datumLength);
}
datumBuffer->len += datumLengthAligned;
}
/*
* SerializeBlockData serializes and compresses block data at given block index with given
* compression type for every column.
*/
static void
SerializeBlockData(TableWriteState *writeState, uint32 blockIndex, uint32 rowCount)
{
uint32 columnIndex = 0;
StripeBuffers *stripeBuffers = writeState->stripeBuffers;
BlockData *blockData = writeState->blockData;
CompressionType requestedCompressionType = writeState->compressionType;
const uint32 columnCount = stripeBuffers->columnCount;
StringInfo compressionBuffer = writeState->compressionBuffer;
/* serialize exist values, data values are already serialized */
for (columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
ColumnBuffers *columnBuffers = stripeBuffers->columnBuffersArray[columnIndex];
ColumnBlockBuffers *blockBuffers = columnBuffers->blockBuffersArray[blockIndex];
blockBuffers->existsBuffer =
SerializeBoolArray(blockData->existsArray[columnIndex], rowCount);
}
/*
* check and compress value buffers, if a value buffer is not compressable
* then keep it as uncompressed, store compression information.
*/
for (columnIndex = 0; columnIndex < columnCount; columnIndex++)
{
ColumnBuffers *columnBuffers = stripeBuffers->columnBuffersArray[columnIndex];
ColumnBlockBuffers *blockBuffers = columnBuffers->blockBuffersArray[blockIndex];
CompressionType actualCompressionType = COMPRESSION_NONE;
StringInfo serializedValueBuffer = blockData->valueBufferArray[columnIndex];
/* the only other supported compression type is pg_lz for now */
Assert(requestedCompressionType == COMPRESSION_NONE ||
requestedCompressionType == COMPRESSION_PG_LZ);
/*
* if serializedValueBuffer is be compressed, update serializedValueBuffer
* with compressed data and store compression type.
*/
bool compressed = CompressBuffer(serializedValueBuffer, compressionBuffer,
requestedCompressionType);
if (compressed)
{
serializedValueBuffer = compressionBuffer;
actualCompressionType = COMPRESSION_PG_LZ;
}
/* store (compressed) value buffer */
blockBuffers->valueCompressionType = actualCompressionType;
blockBuffers->valueBuffer = CopyStringInfo(serializedValueBuffer);
/* valueBuffer needs to be reset for next block's data */
resetStringInfo(blockData->valueBufferArray[columnIndex]);
}
}
/*
* UpdateBlockSkipNodeMinMax takes the given column value, and checks if this
* value falls outside the range of minimum/maximum values of the given column
* block skip node. If it does, the function updates the column block skip node
* accordingly.
*/
static void
UpdateBlockSkipNodeMinMax(ColumnBlockSkipNode *blockSkipNode, Datum columnValue,
bool columnTypeByValue, int columnTypeLength,
Oid columnCollation, FmgrInfo *comparisonFunction)
{
bool hasMinMax = blockSkipNode->hasMinMax;
Datum previousMinimum = blockSkipNode->minimumValue;
Datum previousMaximum = blockSkipNode->maximumValue;
Datum currentMinimum = 0;
Datum currentMaximum = 0;
/* if type doesn't have a comparison function, skip min/max values */
if (comparisonFunction == NULL)
{
return;
}
if (!hasMinMax)
{
currentMinimum = DatumCopy(columnValue, columnTypeByValue, columnTypeLength);
currentMaximum = DatumCopy(columnValue, columnTypeByValue, columnTypeLength);
}
else
{
Datum minimumComparisonDatum = FunctionCall2Coll(comparisonFunction,
columnCollation, columnValue,
previousMinimum);
Datum maximumComparisonDatum = FunctionCall2Coll(comparisonFunction,
columnCollation, columnValue,
previousMaximum);
int minimumComparison = DatumGetInt32(minimumComparisonDatum);
int maximumComparison = DatumGetInt32(maximumComparisonDatum);
if (minimumComparison < 0)
{
currentMinimum = DatumCopy(columnValue, columnTypeByValue, columnTypeLength);
}
else
{
currentMinimum = previousMinimum;
}
if (maximumComparison > 0)
{
currentMaximum = DatumCopy(columnValue, columnTypeByValue, columnTypeLength);
}
else
{
currentMaximum = previousMaximum;
}
}
blockSkipNode->hasMinMax = true;
blockSkipNode->minimumValue = currentMinimum;
blockSkipNode->maximumValue = currentMaximum;
}
/* Creates a copy of the given datum. */
static Datum
DatumCopy(Datum datum, bool datumTypeByValue, int datumTypeLength)
{
Datum datumCopy = 0;
if (datumTypeByValue)
{
datumCopy = datum;
}
else
{
uint32 datumLength = att_addlength_datum(0, datumTypeLength, datum);
char *datumData = palloc0(datumLength);
memcpy_s(datumData, datumLength, DatumGetPointer(datum), datumLength);
datumCopy = PointerGetDatum(datumData);
}
return datumCopy;
}
/*
* CopyStringInfo creates a deep copy of given source string allocating only needed
* amount of memory.
*/
static StringInfo
CopyStringInfo(StringInfo sourceString)
{
StringInfo targetString = palloc0(sizeof(StringInfoData));
if (sourceString->len > 0)
{
targetString->data = palloc0(sourceString->len);
targetString->len = sourceString->len;
targetString->maxlen = sourceString->len;
memcpy_s(targetString->data, sourceString->len,
sourceString->data, sourceString->len);
}
return targetString;
}

View File

@ -0,0 +1,49 @@
/*-------------------------------------------------------------------------
*
* mod.c
*
* This file contains module-level definitions.
*
* Copyright (c) 2016, Citus Data, Inc.
*
* $Id$
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "fmgr.h"
#include "citus_version.h"
#include "columnar/cstore.h"
#include "columnar/cstore_fdw.h"
#include "columnar/mod.h"
#ifdef HAS_TABLEAM
#include "columnar/cstore_tableam.h"
#endif
void
columnar_init(void)
{
cstore_init();
cstore_fdw_init();
#ifdef HAS_TABLEAM
cstore_tableam_init();
#endif
}
void
columnar_fini(void)
{
cstore_fdw_finish();
#if HAS_TABLEAM
cstore_tableam_finish();
#endif
}

View File

@ -0,0 +1,107 @@
/* columnar--9.5-1--10.0-1.sql */
CREATE SCHEMA cstore;
SET search_path TO cstore;
CREATE FUNCTION cstore_fdw_handler()
RETURNS fdw_handler
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;
CREATE FUNCTION cstore_fdw_validator(text[], oid)
RETURNS void
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;
CREATE FOREIGN DATA WRAPPER cstore_fdw
HANDLER cstore_fdw_handler
VALIDATOR cstore_fdw_validator;
CREATE FUNCTION cstore_ddl_event_end_trigger()
RETURNS event_trigger
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;
CREATE EVENT TRIGGER cstore_ddl_event_end
ON ddl_command_end
EXECUTE PROCEDURE cstore_ddl_event_end_trigger();
CREATE FUNCTION pg_catalog.cstore_table_size(relation regclass)
RETURNS bigint
AS 'MODULE_PATHNAME'
LANGUAGE C STRICT;
CREATE TABLE cstore_data_files (
relfilenode oid NOT NULL,
block_row_count int NOT NULL,
stripe_row_count int NOT NULL,
compression name NOT NULL,
version_major bigint NOT NULL,
version_minor bigint NOT NULL,
PRIMARY KEY (relfilenode)
) WITH (user_catalog_table = true);
COMMENT ON TABLE cstore_data_files IS 'CStore data file wide metadata';
CREATE TABLE cstore_stripes (
relfilenode oid NOT NULL,
stripe bigint NOT NULL,
file_offset bigint NOT NULL,
data_length bigint NOT NULL,
column_count int NOT NULL,
block_count int NOT NULL,
block_row_count int NOT NULL,
row_count bigint NOT NULL,
PRIMARY KEY (relfilenode, stripe),
FOREIGN KEY (relfilenode) REFERENCES cstore_data_files(relfilenode) ON DELETE CASCADE INITIALLY DEFERRED
) WITH (user_catalog_table = true);
COMMENT ON TABLE cstore_stripes IS 'CStore per stripe metadata';
CREATE TABLE cstore_skipnodes (
relfilenode oid NOT NULL,
stripe bigint NOT NULL,
attr int NOT NULL,
block int NOT NULL,
row_count bigint NOT NULL,
minimum_value bytea,
maximum_value bytea,
value_stream_offset bigint NOT NULL,
value_stream_length bigint NOT NULL,
exists_stream_offset bigint NOT NULL,
exists_stream_length bigint NOT NULL,
value_compression_type int NOT NULL,
PRIMARY KEY (relfilenode, stripe, attr, block),
FOREIGN KEY (relfilenode, stripe) REFERENCES cstore_stripes(relfilenode, stripe) ON DELETE CASCADE INITIALLY DEFERRED
) WITH (user_catalog_table = true);
COMMENT ON TABLE cstore_skipnodes IS 'CStore per block metadata';
CREATE VIEW cstore_options AS
SELECT c.oid::regclass regclass,
d.block_row_count,
d.stripe_row_count,
d.compression
FROM pg_class c
JOIN cstore.cstore_data_files d USING(relfilenode);
COMMENT ON VIEW cstore_options IS 'CStore per table settings';
DO $proc$
BEGIN
-- from version 12 and up we have support for tableam's if installed on pg11 we can't
-- create the objects here. Instead we rely on citus_finish_pg_upgrade to be called by the
-- user instead to add the missing objects
IF substring(current_Setting('server_version'), '\d+')::int >= 12 THEN
EXECUTE $$
#include "udfs/cstore_tableam_handler/10.0-1.sql"
#include "udfs/alter_cstore_table_set/10.0-1.sql"
#include "udfs/alter_cstore_table_reset/10.0-1.sql"
$$;
END IF;
END$proc$;
#include "udfs/cstore_ensure_objects_exist/10.0-1.sql"
RESET search_path;

View File

@ -0,0 +1,47 @@
/* columnar--10.0-1--9.5-1.sql */
SET search_path TO cstore;
DO $proc$
BEGIN
IF substring(current_Setting('server_version'), '\d+')::int >= 12 THEN
EXECUTE $$
DROP FUNCTION pg_catalog.alter_cstore_table_reset(
table_name regclass,
block_row_count bool,
stripe_row_count bool,
compression bool);
DROP FUNCTION pg_catalog.alter_cstore_table_set(
table_name regclass,
block_row_count int,
stripe_row_count int,
compression name);
DROP ACCESS METHOD cstore_tableam;
DROP FUNCTION cstore_tableam_handler(internal);
$$;
END IF;
END$proc$;
DROP VIEW cstore_options;
DROP TABLE cstore_skipnodes;
DROP TABLE cstore_stripes;
DROP TABLE cstore_data_files;
DROP FUNCTION pg_catalog.cstore_table_size(relation regclass);
DROP EVENT TRIGGER cstore_ddl_event_end;
DROP FUNCTION cstore_ddl_event_end_trigger();
DROP FOREIGN DATA WRAPPER cstore_fdw;
DROP FUNCTION cstore_fdw_validator(text[], oid);
DROP FUNCTION cstore_fdw_handler();
DROP FUNCTION citus_internal.cstore_ensure_objects_exist();
RESET search_path;
DROP SCHEMA cstore;

View File

@ -0,0 +1,15 @@
CREATE OR REPLACE FUNCTION pg_catalog.alter_cstore_table_reset(
table_name regclass,
block_row_count bool DEFAULT false,
stripe_row_count bool DEFAULT false,
compression bool DEFAULT false)
RETURNS void
LANGUAGE C
AS 'MODULE_PATHNAME', 'alter_cstore_table_reset';
COMMENT ON FUNCTION pg_catalog.alter_cstore_table_reset(
table_name regclass,
block_row_count bool,
stripe_row_count bool,
compression bool)
IS 'reset on or more options on a cstore table to the system defaults';

View File

@ -0,0 +1,15 @@
CREATE OR REPLACE FUNCTION pg_catalog.alter_cstore_table_reset(
table_name regclass,
block_row_count bool DEFAULT false,
stripe_row_count bool DEFAULT false,
compression bool DEFAULT false)
RETURNS void
LANGUAGE C
AS 'MODULE_PATHNAME', 'alter_cstore_table_reset';
COMMENT ON FUNCTION pg_catalog.alter_cstore_table_reset(
table_name regclass,
block_row_count bool,
stripe_row_count bool,
compression bool)
IS 'reset on or more options on a cstore table to the system defaults';

View File

@ -0,0 +1,15 @@
CREATE OR REPLACE FUNCTION pg_catalog.alter_cstore_table_set(
table_name regclass,
block_row_count int DEFAULT NULL,
stripe_row_count int DEFAULT NULL,
compression name DEFAULT null)
RETURNS void
LANGUAGE C
AS 'MODULE_PATHNAME', 'alter_cstore_table_set';
COMMENT ON FUNCTION pg_catalog.alter_cstore_table_set(
table_name regclass,
block_row_count int,
stripe_row_count int,
compression name)
IS 'set one or more options on a cstore table, when set to NULL no change is made';

View File

@ -0,0 +1,15 @@
CREATE OR REPLACE FUNCTION pg_catalog.alter_cstore_table_set(
table_name regclass,
block_row_count int DEFAULT NULL,
stripe_row_count int DEFAULT NULL,
compression name DEFAULT null)
RETURNS void
LANGUAGE C
AS 'MODULE_PATHNAME', 'alter_cstore_table_set';
COMMENT ON FUNCTION pg_catalog.alter_cstore_table_set(
table_name regclass,
block_row_count int,
stripe_row_count int,
compression name)
IS 'set one or more options on a cstore table, when set to NULL no change is made';

View File

@ -0,0 +1,47 @@
-- citus_internal.cstore_ensure_objects_exist is an internal helper function to create
-- missing objects, anything related to the table access methods.
-- Since the API for table access methods is only available in PG12 we can't create these
-- objects when Citus is installed in PG11. Once citus is installed on PG11 the user can
-- upgrade their database to PG12. Now they require the table access method objects that
-- we couldn't create before.
-- This internal function is called from `citus_finish_pg_upgrade` which the user is
-- required to call after a PG major upgrade.
CREATE OR REPLACE FUNCTION citus_internal.cstore_ensure_objects_exist()
RETURNS void
LANGUAGE plpgsql
SET search_path = pg_catalog
AS $ceoe$
BEGIN
-- when postgres is version 12 or above we need to create the tableam. If the tableam
-- exist we assume all objects have been created.
IF substring(current_Setting('server_version'), '\d+')::int >= 12 THEN
IF NOT EXISTS (SELECT 1 FROM pg_am WHERE amname = 'cstore_tableam') THEN
#include "../cstore_tableam_handler/10.0-1.sql"
#include "../alter_cstore_table_set/10.0-1.sql"
#include "../alter_cstore_table_reset/10.0-1.sql"
-- add the missing objects to the extension
ALTER EXTENSION citus ADD FUNCTION cstore.cstore_tableam_handler(internal);
ALTER EXTENSION citus ADD ACCESS METHOD cstore_tableam;
ALTER EXTENSION citus ADD FUNCTION pg_catalog.alter_cstore_table_set(
table_name regclass,
block_row_count int,
stripe_row_count int,
compression name);
ALTER EXTENSION citus ADD FUNCTION pg_catalog.alter_cstore_table_reset(
table_name regclass,
block_row_count bool,
stripe_row_count bool,
compression bool);
END IF;
END IF;
END;
$ceoe$;
COMMENT ON FUNCTION citus_internal.cstore_ensure_objects_exist()
IS 'internal function to be called by pg_catalog.citus_finish_pg_upgrade responsible for creating the columnar objects';

View File

@ -0,0 +1,47 @@
-- citus_internal.cstore_ensure_objects_exist is an internal helper function to create
-- missing objects, anything related to the table access methods.
-- Since the API for table access methods is only available in PG12 we can't create these
-- objects when Citus is installed in PG11. Once citus is installed on PG11 the user can
-- upgrade their database to PG12. Now they require the table access method objects that
-- we couldn't create before.
-- This internal function is called from `citus_finish_pg_upgrade` which the user is
-- required to call after a PG major upgrade.
CREATE OR REPLACE FUNCTION citus_internal.cstore_ensure_objects_exist()
RETURNS void
LANGUAGE plpgsql
SET search_path = pg_catalog
AS $ceoe$
BEGIN
-- when postgres is version 12 or above we need to create the tableam. If the tableam
-- exist we assume all objects have been created.
IF substring(current_Setting('server_version'), '\d+')::int >= 12 THEN
IF NOT EXISTS (SELECT 1 FROM pg_am WHERE amname = 'cstore_tableam') THEN
#include "../cstore_tableam_handler/10.0-1.sql"
#include "../alter_cstore_table_set/10.0-1.sql"
#include "../alter_cstore_table_reset/10.0-1.sql"
-- add the missing objects to the extension
ALTER EXTENSION citus ADD FUNCTION cstore.cstore_tableam_handler(internal);
ALTER EXTENSION citus ADD ACCESS METHOD cstore_tableam;
ALTER EXTENSION citus ADD FUNCTION pg_catalog.alter_cstore_table_set(
table_name regclass,
block_row_count int,
stripe_row_count int,
compression name);
ALTER EXTENSION citus ADD FUNCTION pg_catalog.alter_cstore_table_reset(
table_name regclass,
block_row_count bool,
stripe_row_count bool,
compression bool);
END IF;
END IF;
END;
$ceoe$;
COMMENT ON FUNCTION citus_internal.cstore_ensure_objects_exist()
IS 'internal function to be called by pg_catalog.citus_finish_pg_upgrade responsible for creating the columnar objects';

View File

@ -0,0 +1,9 @@
CREATE OR REPLACE FUNCTION cstore.cstore_tableam_handler(internal)
RETURNS table_am_handler
LANGUAGE C
AS 'MODULE_PATHNAME', 'cstore_tableam_handler';
COMMENT ON FUNCTION cstore.cstore_tableam_handler(internal)
IS 'internal function returning the handler for cstore tables';
CREATE ACCESS METHOD cstore_tableam TYPE TABLE HANDLER cstore.cstore_tableam_handler;

View File

@ -0,0 +1,9 @@
CREATE OR REPLACE FUNCTION cstore.cstore_tableam_handler(internal)
RETURNS table_am_handler
LANGUAGE C
AS 'MODULE_PATHNAME', 'cstore_tableam_handler';
COMMENT ON FUNCTION cstore.cstore_tableam_handler(internal)
IS 'internal function returning the handler for cstore tables';
CREATE ACCESS METHOD cstore_tableam TYPE TABLE HANDLER cstore.cstore_tableam_handler;

View File

@ -19,6 +19,8 @@ DATA_built = $(generated_sql_files)
# directories with source files # directories with source files
SUBDIRS = . commands connection ddl deparser executor metadata operations planner progress relay safeclib test transaction utils worker SUBDIRS = . commands connection ddl deparser executor metadata operations planner progress relay safeclib test transaction utils worker
# columnar modules
SUBDIRS += ../columnar
# enterprise modules # enterprise modules
SUBDIRS += SUBDIRS +=

View File

@ -84,6 +84,8 @@
#include "utils/guc_tables.h" #include "utils/guc_tables.h"
#include "utils/varlena.h" #include "utils/varlena.h"
#include "columnar/mod.h"
/* marks shared object as one loadable by the postgres version compiled against */ /* marks shared object as one loadable by the postgres version compiled against */
PG_MODULE_MAGIC; PG_MODULE_MAGIC;
@ -92,6 +94,7 @@ static char *CitusVersion = CITUS_VERSION;
void _PG_init(void); void _PG_init(void);
void _PG_fini(void);
static void DoInitialCleanup(void); static void DoInitialCleanup(void);
static void ResizeStackToMaximumDepth(void); static void ResizeStackToMaximumDepth(void);
@ -311,6 +314,15 @@ _PG_init(void)
{ {
DoInitialCleanup(); DoInitialCleanup();
} }
columnar_init();
}
/* shared library deconstruction function */
void
_PG_fini(void)
{
columnar_fini();
} }

View File

@ -1,3 +1,7 @@
-- citus--9.5-1--10.0-1 -- citus--9.5-1--10.0-1
-- bump version to 10.0-1 -- bump version to 10.0-1
#include "udfs/citus_finish_pg_upgrade/10.0-1.sql"
#include "../../columnar/sql/columnar--9.5-1--10.0-1.sql"

View File

@ -1,2 +1,6 @@
-- citus--10.0-1--9.5-1 -- citus--10.0-1--9.5-1
-- this is an empty downgrade path since citus--9.5-1--10.0-1.sql is empty for now -- this is an empty downgrade path since citus--9.5-1--10.0-1.sql is empty for now
#include "../udfs/citus_finish_pg_upgrade/9.5-1.sql"
#include "../../../columnar/sql/downgrades/columnar--10.0-1--9.5-1.sql"

View File

@ -0,0 +1,116 @@
CREATE OR REPLACE FUNCTION pg_catalog.citus_finish_pg_upgrade()
RETURNS void
LANGUAGE plpgsql
SET search_path = pg_catalog
AS $cppu$
DECLARE
table_name regclass;
command text;
trigger_name text;
BEGIN
--
-- restore citus catalog tables
--
INSERT INTO pg_catalog.pg_dist_partition SELECT * FROM public.pg_dist_partition;
INSERT INTO pg_catalog.pg_dist_shard SELECT * FROM public.pg_dist_shard;
INSERT INTO pg_catalog.pg_dist_placement SELECT * FROM public.pg_dist_placement;
INSERT INTO pg_catalog.pg_dist_node_metadata SELECT * FROM public.pg_dist_node_metadata;
INSERT INTO pg_catalog.pg_dist_node SELECT * FROM public.pg_dist_node;
INSERT INTO pg_catalog.pg_dist_local_group SELECT * FROM public.pg_dist_local_group;
INSERT INTO pg_catalog.pg_dist_transaction SELECT * FROM public.pg_dist_transaction;
INSERT INTO pg_catalog.pg_dist_colocation SELECT * FROM public.pg_dist_colocation;
-- enterprise catalog tables
INSERT INTO pg_catalog.pg_dist_authinfo SELECT * FROM public.pg_dist_authinfo;
INSERT INTO pg_catalog.pg_dist_poolinfo SELECT * FROM public.pg_dist_poolinfo;
ALTER TABLE pg_catalog.pg_dist_rebalance_strategy DISABLE TRIGGER pg_dist_rebalance_strategy_enterprise_check_trigger;
INSERT INTO pg_catalog.pg_dist_rebalance_strategy SELECT
name,
default_strategy,
shard_cost_function::regprocedure::regproc,
node_capacity_function::regprocedure::regproc,
shard_allowed_on_node_function::regprocedure::regproc,
default_threshold,
minimum_threshold
FROM public.pg_dist_rebalance_strategy;
ALTER TABLE pg_catalog.pg_dist_rebalance_strategy ENABLE TRIGGER pg_dist_rebalance_strategy_enterprise_check_trigger;
--
-- drop backup tables
--
DROP TABLE public.pg_dist_authinfo;
DROP TABLE public.pg_dist_colocation;
DROP TABLE public.pg_dist_local_group;
DROP TABLE public.pg_dist_node;
DROP TABLE public.pg_dist_node_metadata;
DROP TABLE public.pg_dist_partition;
DROP TABLE public.pg_dist_placement;
DROP TABLE public.pg_dist_poolinfo;
DROP TABLE public.pg_dist_shard;
DROP TABLE public.pg_dist_transaction;
DROP TABLE public.pg_dist_rebalance_strategy;
--
-- reset sequences
--
PERFORM setval('pg_catalog.pg_dist_shardid_seq', (SELECT MAX(shardid)+1 AS max_shard_id FROM pg_dist_shard), false);
PERFORM setval('pg_catalog.pg_dist_placement_placementid_seq', (SELECT MAX(placementid)+1 AS max_placement_id FROM pg_dist_placement), false);
PERFORM setval('pg_catalog.pg_dist_groupid_seq', (SELECT MAX(groupid)+1 AS max_group_id FROM pg_dist_node), false);
PERFORM setval('pg_catalog.pg_dist_node_nodeid_seq', (SELECT MAX(nodeid)+1 AS max_node_id FROM pg_dist_node), false);
PERFORM setval('pg_catalog.pg_dist_colocationid_seq', (SELECT MAX(colocationid)+1 AS max_colocation_id FROM pg_dist_colocation), false);
--
-- register triggers
--
FOR table_name IN SELECT logicalrelid FROM pg_catalog.pg_dist_partition
LOOP
trigger_name := 'truncate_trigger_' || table_name::oid;
command := 'create trigger ' || trigger_name || ' after truncate on ' || table_name || ' execute procedure pg_catalog.citus_truncate_trigger()';
EXECUTE command;
command := 'update pg_trigger set tgisinternal = true where tgname = ' || quote_literal(trigger_name);
EXECUTE command;
END LOOP;
--
-- set dependencies
--
INSERT INTO pg_depend
SELECT
'pg_class'::regclass::oid as classid,
p.logicalrelid::regclass::oid as objid,
0 as objsubid,
'pg_extension'::regclass::oid as refclassid,
(select oid from pg_extension where extname = 'citus') as refobjid,
0 as refobjsubid ,
'n' as deptype
FROM pg_catalog.pg_dist_partition p;
-- restore pg_dist_object from the stable identifiers
-- DELETE/INSERT to avoid primary key violations
WITH old_records AS (
DELETE FROM
citus.pg_dist_object
RETURNING
type,
object_names,
object_args,
distribution_argument_index,
colocationid
)
INSERT INTO citus.pg_dist_object (classid, objid, objsubid, distribution_argument_index, colocationid)
SELECT
address.classid,
address.objid,
address.objsubid,
naming.distribution_argument_index,
naming.colocationid
FROM
old_records naming,
pg_get_object_address(naming.type, naming.object_names, naming.object_args) address;
PERFORM citus_internal.cstore_ensure_objects_exist();
END;
$cppu$;
COMMENT ON FUNCTION pg_catalog.citus_finish_pg_upgrade()
IS 'perform tasks to restore citus settings from a location that has been prepared before pg_upgrade';

View File

@ -107,6 +107,8 @@ BEGIN
FROM FROM
old_records naming, old_records naming,
pg_get_object_address(naming.type, naming.object_names, naming.object_args) address; pg_get_object_address(naming.type, naming.object_names, naming.object_args) address;
PERFORM citus_internal.cstore_ensure_objects_exist();
END; END;
$cppu$; $cppu$;

View File

@ -31,6 +31,9 @@
/* A string containing the version number, platform, and C compiler */ /* A string containing the version number, platform, and C compiler */
#undef CITUS_VERSION_STR #undef CITUS_VERSION_STR
/* Define to 1 to build with table access method support, pg12 and up */
#undef HAS_TABLEAM
/* Define to 1 if you have the <inttypes.h> header file. */ /* Define to 1 if you have the <inttypes.h> header file. */
#undef HAVE_INTTYPES_H #undef HAVE_INTTYPES_H

View File

@ -26,3 +26,6 @@
/* Base URL for statistics collection and update checks */ /* Base URL for statistics collection and update checks */
#undef REPORTS_BASE_URL #undef REPORTS_BASE_URL
/* columnar table access method capability */
#undef HAS_TABLEAM

View File

@ -0,0 +1,352 @@
/*-------------------------------------------------------------------------
*
* cstore.h
*
* Type and function declarations for CStore
*
* Copyright (c) 2016, Citus Data, Inc.
*
* $Id$
*
*-------------------------------------------------------------------------
*/
#ifndef CSTORE_H
#define CSTORE_H
#include "postgres.h"
#include "fmgr.h"
#include "lib/stringinfo.h"
#include "nodes/parsenodes.h"
#include "storage/bufpage.h"
#include "storage/lockdefs.h"
#include "utils/relcache.h"
#include "utils/snapmgr.h"
/* Defines for valid option names */
#define OPTION_NAME_COMPRESSION_TYPE "compression"
#define OPTION_NAME_STRIPE_ROW_COUNT "stripe_row_count"
#define OPTION_NAME_BLOCK_ROW_COUNT "block_row_count"
/* Limits for option parameters */
#define STRIPE_ROW_COUNT_MINIMUM 1000
#define STRIPE_ROW_COUNT_MAXIMUM 10000000
#define BLOCK_ROW_COUNT_MINIMUM 1000
#define BLOCK_ROW_COUNT_MAXIMUM 100000
/* String representations of compression types */
#define COMPRESSION_STRING_NONE "none"
#define COMPRESSION_STRING_PG_LZ "pglz"
/* CStore file signature */
#define CSTORE_MAGIC_NUMBER "citus_cstore"
#define CSTORE_VERSION_MAJOR 1
#define CSTORE_VERSION_MINOR 7
/* miscellaneous defines */
#define CSTORE_FDW_NAME "cstore_fdw"
#define CSTORE_TUPLE_COST_MULTIPLIER 10
#define CSTORE_POSTSCRIPT_SIZE_LENGTH 1
#define CSTORE_POSTSCRIPT_SIZE_MAX 256
/* Enumaration for cstore file's compression method */
typedef enum
{
COMPRESSION_TYPE_INVALID = -1,
COMPRESSION_NONE = 0,
COMPRESSION_PG_LZ = 1,
COMPRESSION_COUNT
} CompressionType;
/*
* CStoreFdwOptions holds the option values to be used when reading or writing
* a cstore file. To resolve these values, we first check foreign table's options,
* and if not present, we then fall back to the default values specified above.
*/
typedef struct CStoreOptions
{
CompressionType compressionType;
uint64 stripeRowCount;
uint32 blockRowCount;
} CStoreOptions;
/*
* StripeMetadata represents information about a stripe. This information is
* stored in the cstore file's footer.
*/
typedef struct StripeMetadata
{
uint64 fileOffset;
uint64 dataLength;
uint32 columnCount;
uint32 blockCount;
uint32 blockRowCount;
uint64 rowCount;
uint64 id;
} StripeMetadata;
/* DataFileMetadata represents the metadata of a cstore file. */
typedef struct DataFileMetadata
{
List *stripeMetadataList;
uint64 blockRowCount;
uint64 stripeRowCount;
CompressionType compression;
} DataFileMetadata;
/* ColumnBlockSkipNode contains statistics for a ColumnBlockData. */
typedef struct ColumnBlockSkipNode
{
/* statistics about values of a column block */
bool hasMinMax;
Datum minimumValue;
Datum maximumValue;
uint64 rowCount;
/*
* Offsets and sizes of value and exists streams in the column data.
* These enable us to skip reading suppressed row blocks, and start reading
* a block without reading previous blocks.
*/
uint64 valueBlockOffset;
uint64 valueLength;
uint64 existsBlockOffset;
uint64 existsLength;
CompressionType valueCompressionType;
} ColumnBlockSkipNode;
/*
* StripeSkipList can be used for skipping row blocks. It contains a column block
* skip node for each block of each column. blockSkipNodeArray[column][block]
* is the entry for the specified column block.
*/
typedef struct StripeSkipList
{
ColumnBlockSkipNode **blockSkipNodeArray;
uint32 columnCount;
uint32 blockCount;
} StripeSkipList;
/*
* BlockData represents a block of data for multiple columns. valueArray stores
* the values of data, and existsArray stores whether a value is present.
* valueBuffer is used to store (uncompressed) serialized values
* referenced by Datum's in valueArray. It is only used for by-reference Datum's.
* There is a one-to-one correspondence between valueArray and existsArray.
*/
typedef struct BlockData
{
uint32 rowCount;
uint32 columnCount;
/*
* Following are indexed by [column][row]. If a column is not projected,
* then existsArray[column] and valueArray[column] are NULL.
*/
bool **existsArray;
Datum **valueArray;
/* valueBuffer keeps actual data for type-by-reference datums from valueArray. */
StringInfo *valueBufferArray;
} BlockData;
/*
* ColumnBlockBuffers represents a block of serialized data in a column.
* valueBuffer stores the serialized values of data, and existsBuffer stores
* serialized value of presence information. valueCompressionType contains
* compression type if valueBuffer is compressed. Finally rowCount has
* the number of rows in this block.
*/
typedef struct ColumnBlockBuffers
{
StringInfo existsBuffer;
StringInfo valueBuffer;
CompressionType valueCompressionType;
} ColumnBlockBuffers;
/*
* ColumnBuffers represents data buffers for a column in a row stripe. Each
* column is made of multiple column blocks.
*/
typedef struct ColumnBuffers
{
ColumnBlockBuffers **blockBuffersArray;
} ColumnBuffers;
/* StripeBuffers represents data for a row stripe in a cstore file. */
typedef struct StripeBuffers
{
uint32 columnCount;
uint32 rowCount;
ColumnBuffers **columnBuffersArray;
} StripeBuffers;
/* TableReadState represents state of a cstore file read operation. */
typedef struct TableReadState
{
DataFileMetadata *datafileMetadata;
StripeMetadata *currentStripeMetadata;
TupleDesc tupleDescriptor;
Relation relation;
/*
* List of Var pointers for columns in the query. We use this both for
* getting vector of projected columns, and also when we want to build
* base constraint to find selected row blocks.
*/
List *projectedColumnList;
List *whereClauseList;
MemoryContext stripeReadContext;
StripeBuffers *stripeBuffers;
uint32 readStripeCount;
uint64 stripeReadRowCount;
BlockData *blockData;
int32 deserializedBlockIndex;
} TableReadState;
/* TableWriteState represents state of a cstore file write operation. */
typedef struct TableWriteState
{
CompressionType compressionType;
TupleDesc tupleDescriptor;
FmgrInfo **comparisonFunctionArray;
Relation relation;
MemoryContext stripeWriteContext;
StripeBuffers *stripeBuffers;
StripeSkipList *stripeSkipList;
uint32 stripeMaxRowCount;
uint32 blockRowCount;
BlockData *blockData;
/*
* compressionBuffer buffer is used as temporary storage during
* data value compression operation. It is kept here to minimize
* memory allocations. It lives in stripeWriteContext and gets
* deallocated when memory context is reset.
*/
StringInfo compressionBuffer;
} TableWriteState;
extern int cstore_compression;
extern int cstore_stripe_row_count;
extern int cstore_block_row_count;
extern void cstore_init(void);
extern CompressionType ParseCompressionType(const char *compressionTypeString);
/* Function declarations for writing to a cstore file */
extern TableWriteState * CStoreBeginWrite(Relation relation,
CompressionType compressionType,
uint64 stripeMaxRowCount,
uint32 blockRowCount,
TupleDesc tupleDescriptor);
extern void CStoreWriteRow(TableWriteState *state, Datum *columnValues,
bool *columnNulls);
extern void CStoreEndWrite(TableWriteState *state);
/* Function declarations for reading from a cstore file */
extern TableReadState * CStoreBeginRead(Relation relation,
TupleDesc tupleDescriptor,
List *projectedColumnList, List *qualConditions);
extern bool CStoreReadFinished(TableReadState *state);
extern bool CStoreReadNextRow(TableReadState *state, Datum *columnValues,
bool *columnNulls);
extern void CStoreRescan(TableReadState *readState);
extern void CStoreEndRead(TableReadState *state);
/* Function declarations for common functions */
extern FmgrInfo * GetFunctionInfoOrNull(Oid typeId, Oid accessMethodId,
int16 procedureId);
extern BlockData * CreateEmptyBlockData(uint32 columnCount, bool *columnMask,
uint32 blockRowCount);
extern void FreeBlockData(BlockData *blockData);
extern uint64 CStoreTableRowCount(Relation relation);
extern bool CompressBuffer(StringInfo inputBuffer, StringInfo outputBuffer,
CompressionType compressionType);
extern StringInfo DecompressBuffer(StringInfo buffer, CompressionType compressionType);
extern char * CompressionTypeStr(CompressionType type);
/* cstore_metadata_tables.c */
extern void DeleteDataFileMetadataRowIfExists(Oid relfilenode);
extern void InitCStoreDataFileMetadata(Oid relfilenode, int blockRowCount, int
stripeRowCount, CompressionType compression);
extern void UpdateCStoreDataFileMetadata(Oid relfilenode, int blockRowCount, int
stripeRowCount, CompressionType compression);
extern DataFileMetadata * ReadDataFileMetadata(Oid relfilenode, bool missingOk);
extern uint64 GetHighestUsedAddress(Oid relfilenode);
extern StripeMetadata ReserveStripe(Relation rel, uint64 size,
uint64 rowCount, uint64 columnCount,
uint64 blockCount, uint64 blockRowCount);
extern void SaveStripeSkipList(Oid relfilenode, uint64 stripe,
StripeSkipList *stripeSkipList,
TupleDesc tupleDescriptor);
extern StripeSkipList * ReadStripeSkipList(Oid relfilenode, uint64 stripe,
TupleDesc tupleDescriptor,
uint32 blockCount);
typedef struct SmgrAddr
{
BlockNumber blockno;
uint32 offset;
} SmgrAddr;
/*
* Map logical offsets (as tracked in the metadata) to a physical page and
* offset where the data is kept.
*/
static inline SmgrAddr
logical_to_smgr(uint64 logicalOffset)
{
uint64 bytes_per_page = BLCKSZ - SizeOfPageHeaderData;
SmgrAddr addr;
addr.blockno = logicalOffset / bytes_per_page;
addr.offset = SizeOfPageHeaderData + (logicalOffset % bytes_per_page);
return addr;
}
/*
* Map a physical page adnd offset address to a logical address.
*/
static inline uint64
smgr_to_logical(SmgrAddr addr)
{
uint64 bytes_per_page = BLCKSZ - SizeOfPageHeaderData;
return bytes_per_page * addr.blockno + addr.offset - SizeOfPageHeaderData;
}
/*
* Get the first usable address of next block.
*/
static inline SmgrAddr
next_block_start(SmgrAddr addr)
{
SmgrAddr result = {
.blockno = addr.blockno + 1,
.offset = SizeOfPageHeaderData
};
return result;
}
#endif /* CSTORE_H */

View File

@ -0,0 +1,19 @@
/*-------------------------------------------------------------------------
*
* cstore_customscan.h
*
* Forward declarations of functions to hookup the custom scan feature of
* cstore.
*
* $Id$
*
*-------------------------------------------------------------------------
*/
#ifndef CSTORE_FDW_CSTORE_CUSTOMSCAN_H
#define CSTORE_FDW_CSTORE_CUSTOMSCAN_H
void cstore_customscan_init(void);
#endif /*CSTORE_FDW_CSTORE_CUSTOMSCAN_H */

View File

@ -0,0 +1,35 @@
/*-------------------------------------------------------------------------
*
* cstore_fdw.h
*
* Type and function declarations for CStore foreign data wrapper.
*
* Copyright (c) 2016, Citus Data, Inc.
*
* $Id$
*
*-------------------------------------------------------------------------
*/
#ifndef CSTORE_FDW_H
#define CSTORE_FDW_H
#include "postgres.h"
#include "fmgr.h"
void cstore_fdw_init(void);
void cstore_fdw_finish(void);
/* event trigger function declarations */
extern Datum cstore_ddl_event_end_trigger(PG_FUNCTION_ARGS);
/* Function declarations for utility UDFs */
extern Datum cstore_table_size(PG_FUNCTION_ARGS);
extern Datum cstore_clean_table_resources(PG_FUNCTION_ARGS);
/* Function declarations for foreign data wrapper */
extern Datum cstore_fdw_handler(PG_FUNCTION_ARGS);
extern Datum cstore_fdw_validator(PG_FUNCTION_ARGS);
#endif /* CSTORE_FDW_H */

View File

@ -0,0 +1,19 @@
#include "citus_version.h"
#if HAS_TABLEAM
#include "postgres.h"
#include "fmgr.h"
#include "access/tableam.h"
#include "access/skey.h"
#include "nodes/bitmapset.h"
const TableAmRoutine * GetCstoreTableAmRoutine(void);
extern void cstore_tableam_init(void);
extern void cstore_tableam_finish(void);
extern TableScanDesc cstore_beginscan_extended(Relation relation, Snapshot snapshot,
int nkeys, ScanKey key,
ParallelTableScanDesc parallel_scan,
uint32 flags, Bitmapset *attr_needed,
List *scanQual);
#endif

View File

@ -0,0 +1,65 @@
/*-------------------------------------------------------------------------
*
* cstore_version_compat.h
*
* Compatibility macros for writing code agnostic to PostgreSQL versions
*
* Copyright (c) 2018, Citus Data, Inc.
*
* $Id$
*
*-------------------------------------------------------------------------
*/
#ifndef CSTORE_COMPAT_H
#define CSTORE_COMPAT_H
#if PG_VERSION_NUM < 100000
/* Accessor for the i'th attribute of tupdesc. */
#define TupleDescAttr(tupdesc, i) ((tupdesc)->attrs[(i)])
#endif
#if PG_VERSION_NUM < 110000
#define ALLOCSET_DEFAULT_SIZES ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, \
ALLOCSET_DEFAULT_MAXSIZE
#define ACLCHECK_OBJECT_TABLE ACL_KIND_CLASS
#else
#define ACLCHECK_OBJECT_TABLE OBJECT_TABLE
#define ExplainPropertyLong(qlabel, value, es) \
ExplainPropertyInteger(qlabel, NULL, value, es)
#endif
#if PG_VERSION_NUM >= 130000
#define CALL_PREVIOUS_UTILITY() \
PreviousProcessUtilityHook(plannedStatement, queryString, context, paramListInfo, \
queryEnvironment, destReceiver, queryCompletion)
#elif PG_VERSION_NUM >= 100000
#define CALL_PREVIOUS_UTILITY() \
PreviousProcessUtilityHook(plannedStatement, queryString, context, paramListInfo, \
queryEnvironment, destReceiver, completionTag)
#else
#define CALL_PREVIOUS_UTILITY() \
PreviousProcessUtilityHook(parseTree, queryString, context, paramListInfo, \
destReceiver, completionTag)
#endif
#if PG_VERSION_NUM < 120000
#define TTS_EMPTY(slot) ((slot)->tts_isempty)
#define ExecForceStoreHeapTuple(tuple, slot, shouldFree) \
ExecStoreTuple(newTuple, tupleSlot, InvalidBuffer, shouldFree);
#define TableScanDesc HeapScanDesc
#define table_beginscan heap_beginscan
#define table_endscan heap_endscan
#endif
#if PG_VERSION_NUM >= 130000
#define heap_open table_open
#define heap_openrv table_openrv
#define heap_close table_close
#endif
#endif /* CSTORE_COMPAT_H */

View File

@ -0,0 +1,21 @@
/*-------------------------------------------------------------------------
*
* mod.h
*
* Type and function declarations for CStore
*
* Copyright (c) 2016, Citus Data, Inc.
*
* $Id$
*
*-------------------------------------------------------------------------
*/
#ifndef MOD_H
#define MOD_H
/* Function declarations for extension loading and unloading */
extern void columnar_init(void);
extern void columnar_fini(void);
#endif /* MOD_H */

View File

@ -161,6 +161,25 @@ check-follower-cluster: all
$(pg_regress_multi_check) --load-extension=citus --follower-cluster \ $(pg_regress_multi_check) --load-extension=citus --follower-cluster \
-- $(MULTI_REGRESS_OPTS) --schedule=$(citus_abs_srcdir)/multi_follower_schedule $(EXTRA_TESTS) -- $(MULTI_REGRESS_OPTS) --schedule=$(citus_abs_srcdir)/multi_follower_schedule $(EXTRA_TESTS)
COLUMNAR_SCHEDULES =
COLUMNAR_ISOLATION_SCHEDULES =
# even though we always add the fdw schedules, keep them separate from the declaration
# above for easy removabl when fdw support is removed
COLUMNAR_SCHEDULES += columnar_fdw_schedule
COLUMNAR_ISOLATION_SCHEDULES += columnar_fdw_isolation_schedule
ifeq ($(HAS_TABLEAM),yes)
COLUMNAR_SCHEDULES += columnar_am_schedule
COLUMNAR_ISOLATION_SCHEDULES += columnar_am_isolation_schedule
endif
check-columnar:
$(pg_regress_multi_check) --load-extension=citus \
-- $(MULTI_REGRESS_OPTS) $(addprefix --schedule=$(citus_abs_srcdir)/,$(COLUMNAR_SCHEDULES)) $(EXTRA_TESTS)
check-columnar-isolation: all $(isolation_test_files)
$(pg_regress_multi_check) --load-extension=citus --isolationtester \
-- $(MULTI_REGRESS_OPTS) --inputdir=$(citus_abs_srcdir)/build $(addprefix --schedule=$(citus_abs_srcdir)/,$(COLUMNAR_ISOLATION_SCHEDULES)) $(EXTRA_TESTS)
check-failure: all check-failure: all
$(pg_regress_multi_check) --load-extension=citus --mitmproxy \ $(pg_regress_multi_check) --load-extension=citus --mitmproxy \
-- $(MULTI_REGRESS_OPTS) --schedule=$(citus_abs_srcdir)/failure_schedule $(EXTRA_TESTS) -- $(MULTI_REGRESS_OPTS) --schedule=$(citus_abs_srcdir)/failure_schedule $(EXTRA_TESTS)

View File

@ -1 +1 @@
test: upgrade_basic_after upgrade_type_after upgrade_ref2ref_after upgrade_distributed_function_after upgrade_rebalance_strategy_after test: upgrade_basic_after upgrade_type_after upgrade_ref2ref_after upgrade_distributed_function_after upgrade_rebalance_strategy_after upgrade_list_citus_objects

View File

@ -181,3 +181,6 @@ s/wrong data type: [0-9]+, expected [0-9]+/wrong data type: XXXX, expected XXXX/
# Errors with relation OID does not exist # Errors with relation OID does not exist
s/relation with OID [0-9]+ does not exist/relation with OID XXXX does not exist/g s/relation with OID [0-9]+ does not exist/relation with OID XXXX does not exist/g
# ignore event triggers, mainly due to the event trigger for columnar
/^DEBUG: EventTriggerInvoke [0-9]+$/d

View File

@ -0,0 +1,2 @@
test: am_write_concurrency
test: am_vacuum_vs_insert

View File

@ -0,0 +1,18 @@
test: am_create
test: am_load
test: am_query
test: am_analyze
test: am_data_types
test: am_functions
test: am_drop
test: am_insert
test: am_copyto
test: am_alter
test: am_rollback
test: am_truncate
test: am_vacuum
test: am_clean
test: am_block_filtering
test: am_join
test: am_trigger
test: am_tableoptions

View File

@ -0,0 +1 @@
# just an empty file now, please remove when we have a test

View File

@ -0,0 +1,14 @@
test: fdw_create
test: fdw_load
test: fdw_query
test: fdw_analyze
test: fdw_data_types
test: fdw_functions
test: fdw_block_filtering
test: fdw_drop
test: fdw_insert
test: fdw_copyto
test: fdw_alter
test: fdw_rollback
test: fdw_truncate
test: fdw_clean

View File

@ -0,0 +1,3 @@
"{1,2,3}","{1,2,3}","{a,b,c}"
{},{},{}
"{-2147483648,2147483647}","{-9223372036854775808,9223372036854775807}","{""""}"
1 {1,2,3} {1,2,3} {a,b,c}
2 {} {} {}
3 {-2147483648,2147483647} {-9223372036854775808,9223372036854775807} {""}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,5 @@
a,1990-01-10,2090,97.1,XA ,{a}
b,1990-11-01,2203,98.1,XA ,"{a,b}"
c,1988-11-01,2907,99.4,XB ,"{w,y}"
d,1985-05-05,2314,98.3,XB ,{}
e,1995-05-05,2236,98.2,XC ,{a}
1 a 1990-01-10 2090 97.1 XA {a}
2 b 1990-11-01 2203 98.1 XA {a,b}
3 c 1988-11-01 2907 99.4 XB {w,y}
4 d 1985-05-05 2314 98.3 XB {}
5 e 1995-05-05 2236 98.2 XC {a}

View File

@ -0,0 +1,3 @@
f,1983-04-02,3090,99.6,XD ,"{a,b,c,y}"
g,1991-12-13,1803,85.1,XD ,"{a,c}"
h,1987-10-26,2112,95.4,XD ,"{w,a}"
1 f 1983-04-02 3090 99.6 XD {a,b,c,y}
2 g 1991-12-13 1803 85.1 XD {a,c}
3 h 1987-10-26 2112 95.4 XD {w,a}

View File

@ -0,0 +1,2 @@
2000-01-02 04:05:06,1999-01-08 14:05:06+02,2000-01-02,04:05:06,04:00:00
1970-01-01 00:00:00,infinity,-infinity,00:00:00,00:00:00
1 2000-01-02 04:05:06 1999-01-08 14:05:06+02 2000-01-02 04:05:06 04:00:00
2 1970-01-01 00:00:00 infinity -infinity 00:00:00 00:00:00

View File

@ -0,0 +1,2 @@
a,"(2,b)"
b,"(3,c)"
1 a (2,b)
2 b (3,c)

View File

@ -0,0 +1,2 @@
,{NULL},"(,)"
,,
1 {NULL} (,)
2

View File

@ -0,0 +1,2 @@
f,\xdeadbeef,$1.00,192.168.1.2,10101,a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,"{""key"": ""value""}"
t,\xcdb0,$1.50,127.0.0.1,"",a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11,[]
1 f \xdeadbeef $1.00 192.168.1.2 10101 a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 {"key": "value"}
2 t \xcdb0 $1.50 127.0.0.1 a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11 []

View File

@ -0,0 +1,2 @@
"[1,3)","[1,3)","[1,3)","[""2000-01-02 00:30:00"",""2010-02-03 12:30:00"")"
empty,"[1,)","(,)",empty
1 [1,3) [1,3) [1,3) ["2000-01-02 00:30:00","2010-02-03 12:30:00")
2 empty [1,) (,) empty

View File

@ -21,3 +21,13 @@
/multi_behavioral_analytics_create_table.out /multi_behavioral_analytics_create_table.out
/multi_insert_select_behavioral_analytics_create_table.out /multi_insert_select_behavioral_analytics_create_table.out
/hyperscale_tutorial.out /hyperscale_tutorial.out
/am_block_filtering.out
/am_copyto.out
/am_create.out
/am_data_types.out
/am_load.out
/fdw_block_filtering.out
/fdw_copyto.out
/fdw_create.out
/fdw_data_types.out
/fdw_load.out

View File

@ -0,0 +1,173 @@
--
-- Testing ALTER TABLE on cstore_fdw tables.
--
CREATE TABLE test_alter_table (a int, b int, c int) USING cstore_tableam;
WITH sample_data AS (VALUES
(1, 2, 3),
(4, 5, 6),
(7, 8, 9)
)
INSERT INTO test_alter_table SELECT * FROM sample_data;
-- drop a column
ALTER TABLE test_alter_table DROP COLUMN a;
-- test analyze
ANALYZE test_alter_table;
-- verify select queries run as expected
SELECT * FROM test_alter_table;
b | c
---------------------------------------------------------------------
2 | 3
5 | 6
8 | 9
(3 rows)
SELECT a FROM test_alter_table;
ERROR: column "a" does not exist
SELECT b FROM test_alter_table;
b
---------------------------------------------------------------------
2
5
8
(3 rows)
-- verify insert runs as expected
INSERT INTO test_alter_table (SELECT 3, 5, 8);
ERROR: INSERT has more expressions than target columns
INSERT INTO test_alter_table (SELECT 5, 8);
-- add a column with no defaults
ALTER TABLE test_alter_table ADD COLUMN d int;
SELECT * FROM test_alter_table;
b | c | d
---------------------------------------------------------------------
2 | 3 |
5 | 6 |
8 | 9 |
5 | 8 |
(4 rows)
INSERT INTO test_alter_table (SELECT 3, 5, 8);
SELECT * FROM test_alter_table;
b | c | d
---------------------------------------------------------------------
2 | 3 |
5 | 6 |
8 | 9 |
5 | 8 |
3 | 5 | 8
(5 rows)
-- add a fixed-length column with default value
ALTER TABLE test_alter_table ADD COLUMN e int default 3;
SELECT * from test_alter_table;
b | c | d | e
---------------------------------------------------------------------
2 | 3 | | 3
5 | 6 | | 3
8 | 9 | | 3
5 | 8 | | 3
3 | 5 | 8 | 3
(5 rows)
INSERT INTO test_alter_table (SELECT 1, 2, 4, 8);
SELECT * from test_alter_table;
b | c | d | e
---------------------------------------------------------------------
2 | 3 | | 3
5 | 6 | | 3
8 | 9 | | 3
5 | 8 | | 3
3 | 5 | 8 | 3
1 | 2 | 4 | 8
(6 rows)
-- add a variable-length column with default value
ALTER TABLE test_alter_table ADD COLUMN f text DEFAULT 'TEXT ME';
SELECT * from test_alter_table;
b | c | d | e | f
---------------------------------------------------------------------
2 | 3 | | 3 | TEXT ME
5 | 6 | | 3 | TEXT ME
8 | 9 | | 3 | TEXT ME
5 | 8 | | 3 | TEXT ME
3 | 5 | 8 | 3 | TEXT ME
1 | 2 | 4 | 8 | TEXT ME
(6 rows)
INSERT INTO test_alter_table (SELECT 1, 2, 4, 8, 'ABCDEF');
SELECT * from test_alter_table;
b | c | d | e | f
---------------------------------------------------------------------
2 | 3 | | 3 | TEXT ME
5 | 6 | | 3 | TEXT ME
8 | 9 | | 3 | TEXT ME
5 | 8 | | 3 | TEXT ME
3 | 5 | 8 | 3 | TEXT ME
1 | 2 | 4 | 8 | TEXT ME
1 | 2 | 4 | 8 | ABCDEF
(7 rows)
-- drop couple of columns
ALTER TABLE test_alter_table DROP COLUMN c;
ALTER TABLE test_alter_table DROP COLUMN e;
ANALYZE test_alter_table;
SELECT * from test_alter_table;
b | d | f
---------------------------------------------------------------------
2 | | TEXT ME
5 | | TEXT ME
8 | | TEXT ME
5 | | TEXT ME
3 | 8 | TEXT ME
1 | 4 | TEXT ME
1 | 4 | ABCDEF
(7 rows)
SELECT count(*) from test_alter_table;
count
---------------------------------------------------------------------
7
(1 row)
SELECT count(t.*) from test_alter_table t;
count
---------------------------------------------------------------------
7
(1 row)
-- unsupported default values
ALTER TABLE test_alter_table ADD COLUMN g boolean DEFAULT isfinite(current_date);
ALTER TABLE test_alter_table ADD COLUMN h DATE DEFAULT current_date;
SELECT * FROM test_alter_table;
ERROR: unsupported default value for column "g"
HINT: Expression is either mutable or does not evaluate to constant value
ALTER TABLE test_alter_table ALTER COLUMN g DROP DEFAULT;
SELECT * FROM test_alter_table;
ERROR: unsupported default value for column "h"
HINT: Expression is either mutable or does not evaluate to constant value
ALTER TABLE test_alter_table ALTER COLUMN h DROP DEFAULT;
ANALYZE test_alter_table;
SELECT * FROM test_alter_table;
b | d | f | g | h
---------------------------------------------------------------------
2 | | TEXT ME | |
5 | | TEXT ME | |
8 | | TEXT ME | |
5 | | TEXT ME | |
3 | 8 | TEXT ME | |
1 | 4 | TEXT ME | |
1 | 4 | ABCDEF | |
(7 rows)
-- unsupported type change
ALTER TABLE test_alter_table ADD COLUMN i int;
ALTER TABLE test_alter_table ADD COLUMN j float;
ALTER TABLE test_alter_table ADD COLUMN k text;
-- this is valid type change
ALTER TABLE test_alter_table ALTER COLUMN i TYPE float;
-- this is not valid
ALTER TABLE test_alter_table ALTER COLUMN j TYPE int;
-- text / varchar conversion is valid both ways
ALTER TABLE test_alter_table ALTER COLUMN k TYPE varchar(20);
ALTER TABLE test_alter_table ALTER COLUMN k TYPE text;
DROP TABLE test_alter_table;

View File

@ -0,0 +1,19 @@
--
-- Test the ANALYZE command for cstore_fdw tables.
--
-- ANALYZE uncompressed table
ANALYZE contestant;
SELECT count(*) FROM pg_stats WHERE tablename='contestant';
count
---------------------------------------------------------------------
6
(1 row)
-- ANALYZE compressed table
ANALYZE contestant_compressed;
SELECT count(*) FROM pg_stats WHERE tablename='contestant_compressed';
count
---------------------------------------------------------------------
6
(1 row)

View File

@ -0,0 +1,8 @@
DROP TABLE test_null_values;
DROP TABLE test_other_types;
DROP TABLE test_range_types;
DROP TABLE test_enum_and_composite_types;
DROP TYPE composite_type;
DROP TYPE enum_type;
DROP TABLE test_datetime_types;
DROP TABLE test_array_types;

View File

@ -0,0 +1,54 @@
--
-- Tests the different DROP commands for cstore_fdw tables.
--
-- DROP TABL
-- DROP SCHEMA
-- DROP EXTENSION
-- DROP DATABASE
--
-- Note that travis does not create
-- cstore_fdw extension in default database (postgres). This has caused
-- different behavior between travis tests and local tests. Thus
-- 'postgres' directory is excluded from comparison to have the same result.
-- store postgres database oid
SELECT oid postgres_oid FROM pg_database WHERE datname = 'postgres' \gset
SELECT count(*) AS cstore_data_files_before_drop FROM cstore.cstore_data_files \gset
-- DROP cstore_fdw tables
DROP TABLE contestant;
DROP TABLE contestant_compressed;
-- make sure DROP deletes metadata
SELECT :cstore_data_files_before_drop - count(*) FROM cstore.cstore_data_files;
?column?
---------------------------------------------------------------------
2
(1 row)
-- Create a cstore_fdw table under a schema and drop it.
CREATE SCHEMA test_schema;
CREATE TABLE test_schema.test_table(data int) USING cstore_tableam;
SELECT count(*) AS cstore_data_files_before_drop FROM cstore.cstore_data_files \gset
DROP SCHEMA test_schema CASCADE;
NOTICE: drop cascades to table test_schema.test_table
SELECT :cstore_data_files_before_drop - count(*) FROM cstore.cstore_data_files;
?column?
---------------------------------------------------------------------
1
(1 row)
SELECT current_database() datname \gset
CREATE DATABASE db_to_drop;
NOTICE: Citus partially supports CREATE DATABASE for distributed databases
DETAIL: Citus does not propagate CREATE DATABASE command to workers
HINT: You can manually create a database and its extensions on workers.
\c db_to_drop
CREATE EXTENSION citus;
SELECT oid::text databaseoid FROM pg_database WHERE datname = current_database() \gset
CREATE TABLE test_table(data int) USING cstore_tableam;
DROP EXTENSION citus CASCADE;
NOTICE: drop cascades to table test_table
-- test database drop
CREATE EXTENSION citus;
SELECT oid::text databaseoid FROM pg_database WHERE datname = current_database() \gset
CREATE TABLE test_table(data int) USING cstore_tableam;
\c :datname
DROP DATABASE db_to_drop;

View File

@ -0,0 +1,18 @@
--
-- Test utility functions for cstore_fdw tables.
--
CREATE TABLE empty_table (a int) USING cstore_tableam;
CREATE TABLE table_with_data (a int) USING cstore_tableam;
CREATE TABLE non_cstore_table (a int);
COPY table_with_data FROM STDIN;
SELECT pg_relation_size('empty_table') < pg_relation_size('table_with_data');
?column?
---------------------------------------------------------------------
t
(1 row)
SELECT cstore_table_size('non_cstore_table');
ERROR: relation is not a cstore table
DROP TABLE empty_table;
DROP TABLE table_with_data;
DROP TABLE non_cstore_table;

View File

@ -0,0 +1,86 @@
--
-- Testing insert on cstore_fdw tables.
--
CREATE TABLE test_insert_command (a int) USING cstore_tableam;
-- test single row inserts fail
select count(*) from test_insert_command;
count
---------------------------------------------------------------------
0
(1 row)
insert into test_insert_command values(1);
select count(*) from test_insert_command;
count
---------------------------------------------------------------------
1
(1 row)
insert into test_insert_command default values;
select count(*) from test_insert_command;
count
---------------------------------------------------------------------
2
(1 row)
-- test inserting from another table succeed
CREATE TABLE test_insert_command_data (a int);
select count(*) from test_insert_command_data;
count
---------------------------------------------------------------------
0
(1 row)
insert into test_insert_command_data values(1);
select count(*) from test_insert_command_data;
count
---------------------------------------------------------------------
1
(1 row)
insert into test_insert_command select * from test_insert_command_data;
select count(*) from test_insert_command;
count
---------------------------------------------------------------------
3
(1 row)
drop table test_insert_command_data;
drop table test_insert_command;
-- test long attribute value insertion
-- create sufficiently long text so that data is stored in toast
CREATE TABLE test_long_text AS
SELECT a as int_val, string_agg(random()::text, '') as text_val
FROM generate_series(1, 10) a, generate_series(1, 1000) b
GROUP BY a ORDER BY a;
-- store hash values of text for later comparison
CREATE TABLE test_long_text_hash AS
SELECT int_val, md5(text_val) AS hash
FROM test_long_text;
CREATE TABLE test_cstore_long_text(int_val int, text_val text)
USING cstore_tableam;
-- store long text in cstore table
INSERT INTO test_cstore_long_text SELECT * FROM test_long_text;
-- drop source table to remove original text from toast
DROP TABLE test_long_text;
-- check if text data is still available in cstore table
-- by comparing previously stored hash.
SELECT a.int_val
FROM test_long_text_hash a, test_cstore_long_text c
WHERE a.int_val = c.int_val AND a.hash = md5(c.text_val);
int_val
---------------------------------------------------------------------
1
2
3
4
5
6
7
8
9
10
(10 rows)
DROP TABLE test_long_text_hash;
DROP TABLE test_cstore_long_text;

View File

@ -0,0 +1,37 @@
CREATE SCHEMA am_cstore_join;
SET search_path TO am_cstore_join;
CREATE TABLE users (id int, name text) USING cstore_tableam;
INSERT INTO users SELECT a, 'name' || a FROM generate_series(0,30-1) AS a;
CREATE TABLE things (id int, user_id int, name text) USING cstore_tableam;
INSERT INTO things SELECT a, a % 30, 'thing' || a FROM generate_series(1,300) AS a;
-- force the nested loop to rescan the table
SET enable_material TO off;
SET enable_hashjoin TO off;
SET enable_mergejoin TO off;
SELECT count(*)
FROM users
JOIN things ON (users.id = things.user_id)
WHERE things.id > 290;
count
---------------------------------------------------------------------
10
(1 row)
-- verify the join uses a nested loop to trigger the rescan behaviour
EXPLAIN (COSTS OFF)
SELECT count(*)
FROM users
JOIN things ON (users.id = things.user_id)
WHERE things.id > 299990;
QUERY PLAN
---------------------------------------------------------------------
Aggregate
-> Nested Loop
Join Filter: (users.id = things.user_id)
-> Custom Scan (CStoreScan) on things
Filter: (id > 299990)
-> Custom Scan (CStoreScan) on users
(6 rows)
SET client_min_messages TO warning;
DROP SCHEMA am_cstore_join CASCADE;

View File

@ -0,0 +1,105 @@
--
-- Test querying cstore_fdw tables.
--
-- Settings to make the result deterministic
SET datestyle = "ISO, YMD";
-- Query uncompressed data
SELECT count(*) FROM contestant;
count
---------------------------------------------------------------------
8
(1 row)
SELECT avg(rating), stddev_samp(rating) FROM contestant;
avg | stddev_samp
---------------------------------------------------------------------
2344.3750000000000000 | 433.746119785032
(1 row)
SELECT country, avg(rating) FROM contestant WHERE rating > 2200
GROUP BY country ORDER BY country;
country | avg
---------------------------------------------------------------------
XA | 2203.0000000000000000
XB | 2610.5000000000000000
XC | 2236.0000000000000000
XD | 3090.0000000000000000
(4 rows)
SELECT * FROM contestant ORDER BY handle;
handle | birthdate | rating | percentile | country | achievements
---------------------------------------------------------------------
a | 1990-01-10 | 2090 | 97.1 | XA | {a}
b | 1990-11-01 | 2203 | 98.1 | XA | {a,b}
c | 1988-11-01 | 2907 | 99.4 | XB | {w,y}
d | 1985-05-05 | 2314 | 98.3 | XB | {}
e | 1995-05-05 | 2236 | 98.2 | XC | {a}
f | 1983-04-02 | 3090 | 99.6 | XD | {a,b,c,y}
g | 1991-12-13 | 1803 | 85.1 | XD | {a,c}
h | 1987-10-26 | 2112 | 95.4 | XD | {w,a}
(8 rows)
-- Query compressed data
SELECT count(*) FROM contestant_compressed;
count
---------------------------------------------------------------------
8
(1 row)
SELECT avg(rating), stddev_samp(rating) FROM contestant_compressed;
avg | stddev_samp
---------------------------------------------------------------------
2344.3750000000000000 | 433.746119785032
(1 row)
SELECT country, avg(rating) FROM contestant_compressed WHERE rating > 2200
GROUP BY country ORDER BY country;
country | avg
---------------------------------------------------------------------
XA | 2203.0000000000000000
XB | 2610.5000000000000000
XC | 2236.0000000000000000
XD | 3090.0000000000000000
(4 rows)
SELECT * FROM contestant_compressed ORDER BY handle;
handle | birthdate | rating | percentile | country | achievements
---------------------------------------------------------------------
a | 1990-01-10 | 2090 | 97.1 | XA | {a}
b | 1990-11-01 | 2203 | 98.1 | XA | {a,b}
c | 1988-11-01 | 2907 | 99.4 | XB | {w,y}
d | 1985-05-05 | 2314 | 98.3 | XB | {}
e | 1995-05-05 | 2236 | 98.2 | XC | {a}
f | 1983-04-02 | 3090 | 99.6 | XD | {a,b,c,y}
g | 1991-12-13 | 1803 | 85.1 | XD | {a,c}
h | 1987-10-26 | 2112 | 95.4 | XD | {w,a}
(8 rows)
-- Verify that we handle whole-row references correctly
SELECT to_json(v) FROM contestant v ORDER BY rating LIMIT 1;
to_json
---------------------------------------------------------------------
{"handle":"g","birthdate":"1991-12-13","rating":1803,"percentile":85.1,"country":"XD ","achievements":["a","c"]}
(1 row)
-- Test variables used in expressions
CREATE TABLE union_first (a int, b int) USING cstore_tableam;
CREATE TABLE union_second (a int, b int) USING cstore_tableam;
INSERT INTO union_first SELECT a, a FROM generate_series(1, 5) a;
INSERT INTO union_second SELECT a, a FROM generate_series(11, 15) a;
(SELECT a*1, b FROM union_first) union all (SELECT a*1, b FROM union_second);
?column? | b
---------------------------------------------------------------------
1 | 1
2 | 2
3 | 3
4 | 4
5 | 5
11 | 11
12 | 12
13 | 13
14 | 14
15 | 15
(10 rows)
DROP TABLE union_first, union_second;

View File

@ -0,0 +1,77 @@
--
-- Testing we handle rollbacks properly
--
CREATE TABLE t(a int, b int) USING cstore_tableam;
BEGIN;
INSERT INTO t SELECT i, i+1 FROM generate_series(1, 10) i;
ROLLBACK;
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
0
(1 row)
-- check stripe metadata also have been rolled-back
SELECT count(*) FROM cstore.cstore_stripes a, pg_class b
WHERE a.relfilenode = b.relfilenode AND b.relname = 't';
count
---------------------------------------------------------------------
0
(1 row)
INSERT INTO t SELECT i, i+1 FROM generate_series(1, 10) i;
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
10
(1 row)
SELECT count(*) FROM cstore.cstore_stripes a, pg_class b
WHERE a.relfilenode = b.relfilenode AND b.relname = 't';
count
---------------------------------------------------------------------
1
(1 row)
-- savepoint rollback
BEGIN;
SAVEPOINT s0;
INSERT INTO t SELECT i, i+1 FROM generate_series(1, 10) i;
SAVEPOINT s1;
INSERT INTO t SELECT i, i+1 FROM generate_series(1, 10) i;
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
30
(1 row)
ROLLBACK TO SAVEPOINT s1;
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
20
(1 row)
ROLLBACK TO SAVEPOINT s0;
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
10
(1 row)
INSERT INTO t SELECT i, i+1 FROM generate_series(1, 10) i;
COMMIT;
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
20
(1 row)
SELECT count(*) FROM cstore.cstore_stripes a, pg_class b
WHERE a.relfilenode = b.relfilenode AND b.relname = 't';
count
---------------------------------------------------------------------
2
(1 row)
DROP TABLE t;

View File

@ -0,0 +1,179 @@
CREATE SCHEMA am_tableoptions;
SET search_path TO am_tableoptions;
CREATE TABLE table_options (a int) USING cstore_tableam;
INSERT INTO table_options SELECT generate_series(1,100);
-- show table_options settings
SELECT * FROM cstore.cstore_options
WHERE regclass = 'table_options'::regclass;
regclass | block_row_count | stripe_row_count | compression
---------------------------------------------------------------------
table_options | 10000 | 150000 | none
(1 row)
-- test changing the compression
SELECT alter_cstore_table_set('table_options', compression => 'pglz');
alter_cstore_table_set
---------------------------------------------------------------------
(1 row)
-- show table_options settings
SELECT * FROM cstore.cstore_options
WHERE regclass = 'table_options'::regclass;
regclass | block_row_count | stripe_row_count | compression
---------------------------------------------------------------------
table_options | 10000 | 150000 | pglz
(1 row)
-- test changing the block_row_count
SELECT alter_cstore_table_set('table_options', block_row_count => 10);
alter_cstore_table_set
---------------------------------------------------------------------
(1 row)
-- show table_options settings
SELECT * FROM cstore.cstore_options
WHERE regclass = 'table_options'::regclass;
regclass | block_row_count | stripe_row_count | compression
---------------------------------------------------------------------
table_options | 10 | 150000 | pglz
(1 row)
-- test changing the block_row_count
SELECT alter_cstore_table_set('table_options', stripe_row_count => 100);
alter_cstore_table_set
---------------------------------------------------------------------
(1 row)
-- show table_options settings
SELECT * FROM cstore.cstore_options
WHERE regclass = 'table_options'::regclass;
regclass | block_row_count | stripe_row_count | compression
---------------------------------------------------------------------
table_options | 10 | 100 | pglz
(1 row)
-- VACUUM FULL creates a new table, make sure it copies settings from the table you are vacuuming
VACUUM FULL table_options;
-- show table_options settings
SELECT * FROM cstore.cstore_options
WHERE regclass = 'table_options'::regclass;
regclass | block_row_count | stripe_row_count | compression
---------------------------------------------------------------------
table_options | 10 | 100 | pglz
(1 row)
-- set all settings at the same time
SELECT alter_cstore_table_set('table_options', stripe_row_count => 1000, block_row_count => 100, compression => 'none');
alter_cstore_table_set
---------------------------------------------------------------------
(1 row)
-- show table_options settings
SELECT * FROM cstore.cstore_options
WHERE regclass = 'table_options'::regclass;
regclass | block_row_count | stripe_row_count | compression
---------------------------------------------------------------------
table_options | 100 | 1000 | none
(1 row)
-- reset settings one by one to the version of the GUC's
SET cstore.block_row_count TO 1000;
SET cstore.stripe_row_count TO 10000;
SET cstore.compression TO 'pglz';
-- verify setting the GUC's didn't change the settings
-- show table_options settings
SELECT * FROM cstore.cstore_options
WHERE regclass = 'table_options'::regclass;
regclass | block_row_count | stripe_row_count | compression
---------------------------------------------------------------------
table_options | 100 | 1000 | none
(1 row)
SELECT alter_cstore_table_reset('table_options', block_row_count => true);
alter_cstore_table_reset
---------------------------------------------------------------------
(1 row)
-- show table_options settings
SELECT * FROM cstore.cstore_options
WHERE regclass = 'table_options'::regclass;
regclass | block_row_count | stripe_row_count | compression
---------------------------------------------------------------------
table_options | 1000 | 1000 | none
(1 row)
SELECT alter_cstore_table_reset('table_options', stripe_row_count => true);
alter_cstore_table_reset
---------------------------------------------------------------------
(1 row)
-- show table_options settings
SELECT * FROM cstore.cstore_options
WHERE regclass = 'table_options'::regclass;
regclass | block_row_count | stripe_row_count | compression
---------------------------------------------------------------------
table_options | 1000 | 10000 | none
(1 row)
SELECT alter_cstore_table_reset('table_options', compression => true);
alter_cstore_table_reset
---------------------------------------------------------------------
(1 row)
-- show table_options settings
SELECT * FROM cstore.cstore_options
WHERE regclass = 'table_options'::regclass;
regclass | block_row_count | stripe_row_count | compression
---------------------------------------------------------------------
table_options | 1000 | 10000 | pglz
(1 row)
-- verify resetting all settings at once work
SET cstore.block_row_count TO 10000;
SET cstore.stripe_row_count TO 100000;
SET cstore.compression TO 'none';
-- show table_options settings
SELECT * FROM cstore.cstore_options
WHERE regclass = 'table_options'::regclass;
regclass | block_row_count | stripe_row_count | compression
---------------------------------------------------------------------
table_options | 1000 | 10000 | pglz
(1 row)
SELECT alter_cstore_table_reset(
'table_options',
block_row_count => true,
stripe_row_count => true,
compression => true);
alter_cstore_table_reset
---------------------------------------------------------------------
(1 row)
-- show table_options settings
SELECT * FROM cstore.cstore_options
WHERE regclass = 'table_options'::regclass;
regclass | block_row_count | stripe_row_count | compression
---------------------------------------------------------------------
table_options | 10000 | 100000 | none
(1 row)
-- verify edge cases
-- first start with a table that is not a cstore table
CREATE TABLE not_a_cstore_table (a int);
SELECT alter_cstore_table_set('not_a_cstore_table', compression => 'pglz');
ERROR: table not_a_cstore_table is not a cstore table
SELECT alter_cstore_table_reset('not_a_cstore_table', compression => true);
ERROR: table not_a_cstore_table is not a cstore table
-- verify you can't use a compression that is not known
SELECT alter_cstore_table_set('table_options', compression => 'foobar');
ERROR: unknown compression type for cstore table: foobar
SET client_min_messages TO warning;
DROP SCHEMA am_tableoptions CASCADE;

View File

@ -0,0 +1,77 @@
create or replace function trs_before() returns trigger language plpgsql as $$
BEGIN
RAISE NOTICE 'BEFORE STATEMENT %', TG_OP;
RETURN NULL;
END;
$$;
create or replace function trs_after() returns trigger language plpgsql as $$
DECLARE
r RECORD;
BEGIN
RAISE NOTICE 'AFTER STATEMENT %', TG_OP;
IF (TG_OP = 'DELETE') THEN
FOR R IN select * from old_table
LOOP
RAISE NOTICE ' (%)', r.i;
END LOOP;
ELSE
FOR R IN select * from new_table
LOOP
RAISE NOTICE ' (%)', r.i;
END LOOP;
END IF;
RETURN NULL;
END;
$$;
create or replace function trr_before() returns trigger language plpgsql as $$
BEGIN
RAISE NOTICE 'BEFORE ROW %: (%)', TG_OP, NEW.i;
RETURN NEW;
END;
$$;
create or replace function trr_after() returns trigger language plpgsql as $$
BEGIN
RAISE NOTICE 'AFTER ROW %: (%)', TG_OP, NEW.i;
RETURN NEW;
END;
$$;
create table test_tr(i int) using cstore_tableam;
create trigger tr_before_stmt before insert on test_tr
for each statement execute procedure trs_before();
create trigger tr_after_stmt after insert on test_tr
referencing new table as new_table
for each statement execute procedure trs_after();
create trigger tr_before_row before insert on test_tr
for each row execute procedure trr_before();
-- after triggers require TIDs, which are not supported yet
create trigger tr_after_row after insert on test_tr
for each row execute procedure trr_after();
ERROR: AFTER ROW triggers are not supported for columnstore access method
HINT: Consider an AFTER STATEMENT trigger instead.
insert into test_tr values(1);
NOTICE: BEFORE STATEMENT INSERT
CONTEXT: PL/pgSQL function trs_before() line 3 at RAISE
NOTICE: BEFORE ROW INSERT: (1)
CONTEXT: PL/pgSQL function trr_before() line 3 at RAISE
NOTICE: AFTER STATEMENT INSERT
CONTEXT: PL/pgSQL function trs_after() line 5 at RAISE
NOTICE: (1)
CONTEXT: PL/pgSQL function trs_after() line 14 at RAISE
insert into test_tr values(2),(3),(4);
NOTICE: BEFORE STATEMENT INSERT
CONTEXT: PL/pgSQL function trs_before() line 3 at RAISE
NOTICE: BEFORE ROW INSERT: (2)
CONTEXT: PL/pgSQL function trr_before() line 3 at RAISE
NOTICE: BEFORE ROW INSERT: (3)
CONTEXT: PL/pgSQL function trr_before() line 3 at RAISE
NOTICE: BEFORE ROW INSERT: (4)
CONTEXT: PL/pgSQL function trr_before() line 3 at RAISE
NOTICE: AFTER STATEMENT INSERT
CONTEXT: PL/pgSQL function trs_after() line 5 at RAISE
NOTICE: (2)
CONTEXT: PL/pgSQL function trs_after() line 14 at RAISE
NOTICE: (3)
CONTEXT: PL/pgSQL function trs_after() line 14 at RAISE
NOTICE: (4)
CONTEXT: PL/pgSQL function trs_after() line 14 at RAISE
drop table test_tr;

View File

@ -0,0 +1,273 @@
--
-- Test the TRUNCATE TABLE command for cstore_fdw tables.
--
-- print whether we're using version > 10 to make version-specific tests clear
SHOW server_version \gset
SELECT substring(:'server_version', '\d+')::int > 10 AS version_above_ten;
version_above_ten
---------------------------------------------------------------------
t
(1 row)
-- CREATE a cstore_fdw table, fill with some data --
CREATE TABLE cstore_truncate_test (a int, b int) USING cstore_tableam;
CREATE TABLE cstore_truncate_test_second (a int, b int) USING cstore_tableam;
-- COMPRESSED
CREATE TABLE cstore_truncate_test_compressed (a int, b int) USING cstore_tableam;
CREATE TABLE cstore_truncate_test_regular (a int, b int);
SELECT count(*) AS cstore_data_files_before_truncate FROM cstore.cstore_data_files \gset
INSERT INTO cstore_truncate_test select a, a from generate_series(1, 10) a;
set cstore.compression = 'pglz';
INSERT INTO cstore_truncate_test_compressed select a, a from generate_series(1, 10) a;
INSERT INTO cstore_truncate_test_compressed select a, a from generate_series(1, 10) a;
set cstore.compression to default;
-- query rows
SELECT * FROM cstore_truncate_test;
a | b
---------------------------------------------------------------------
1 | 1
2 | 2
3 | 3
4 | 4
5 | 5
6 | 6
7 | 7
8 | 8
9 | 9
10 | 10
(10 rows)
TRUNCATE TABLE cstore_truncate_test;
SELECT * FROM cstore_truncate_test;
a | b
---------------------------------------------------------------------
(0 rows)
SELECT COUNT(*) from cstore_truncate_test;
count
---------------------------------------------------------------------
0
(1 row)
SELECT count(*) FROM cstore_truncate_test_compressed;
count
---------------------------------------------------------------------
20
(1 row)
TRUNCATE TABLE cstore_truncate_test_compressed;
SELECT count(*) FROM cstore_truncate_test_compressed;
count
---------------------------------------------------------------------
0
(1 row)
SELECT pg_relation_size('cstore_truncate_test_compressed');
pg_relation_size
---------------------------------------------------------------------
0
(1 row)
INSERT INTO cstore_truncate_test select a, a from generate_series(1, 10) a;
INSERT INTO cstore_truncate_test_regular select a, a from generate_series(10, 20) a;
INSERT INTO cstore_truncate_test_second select a, a from generate_series(20, 30) a;
SELECT * from cstore_truncate_test;
a | b
---------------------------------------------------------------------
1 | 1
2 | 2
3 | 3
4 | 4
5 | 5
6 | 6
7 | 7
8 | 8
9 | 9
10 | 10
(10 rows)
SELECT * from cstore_truncate_test_second;
a | b
---------------------------------------------------------------------
20 | 20
21 | 21
22 | 22
23 | 23
24 | 24
25 | 25
26 | 26
27 | 27
28 | 28
29 | 29
30 | 30
(11 rows)
SELECT * from cstore_truncate_test_regular;
a | b
---------------------------------------------------------------------
10 | 10
11 | 11
12 | 12
13 | 13
14 | 14
15 | 15
16 | 16
17 | 17
18 | 18
19 | 19
20 | 20
(11 rows)
-- make sure multi truncate works
-- notice that the same table might be repeated
TRUNCATE TABLE cstore_truncate_test,
cstore_truncate_test_regular,
cstore_truncate_test_second,
cstore_truncate_test;
SELECT * from cstore_truncate_test;
a | b
---------------------------------------------------------------------
(0 rows)
SELECT * from cstore_truncate_test_second;
a | b
---------------------------------------------------------------------
(0 rows)
SELECT * from cstore_truncate_test_regular;
a | b
---------------------------------------------------------------------
(0 rows)
-- test if truncate on empty table works
TRUNCATE TABLE cstore_truncate_test;
SELECT * from cstore_truncate_test;
a | b
---------------------------------------------------------------------
(0 rows)
-- make sure TRUNATE deletes metadata for old relfilenode
SELECT :cstore_data_files_before_truncate - count(*) FROM cstore.cstore_data_files;
?column?
---------------------------------------------------------------------
0
(1 row)
-- test if truncation in the same transaction that created the table works properly
BEGIN;
CREATE TABLE cstore_same_transaction_truncate(a int) USING cstore_tableam;
INSERT INTO cstore_same_transaction_truncate SELECT * FROM generate_series(1, 100);
TRUNCATE cstore_same_transaction_truncate;
INSERT INTO cstore_same_transaction_truncate SELECT * FROM generate_series(20, 23);
COMMIT;
-- should output "1" for the newly created relation
SELECT count(*) - :cstore_data_files_before_truncate FROM cstore.cstore_data_files;
?column?
---------------------------------------------------------------------
1
(1 row)
SELECT * FROM cstore_same_transaction_truncate;
a
---------------------------------------------------------------------
20
21
22
23
(4 rows)
DROP TABLE cstore_same_transaction_truncate;
-- test if a cached truncate from a pl/pgsql function works
CREATE FUNCTION cstore_truncate_test_regular_func() RETURNS void AS $$
BEGIN
INSERT INTO cstore_truncate_test_regular select a, a from generate_series(1, 10) a;
TRUNCATE TABLE cstore_truncate_test_regular;
END;$$
LANGUAGE plpgsql;
SELECT cstore_truncate_test_regular_func();
cstore_truncate_test_regular_func
---------------------------------------------------------------------
(1 row)
-- the cached plans are used stating from the second call
SELECT cstore_truncate_test_regular_func();
cstore_truncate_test_regular_func
---------------------------------------------------------------------
(1 row)
DROP FUNCTION cstore_truncate_test_regular_func();
DROP TABLE cstore_truncate_test, cstore_truncate_test_second;
DROP TABLE cstore_truncate_test_regular;
DROP TABLE cstore_truncate_test_compressed;
-- test truncate with schema
CREATE SCHEMA truncate_schema;
-- COMPRESSED
CREATE TABLE truncate_schema.truncate_tbl (id int) USING cstore_tableam;
set cstore.compression = 'pglz';
INSERT INTO truncate_schema.truncate_tbl SELECT generate_series(1, 100);
set cstore.compression to default;
SELECT COUNT(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
TRUNCATE TABLE truncate_schema.truncate_tbl;
SELECT COUNT(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
0
(1 row)
set cstore.compression = 'pglz';
INSERT INTO truncate_schema.truncate_tbl SELECT generate_series(1, 100);
set cstore.compression to default;
-- create a user that can not truncate
CREATE USER truncate_user;
NOTICE: not propagating CREATE ROLE/USER commands to worker nodes
HINT: Connect to worker nodes directly to manually create all necessary users and roles.
GRANT USAGE ON SCHEMA truncate_schema TO truncate_user;
GRANT SELECT ON TABLE truncate_schema.truncate_tbl TO truncate_user;
REVOKE TRUNCATE ON TABLE truncate_schema.truncate_tbl FROM truncate_user;
SELECT current_user \gset
\c - truncate_user
-- verify truncate command fails and check number of rows
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
TRUNCATE TABLE truncate_schema.truncate_tbl;
ERROR: permission denied for table truncate_tbl
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
-- switch to super user, grant truncate to truncate_user
\c - :current_user
GRANT TRUNCATE ON TABLE truncate_schema.truncate_tbl TO truncate_user;
-- verify truncate_user can truncate now
\c - truncate_user
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
TRUNCATE TABLE truncate_schema.truncate_tbl;
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
0
(1 row)
\c - :current_user
-- cleanup
DROP SCHEMA truncate_schema CASCADE;
NOTICE: drop cascades to table truncate_schema.truncate_tbl
DROP USER truncate_user;

View File

@ -0,0 +1,262 @@
--
-- Test the TRUNCATE TABLE command for cstore_fdw tables.
--
-- print whether we're using version > 10 to make version-specific tests clear
SHOW server_version \gset
SELECT substring(:'server_version', '\d+')::int > 10 AS version_above_ten;
version_above_ten
---------------------------------------------------------------------
f
(1 row)
-- Check that files for the automatically managed table exist in the
-- cstore_fdw/{databaseoid} directory.
SELECT count(*) FROM (
SELECT pg_ls_dir('cstore_fdw/' || databaseoid ) FROM (
SELECT oid::text databaseoid FROM pg_database WHERE datname = current_database()
) AS q1) AS q2;
count
---------------------------------------------------------------------
0
(1 row)
-- CREATE a cstore_fdw table, fill with some data --
CREATE FOREIGN TABLE cstore_truncate_test (a int, b int) SERVER cstore_server;
CREATE FOREIGN TABLE cstore_truncate_test_second (a int, b int) SERVER cstore_server;
CREATE FOREIGN TABLE cstore_truncate_test_compressed (a int, b int) SERVER cstore_server OPTIONS (compression 'pglz');
CREATE TABLE cstore_truncate_test_regular (a int, b int);
INSERT INTO cstore_truncate_test select a, a from generate_series(1, 10) a;
INSERT INTO cstore_truncate_test_compressed select a, a from generate_series(1, 10) a;
INSERT INTO cstore_truncate_test_compressed select a, a from generate_series(1, 10) a;
-- query rows
SELECT * FROM cstore_truncate_test;
a | b
---------------------------------------------------------------------
1 | 1
2 | 2
3 | 3
4 | 4
5 | 5
6 | 6
7 | 7
8 | 8
9 | 9
10 | 10
(10 rows)
TRUNCATE TABLE cstore_truncate_test;
SELECT * FROM cstore_truncate_test;
a | b
---------------------------------------------------------------------
(0 rows)
SELECT COUNT(*) from cstore_truncate_test;
count
---------------------------------------------------------------------
0
(1 row)
SELECT count(*) FROM cstore_truncate_test_compressed;
count
---------------------------------------------------------------------
20
(1 row)
TRUNCATE TABLE cstore_truncate_test_compressed;
SELECT count(*) FROM cstore_truncate_test_compressed;
count
---------------------------------------------------------------------
0
(1 row)
SELECT cstore_table_size('cstore_truncate_test_compressed');
cstore_table_size
---------------------------------------------------------------------
26
(1 row)
-- make sure data files still present
SELECT count(*) FROM (
SELECT pg_ls_dir('cstore_fdw/' || databaseoid ) FROM (
SELECT oid::text databaseoid FROM pg_database WHERE datname = current_database()
) AS q1) AS q2;
count
---------------------------------------------------------------------
6
(1 row)
INSERT INTO cstore_truncate_test select a, a from generate_series(1, 10) a;
INSERT INTO cstore_truncate_test_regular select a, a from generate_series(10, 20) a;
INSERT INTO cstore_truncate_test_second select a, a from generate_series(20, 30) a;
SELECT * from cstore_truncate_test;
a | b
---------------------------------------------------------------------
1 | 1
2 | 2
3 | 3
4 | 4
5 | 5
6 | 6
7 | 7
8 | 8
9 | 9
10 | 10
(10 rows)
SELECT * from cstore_truncate_test_second;
a | b
---------------------------------------------------------------------
20 | 20
21 | 21
22 | 22
23 | 23
24 | 24
25 | 25
26 | 26
27 | 27
28 | 28
29 | 29
30 | 30
(11 rows)
SELECT * from cstore_truncate_test_regular;
a | b
---------------------------------------------------------------------
10 | 10
11 | 11
12 | 12
13 | 13
14 | 14
15 | 15
16 | 16
17 | 17
18 | 18
19 | 19
20 | 20
(11 rows)
-- make sure multi truncate works
-- notice that the same table might be repeated
TRUNCATE TABLE cstore_truncate_test,
cstore_truncate_test_regular,
cstore_truncate_test_second,
cstore_truncate_test;
SELECT * from cstore_truncate_test;
a | b
---------------------------------------------------------------------
(0 rows)
SELECT * from cstore_truncate_test_second;
a | b
---------------------------------------------------------------------
(0 rows)
SELECT * from cstore_truncate_test_regular;
a | b
---------------------------------------------------------------------
(0 rows)
-- test if truncate on empty table works
TRUNCATE TABLE cstore_truncate_test;
SELECT * from cstore_truncate_test;
a | b
---------------------------------------------------------------------
(0 rows)
-- test if a cached truncate from a pl/pgsql function works
CREATE FUNCTION cstore_truncate_test_regular_func() RETURNS void AS $$
BEGIN
INSERT INTO cstore_truncate_test_regular select a, a from generate_series(1, 10) a;
TRUNCATE TABLE cstore_truncate_test_regular;
END;$$
LANGUAGE plpgsql;
SELECT cstore_truncate_test_regular_func();
cstore_truncate_test_regular_func
---------------------------------------------------------------------
(1 row)
-- the cached plans are used stating from the second call
SELECT cstore_truncate_test_regular_func();
cstore_truncate_test_regular_func
---------------------------------------------------------------------
(1 row)
DROP FUNCTION cstore_truncate_test_regular_func();
DROP FOREIGN TABLE cstore_truncate_test, cstore_truncate_test_second;
DROP TABLE cstore_truncate_test_regular;
DROP FOREIGN TABLE cstore_truncate_test_compressed;
-- test truncate with schema
CREATE SCHEMA truncate_schema;
CREATE FOREIGN TABLE truncate_schema.truncate_tbl (id int) SERVER cstore_server OPTIONS(compression 'pglz');
INSERT INTO truncate_schema.truncate_tbl SELECT generate_series(1, 100);
SELECT COUNT(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
TRUNCATE TABLE truncate_schema.truncate_tbl;
SELECT COUNT(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
0
(1 row)
INSERT INTO truncate_schema.truncate_tbl SELECT generate_series(1, 100);
-- create a user that can not truncate
CREATE USER truncate_user;
GRANT USAGE ON SCHEMA truncate_schema TO truncate_user;
GRANT SELECT ON TABLE truncate_schema.truncate_tbl TO truncate_user;
REVOKE TRUNCATE ON TABLE truncate_schema.truncate_tbl FROM truncate_user;
SELECT current_user \gset
\c - truncate_user
-- verify truncate command fails and check number of rows
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
TRUNCATE TABLE truncate_schema.truncate_tbl;
ERROR: permission denied for relation truncate_tbl
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
-- switch to super user, grant truncate to truncate_user
\c - :current_user
GRANT TRUNCATE ON TABLE truncate_schema.truncate_tbl TO truncate_user;
-- verify truncate_user can truncate now
\c - truncate_user
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
TRUNCATE TABLE truncate_schema.truncate_tbl;
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
0
(1 row)
\c - :current_user
-- cleanup
DROP SCHEMA truncate_schema CASCADE;
NOTICE: drop cascades to foreign table truncate_schema.truncate_tbl
DROP USER truncate_user;
-- verify files are removed
SELECT count(*) FROM (
SELECT pg_ls_dir('cstore_fdw/' || databaseoid ) FROM (
SELECT oid::text databaseoid FROM pg_database WHERE datname = current_database()
) AS q1) AS q2;
count
---------------------------------------------------------------------
0
(1 row)

View File

@ -0,0 +1,234 @@
SELECT count(*) AS columnar_table_count FROM cstore.cstore_data_files \gset
CREATE TABLE t(a int, b int) USING cstore_tableam;
SELECT count(*) FROM cstore.cstore_stripes a, pg_class b WHERE a.relfilenode=b.relfilenode AND b.relname='t';
count
---------------------------------------------------------------------
0
(1 row)
INSERT INTO t SELECT i, i * i FROM generate_series(1, 10) i;
INSERT INTO t SELECT i, i * i FROM generate_series(11, 20) i;
INSERT INTO t SELECT i, i * i FROM generate_series(21, 30) i;
SELECT sum(a), sum(b) FROM t;
sum | sum
---------------------------------------------------------------------
465 | 9455
(1 row)
SELECT count(*) FROM cstore.cstore_stripes a, pg_class b WHERE a.relfilenode=b.relfilenode AND b.relname='t';
count
---------------------------------------------------------------------
3
(1 row)
-- vacuum full should merge stripes together
VACUUM FULL t;
SELECT sum(a), sum(b) FROM t;
sum | sum
---------------------------------------------------------------------
465 | 9455
(1 row)
SELECT count(*) FROM cstore.cstore_stripes a, pg_class b WHERE a.relfilenode=b.relfilenode AND b.relname='t';
count
---------------------------------------------------------------------
1
(1 row)
-- test the case when all data cannot fit into a single stripe
SELECT alter_cstore_table_set('t', stripe_row_count => 1000);
alter_cstore_table_set
---------------------------------------------------------------------
(1 row)
INSERT INTO t SELECT i, 2 * i FROM generate_series(1,2500) i;
SELECT sum(a), sum(b) FROM t;
sum | sum
---------------------------------------------------------------------
3126715 | 6261955
(1 row)
SELECT count(*) FROM cstore.cstore_stripes a, pg_class b WHERE a.relfilenode=b.relfilenode AND b.relname='t';
count
---------------------------------------------------------------------
4
(1 row)
VACUUM FULL t;
SELECT sum(a), sum(b) FROM t;
sum | sum
---------------------------------------------------------------------
3126715 | 6261955
(1 row)
SELECT count(*) FROM cstore.cstore_stripes a, pg_class b WHERE a.relfilenode=b.relfilenode AND b.relname='t';
count
---------------------------------------------------------------------
3
(1 row)
-- VACUUM FULL doesn't reclaim dropped columns, but converts them to NULLs
ALTER TABLE t DROP COLUMN a;
SELECT stripe, attr, block, minimum_value IS NULL, maximum_value IS NULL FROM cstore.cstore_skipnodes a, pg_class b WHERE a.relfilenode=b.relfilenode AND b.relname='t' ORDER BY 1, 2, 3;
stripe | attr | block | ?column? | ?column?
---------------------------------------------------------------------
1 | 1 | 0 | f | f
1 | 2 | 0 | f | f
2 | 1 | 0 | f | f
2 | 2 | 0 | f | f
3 | 1 | 0 | f | f
3 | 2 | 0 | f | f
(6 rows)
VACUUM FULL t;
SELECT stripe, attr, block, minimum_value IS NULL, maximum_value IS NULL FROM cstore.cstore_skipnodes a, pg_class b WHERE a.relfilenode=b.relfilenode AND b.relname='t' ORDER BY 1, 2, 3;
stripe | attr | block | ?column? | ?column?
---------------------------------------------------------------------
1 | 1 | 0 | t | t
1 | 2 | 0 | f | f
2 | 1 | 0 | t | t
2 | 2 | 0 | f | f
3 | 1 | 0 | t | t
3 | 2 | 0 | f | f
(6 rows)
-- Make sure we cleaned-up the transient table metadata after VACUUM FULL commands
SELECT count(*) - :columnar_table_count FROM cstore.cstore_data_files;
?column?
---------------------------------------------------------------------
1
(1 row)
-- do this in a transaction so concurrent autovacuum doesn't interfere with results
BEGIN;
SAVEPOINT s1;
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
2530
(1 row)
SELECT pg_size_pretty(pg_relation_size('t'));
pg_size_pretty
---------------------------------------------------------------------
32 kB
(1 row)
INSERT INTO t SELECT i FROM generate_series(1, 10000) i;
SELECT pg_size_pretty(pg_relation_size('t'));
pg_size_pretty
---------------------------------------------------------------------
112 kB
(1 row)
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
12530
(1 row)
ROLLBACK TO SAVEPOINT s1;
-- not truncated by VACUUM or autovacuum yet (being in transaction ensures this),
-- so relation size should be same as before.
SELECT pg_size_pretty(pg_relation_size('t'));
pg_size_pretty
---------------------------------------------------------------------
112 kB
(1 row)
COMMIT;
-- vacuum should truncate the relation to the usable space
VACUUM VERBOSE t;
INFO: statistics for "t":
total file size: 114688, total data size: 10754
total row count: 2530, stripe count: 3, average rows per stripe: 843
block count: 3, containing data for dropped columns: 0, none compressed: 3, pglz compressed: 0
INFO: "t": truncated 14 to 4 pages
DETAIL: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s
SELECT pg_size_pretty(pg_relation_size('t'));
pg_size_pretty
---------------------------------------------------------------------
32 kB
(1 row)
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
2530
(1 row)
-- add some stripes with different compression types and create some gaps,
-- then vacuum to print stats
BEGIN;
SELECT alter_cstore_table_set('t',
block_row_count => 1000,
stripe_row_count => 2000,
compression => 'pglz');
alter_cstore_table_set
---------------------------------------------------------------------
(1 row)
SAVEPOINT s1;
INSERT INTO t SELECT i FROM generate_series(1, 1500) i;
ROLLBACK TO SAVEPOINT s1;
INSERT INTO t SELECT i / 5 FROM generate_series(1, 1500) i;
SELECT alter_cstore_table_set('t', compression => 'none');
alter_cstore_table_set
---------------------------------------------------------------------
(1 row)
SAVEPOINT s2;
INSERT INTO t SELECT i FROM generate_series(1, 1500) i;
ROLLBACK TO SAVEPOINT s2;
INSERT INTO t SELECT i / 5 FROM generate_series(1, 1500) i;
COMMIT;
VACUUM VERBOSE t;
INFO: statistics for "t":
total file size: 49152, total data size: 18808
total row count: 5530, stripe count: 5, average rows per stripe: 1106
block count: 7, containing data for dropped columns: 0, none compressed: 5, pglz compressed: 2
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
5530
(1 row)
-- check that we report blocks with data for dropped columns
ALTER TABLE t ADD COLUMN c int;
INSERT INTO t SELECT 1, i / 5 FROM generate_series(1, 1500) i;
ALTER TABLE t DROP COLUMN c;
VACUUM VERBOSE t;
INFO: statistics for "t":
total file size: 65536, total data size: 31372
total row count: 7030, stripe count: 6, average rows per stripe: 1171
block count: 11, containing data for dropped columns: 2, none compressed: 9, pglz compressed: 2
-- vacuum full should remove blocks for dropped columns
-- note that, a block will be stored in non-compressed for if compression
-- doesn't reduce its size.
SELECT alter_cstore_table_set('t', compression => 'pglz');
alter_cstore_table_set
---------------------------------------------------------------------
(1 row)
VACUUM FULL t;
VACUUM VERBOSE t;
INFO: statistics for "t":
total file size: 49152, total data size: 15728
total row count: 7030, stripe count: 4, average rows per stripe: 1757
block count: 8, containing data for dropped columns: 0, none compressed: 2, pglz compressed: 6
DROP TABLE t;
-- Make sure we cleaned the metadata for t too
SELECT count(*) - :columnar_table_count FROM cstore.cstore_data_files;
?column?
---------------------------------------------------------------------
0
(1 row)

View File

@ -0,0 +1,68 @@
Parsed test spec with 2 sessions
starting permutation: s1-insert s1-begin s1-insert s2-vacuum s1-commit s2-select
step s1-insert:
INSERT INTO test_vacuum_vs_insert SELECT i, 2 * i FROM generate_series(1, 3) i;
step s1-begin:
BEGIN;
step s1-insert:
INSERT INTO test_vacuum_vs_insert SELECT i, 2 * i FROM generate_series(1, 3) i;
s2: INFO: statistics for "test_vacuum_vs_insert":
total file size: 24576, total data size: 26
total row count: 3, stripe count: 1, average rows per stripe: 3
block count: 2, containing data for dropped columns: 0, none compressed: 2, pglz compressed: 0
s2: INFO: "test_vacuum_vs_insert": stopping truncate due to conflicting lock request
step s2-vacuum:
VACUUM VERBOSE test_vacuum_vs_insert;
step s1-commit:
COMMIT;
step s2-select:
SELECT * FROM test_vacuum_vs_insert;
a b
1 2
2 4
3 6
1 2
2 4
3 6
starting permutation: s1-insert s1-begin s1-insert s2-vacuum-full s1-commit s2-select
step s1-insert:
INSERT INTO test_vacuum_vs_insert SELECT i, 2 * i FROM generate_series(1, 3) i;
step s1-begin:
BEGIN;
step s1-insert:
INSERT INTO test_vacuum_vs_insert SELECT i, 2 * i FROM generate_series(1, 3) i;
step s2-vacuum-full:
VACUUM FULL VERBOSE test_vacuum_vs_insert;
<waiting ...>
step s1-commit:
COMMIT;
s2: INFO: vacuuming "public.test_vacuum_vs_insert"
s2: INFO: "test_vacuum_vs_insert": found 0 removable, 6 nonremovable row versions in 3 pages
DETAIL: 0 dead row versions cannot be removed yet.
CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s.
step s2-vacuum-full: <... completed>
step s2-select:
SELECT * FROM test_vacuum_vs_insert;
a b
1 2
2 4
3 6
1 2
2 4
3 6

View File

@ -0,0 +1,142 @@
Parsed test spec with 2 sessions
starting permutation: s1-begin s2-begin s1-insert s2-insert s1-select s2-select s1-commit s2-commit s1-select
step s1-begin:
BEGIN;
step s2-begin:
BEGIN;
step s1-insert:
INSERT INTO test_insert_concurrency SELECT i, 2 * i FROM generate_series(1, 3) i;
step s2-insert:
INSERT INTO test_insert_concurrency SELECT i, 2 * i FROM generate_series(4, 6) i;
step s1-select:
SELECT * FROM test_insert_concurrency ORDER BY a;
a b
1 2
2 4
3 6
step s2-select:
SELECT * FROM test_insert_concurrency ORDER BY a;
a b
4 8
5 10
6 12
step s1-commit:
COMMIT;
step s2-commit:
COMMIT;
step s1-select:
SELECT * FROM test_insert_concurrency ORDER BY a;
a b
1 2
2 4
3 6
4 8
5 10
6 12
starting permutation: s1-begin s2-begin s1-copy s2-insert s1-select s2-select s1-commit s2-commit s1-select
step s1-begin:
BEGIN;
step s2-begin:
BEGIN;
step s1-copy:
COPY test_insert_concurrency(a) FROM PROGRAM 'seq 11 13';
step s2-insert:
INSERT INTO test_insert_concurrency SELECT i, 2 * i FROM generate_series(4, 6) i;
step s1-select:
SELECT * FROM test_insert_concurrency ORDER BY a;
a b
11
12
13
step s2-select:
SELECT * FROM test_insert_concurrency ORDER BY a;
a b
4 8
5 10
6 12
step s1-commit:
COMMIT;
step s2-commit:
COMMIT;
step s1-select:
SELECT * FROM test_insert_concurrency ORDER BY a;
a b
4 8
5 10
6 12
11
12
13
starting permutation: s1-begin s2-begin s2-insert s1-copy s1-select s2-select s1-commit s2-commit s1-select
step s1-begin:
BEGIN;
step s2-begin:
BEGIN;
step s2-insert:
INSERT INTO test_insert_concurrency SELECT i, 2 * i FROM generate_series(4, 6) i;
step s1-copy:
COPY test_insert_concurrency(a) FROM PROGRAM 'seq 11 13';
step s1-select:
SELECT * FROM test_insert_concurrency ORDER BY a;
a b
11
12
13
step s2-select:
SELECT * FROM test_insert_concurrency ORDER BY a;
a b
4 8
5 10
6 12
step s1-commit:
COMMIT;
step s2-commit:
COMMIT;
step s1-select:
SELECT * FROM test_insert_concurrency ORDER BY a;
a b
4 8
5 10
6 12
11
12
13

View File

@ -0,0 +1,6 @@
Parsed test spec with 1 sessions
starting permutation: s1a
step s1a:
CREATE EXTENSION cstore_fdw;

View File

@ -0,0 +1,174 @@
--
-- Testing ALTER TABLE on cstore_fdw tables.
--
CREATE FOREIGN TABLE test_alter_table (a int, b int, c int) SERVER cstore_server;
WITH sample_data AS (VALUES
(1, 2, 3),
(4, 5, 6),
(7, 8, 9)
)
INSERT INTO test_alter_table SELECT * FROM sample_data;
-- drop a column
ALTER FOREIGN TABLE test_alter_table DROP COLUMN a;
-- test analyze
ANALYZE test_alter_table;
-- verify select queries run as expected
SELECT * FROM test_alter_table;
b | c
---------------------------------------------------------------------
2 | 3
5 | 6
8 | 9
(3 rows)
SELECT a FROM test_alter_table;
ERROR: column "a" does not exist
SELECT b FROM test_alter_table;
b
---------------------------------------------------------------------
2
5
8
(3 rows)
-- verify insert runs as expected
INSERT INTO test_alter_table (SELECT 3, 5, 8);
ERROR: INSERT has more expressions than target columns
INSERT INTO test_alter_table (SELECT 5, 8);
-- add a column with no defaults
ALTER FOREIGN TABLE test_alter_table ADD COLUMN d int;
SELECT * FROM test_alter_table;
b | c | d
---------------------------------------------------------------------
2 | 3 |
5 | 6 |
8 | 9 |
5 | 8 |
(4 rows)
INSERT INTO test_alter_table (SELECT 3, 5, 8);
SELECT * FROM test_alter_table;
b | c | d
---------------------------------------------------------------------
2 | 3 |
5 | 6 |
8 | 9 |
5 | 8 |
3 | 5 | 8
(5 rows)
-- add a fixed-length column with default value
ALTER FOREIGN TABLE test_alter_table ADD COLUMN e int default 3;
SELECT * from test_alter_table;
b | c | d | e
---------------------------------------------------------------------
2 | 3 | | 3
5 | 6 | | 3
8 | 9 | | 3
5 | 8 | | 3
3 | 5 | 8 | 3
(5 rows)
INSERT INTO test_alter_table (SELECT 1, 2, 4, 8);
SELECT * from test_alter_table;
b | c | d | e
---------------------------------------------------------------------
2 | 3 | | 3
5 | 6 | | 3
8 | 9 | | 3
5 | 8 | | 3
3 | 5 | 8 | 3
1 | 2 | 4 | 8
(6 rows)
-- add a variable-length column with default value
ALTER FOREIGN TABLE test_alter_table ADD COLUMN f text DEFAULT 'TEXT ME';
SELECT * from test_alter_table;
b | c | d | e | f
---------------------------------------------------------------------
2 | 3 | | 3 | TEXT ME
5 | 6 | | 3 | TEXT ME
8 | 9 | | 3 | TEXT ME
5 | 8 | | 3 | TEXT ME
3 | 5 | 8 | 3 | TEXT ME
1 | 2 | 4 | 8 | TEXT ME
(6 rows)
INSERT INTO test_alter_table (SELECT 1, 2, 4, 8, 'ABCDEF');
SELECT * from test_alter_table;
b | c | d | e | f
---------------------------------------------------------------------
2 | 3 | | 3 | TEXT ME
5 | 6 | | 3 | TEXT ME
8 | 9 | | 3 | TEXT ME
5 | 8 | | 3 | TEXT ME
3 | 5 | 8 | 3 | TEXT ME
1 | 2 | 4 | 8 | TEXT ME
1 | 2 | 4 | 8 | ABCDEF
(7 rows)
-- drop couple of columns
ALTER FOREIGN TABLE test_alter_table DROP COLUMN c;
ALTER FOREIGN TABLE test_alter_table DROP COLUMN e;
ANALYZE test_alter_table;
SELECT * from test_alter_table;
b | d | f
---------------------------------------------------------------------
2 | | TEXT ME
5 | | TEXT ME
8 | | TEXT ME
5 | | TEXT ME
3 | 8 | TEXT ME
1 | 4 | TEXT ME
1 | 4 | ABCDEF
(7 rows)
SELECT count(*) from test_alter_table;
count
---------------------------------------------------------------------
7
(1 row)
SELECT count(t.*) from test_alter_table t;
count
---------------------------------------------------------------------
7
(1 row)
-- unsupported default values
ALTER FOREIGN TABLE test_alter_table ADD COLUMN g boolean DEFAULT isfinite(current_date);
ALTER FOREIGN TABLE test_alter_table ADD COLUMN h DATE DEFAULT current_date;
SELECT * FROM test_alter_table;
ERROR: unsupported default value for column "g"
HINT: Expression is either mutable or does not evaluate to constant value
ALTER FOREIGN TABLE test_alter_table ALTER COLUMN g DROP DEFAULT;
SELECT * FROM test_alter_table;
ERROR: unsupported default value for column "h"
HINT: Expression is either mutable or does not evaluate to constant value
ALTER FOREIGN TABLE test_alter_table ALTER COLUMN h DROP DEFAULT;
ANALYZE test_alter_table;
SELECT * FROM test_alter_table;
b | d | f | g | h
---------------------------------------------------------------------
2 | | TEXT ME | |
5 | | TEXT ME | |
8 | | TEXT ME | |
5 | | TEXT ME | |
3 | 8 | TEXT ME | |
1 | 4 | TEXT ME | |
1 | 4 | ABCDEF | |
(7 rows)
-- unsupported type change
ALTER FOREIGN TABLE test_alter_table ADD COLUMN i int;
ALTER FOREIGN TABLE test_alter_table ADD COLUMN j float;
ALTER FOREIGN TABLE test_alter_table ADD COLUMN k text;
-- this is valid type change
ALTER FOREIGN TABLE test_alter_table ALTER COLUMN i TYPE float;
-- this is not valid
ALTER FOREIGN TABLE test_alter_table ALTER COLUMN j TYPE int;
ERROR: Column j cannot be cast automatically to type pg_catalog.int4
-- text / varchar conversion is valid both ways
ALTER FOREIGN TABLE test_alter_table ALTER COLUMN k TYPE varchar(20);
ALTER FOREIGN TABLE test_alter_table ALTER COLUMN k TYPE text;
DROP FOREIGN TABLE test_alter_table;

View File

@ -0,0 +1,19 @@
--
-- Test the ANALYZE command for cstore_fdw tables.
--
-- ANALYZE uncompressed table
ANALYZE contestant;
SELECT count(*) FROM pg_stats WHERE tablename='contestant';
count
---------------------------------------------------------------------
6
(1 row)
-- ANALYZE compressed table
ANALYZE contestant_compressed;
SELECT count(*) FROM pg_stats WHERE tablename='contestant_compressed';
count
---------------------------------------------------------------------
6
(1 row)

View File

@ -0,0 +1,10 @@
DROP FOREIGN TABLE collation_block_filtering_test;
DROP FOREIGN TABLE test_block_filtering;
DROP FOREIGN TABLE test_null_values;
DROP FOREIGN TABLE test_other_types;
DROP FOREIGN TABLE test_range_types;
DROP FOREIGN TABLE test_enum_and_composite_types;
DROP TYPE composite_type;
DROP TYPE enum_type;
DROP FOREIGN TABLE test_datetime_types;
DROP FOREIGN TABLE test_array_types;

View File

@ -0,0 +1,58 @@
--
-- Tests the different DROP commands for cstore_fdw tables.
--
-- DROP FOREIGN TABL
-- DROP SCHEMA
-- DROP EXTENSION
-- DROP DATABASE
--
-- Note that travis does not create
-- cstore_fdw extension in default database (postgres). This has caused
-- different behavior between travis tests and local tests. Thus
-- 'postgres' directory is excluded from comparison to have the same result.
-- store postgres database oid
SELECT oid postgres_oid FROM pg_database WHERE datname = 'postgres' \gset
SELECT count(*) AS cstore_data_files_before_drop FROM cstore.cstore_data_files \gset
-- DROP cstore_fdw tables
DROP FOREIGN TABLE contestant;
DROP FOREIGN TABLE contestant_compressed;
-- make sure DROP deletes metadata
SELECT :cstore_data_files_before_drop - count(*) FROM cstore.cstore_data_files;
?column?
---------------------------------------------------------------------
2
(1 row)
-- Create a cstore_fdw table under a schema and drop it.
CREATE SCHEMA test_schema;
CREATE FOREIGN TABLE test_schema.test_table(data int) SERVER cstore_server;
SELECT count(*) AS cstore_data_files_before_drop FROM cstore.cstore_data_files \gset
DROP SCHEMA test_schema CASCADE;
NOTICE: drop cascades to foreign table test_schema.test_table
SELECT :cstore_data_files_before_drop - count(*) FROM cstore.cstore_data_files;
?column?
---------------------------------------------------------------------
1
(1 row)
SELECT current_database() datname \gset
CREATE DATABASE db_to_drop;
NOTICE: Citus partially supports CREATE DATABASE for distributed databases
DETAIL: Citus does not propagate CREATE DATABASE command to workers
HINT: You can manually create a database and its extensions on workers.
\c db_to_drop
CREATE EXTENSION citus;
CREATE SERVER cstore_server FOREIGN DATA WRAPPER cstore_fdw;
SELECT oid::text databaseoid FROM pg_database WHERE datname = current_database() \gset
CREATE FOREIGN TABLE test_table(data int) SERVER cstore_server;
DROP EXTENSION citus CASCADE;
NOTICE: drop cascades to 2 other objects
DETAIL: drop cascades to server cstore_server
drop cascades to foreign table test_table
-- test database drop
CREATE EXTENSION citus;
CREATE SERVER cstore_server FOREIGN DATA WRAPPER cstore_fdw;
SELECT oid::text databaseoid FROM pg_database WHERE datname = current_database() \gset
CREATE FOREIGN TABLE test_table(data int) SERVER cstore_server;
\c :datname
DROP DATABASE db_to_drop;

View File

@ -0,0 +1,18 @@
--
-- Test utility functions for cstore_fdw tables.
--
CREATE FOREIGN TABLE empty_table (a int) SERVER cstore_server;
CREATE FOREIGN TABLE table_with_data (a int) SERVER cstore_server;
CREATE TABLE non_cstore_table (a int);
COPY table_with_data FROM STDIN;
SELECT cstore_table_size('empty_table') < cstore_table_size('table_with_data');
?column?
---------------------------------------------------------------------
t
(1 row)
SELECT cstore_table_size('non_cstore_table');
ERROR: relation is not a cstore table
DROP FOREIGN TABLE empty_table;
DROP FOREIGN TABLE table_with_data;
DROP TABLE non_cstore_table;

View File

@ -0,0 +1,88 @@
--
-- Testing insert on cstore_fdw tables.
--
CREATE FOREIGN TABLE test_insert_command (a int) SERVER cstore_server;
-- test single row inserts fail
select count(*) from test_insert_command;
count
---------------------------------------------------------------------
0
(1 row)
insert into test_insert_command values(1);
ERROR: operation is not supported
select count(*) from test_insert_command;
count
---------------------------------------------------------------------
0
(1 row)
insert into test_insert_command default values;
ERROR: operation is not supported
select count(*) from test_insert_command;
count
---------------------------------------------------------------------
0
(1 row)
-- test inserting from another table succeed
CREATE TABLE test_insert_command_data (a int);
select count(*) from test_insert_command_data;
count
---------------------------------------------------------------------
0
(1 row)
insert into test_insert_command_data values(1);
select count(*) from test_insert_command_data;
count
---------------------------------------------------------------------
1
(1 row)
insert into test_insert_command select * from test_insert_command_data;
select count(*) from test_insert_command;
count
---------------------------------------------------------------------
1
(1 row)
drop table test_insert_command_data;
drop foreign table test_insert_command;
-- test long attribute value insertion
-- create sufficiently long text so that data is stored in toast
CREATE TABLE test_long_text AS
SELECT a as int_val, string_agg(random()::text, '') as text_val
FROM generate_series(1, 10) a, generate_series(1, 1000) b
GROUP BY a ORDER BY a;
-- store hash values of text for later comparison
CREATE TABLE test_long_text_hash AS
SELECT int_val, md5(text_val) AS hash
FROM test_long_text;
CREATE FOREIGN TABLE test_cstore_long_text(int_val int, text_val text)
SERVER cstore_server;
-- store long text in cstore table
INSERT INTO test_cstore_long_text SELECT * FROM test_long_text;
-- drop source table to remove original text from toast
DROP TABLE test_long_text;
-- check if text data is still available in cstore table
-- by comparing previously stored hash.
SELECT a.int_val
FROM test_long_text_hash a, test_cstore_long_text c
WHERE a.int_val = c.int_val AND a.hash = md5(c.text_val);
int_val
---------------------------------------------------------------------
1
2
3
4
5
6
7
8
9
10
(10 rows)
DROP TABLE test_long_text_hash;
DROP FOREIGN TABLE test_cstore_long_text;

View File

@ -0,0 +1,105 @@
--
-- Test querying cstore_fdw tables.
--
-- Settings to make the result deterministic
SET datestyle = "ISO, YMD";
-- Query uncompressed data
SELECT count(*) FROM contestant;
count
---------------------------------------------------------------------
8
(1 row)
SELECT avg(rating), stddev_samp(rating) FROM contestant;
avg | stddev_samp
---------------------------------------------------------------------
2344.3750000000000000 | 433.746119785032
(1 row)
SELECT country, avg(rating) FROM contestant WHERE rating > 2200
GROUP BY country ORDER BY country;
country | avg
---------------------------------------------------------------------
XA | 2203.0000000000000000
XB | 2610.5000000000000000
XC | 2236.0000000000000000
XD | 3090.0000000000000000
(4 rows)
SELECT * FROM contestant ORDER BY handle;
handle | birthdate | rating | percentile | country | achievements
---------------------------------------------------------------------
a | 1990-01-10 | 2090 | 97.1 | XA | {a}
b | 1990-11-01 | 2203 | 98.1 | XA | {a,b}
c | 1988-11-01 | 2907 | 99.4 | XB | {w,y}
d | 1985-05-05 | 2314 | 98.3 | XB | {}
e | 1995-05-05 | 2236 | 98.2 | XC | {a}
f | 1983-04-02 | 3090 | 99.6 | XD | {a,b,c,y}
g | 1991-12-13 | 1803 | 85.1 | XD | {a,c}
h | 1987-10-26 | 2112 | 95.4 | XD | {w,a}
(8 rows)
-- Query compressed data
SELECT count(*) FROM contestant_compressed;
count
---------------------------------------------------------------------
8
(1 row)
SELECT avg(rating), stddev_samp(rating) FROM contestant_compressed;
avg | stddev_samp
---------------------------------------------------------------------
2344.3750000000000000 | 433.746119785032
(1 row)
SELECT country, avg(rating) FROM contestant_compressed WHERE rating > 2200
GROUP BY country ORDER BY country;
country | avg
---------------------------------------------------------------------
XA | 2203.0000000000000000
XB | 2610.5000000000000000
XC | 2236.0000000000000000
XD | 3090.0000000000000000
(4 rows)
SELECT * FROM contestant_compressed ORDER BY handle;
handle | birthdate | rating | percentile | country | achievements
---------------------------------------------------------------------
a | 1990-01-10 | 2090 | 97.1 | XA | {a}
b | 1990-11-01 | 2203 | 98.1 | XA | {a,b}
c | 1988-11-01 | 2907 | 99.4 | XB | {w,y}
d | 1985-05-05 | 2314 | 98.3 | XB | {}
e | 1995-05-05 | 2236 | 98.2 | XC | {a}
f | 1983-04-02 | 3090 | 99.6 | XD | {a,b,c,y}
g | 1991-12-13 | 1803 | 85.1 | XD | {a,c}
h | 1987-10-26 | 2112 | 95.4 | XD | {w,a}
(8 rows)
-- Verify that we handle whole-row references correctly
SELECT to_json(v) FROM contestant v ORDER BY rating LIMIT 1;
to_json
---------------------------------------------------------------------
{"handle":"g","birthdate":"1991-12-13","rating":1803,"percentile":85.1,"country":"XD ","achievements":["a","c"]}
(1 row)
-- Test variables used in expressions
CREATE FOREIGN TABLE union_first (a int, b int) SERVER cstore_server;
CREATE FOREIGN TABLE union_second (a int, b int) SERVER cstore_server;
INSERT INTO union_first SELECT a, a FROM generate_series(1, 5) a;
INSERT INTO union_second SELECT a, a FROM generate_series(11, 15) a;
(SELECT a*1, b FROM union_first) union all (SELECT a*1, b FROM union_second);
?column? | b
---------------------------------------------------------------------
1 | 1
2 | 2
3 | 3
4 | 4
5 | 5
11 | 11
12 | 12
13 | 13
14 | 14
15 | 15
(10 rows)
DROP FOREIGN TABLE union_first, union_second;

View File

@ -0,0 +1,77 @@
--
-- Testing we handle rollbacks properly
--
CREATE FOREIGN TABLE t(a int, b int) SERVER cstore_server;
BEGIN;
INSERT INTO t SELECT i, i+1 FROM generate_series(1, 10) i;
ROLLBACK;
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
0
(1 row)
-- check stripe metadata also have been rolled-back
SELECT count(*) FROM cstore.cstore_stripes a, pg_class b
WHERE a.relfilenode = b.relfilenode AND b.relname = 't';
count
---------------------------------------------------------------------
0
(1 row)
INSERT INTO t SELECT i, i+1 FROM generate_series(1, 10) i;
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
10
(1 row)
SELECT count(*) FROM cstore.cstore_stripes a, pg_class b
WHERE a.relfilenode = b.relfilenode AND b.relname = 't';
count
---------------------------------------------------------------------
1
(1 row)
-- savepoint rollback
BEGIN;
SAVEPOINT s0;
INSERT INTO t SELECT i, i+1 FROM generate_series(1, 10) i;
SAVEPOINT s1;
INSERT INTO t SELECT i, i+1 FROM generate_series(1, 10) i;
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
30
(1 row)
ROLLBACK TO SAVEPOINT s1;
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
20
(1 row)
ROLLBACK TO SAVEPOINT s0;
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
10
(1 row)
INSERT INTO t SELECT i, i+1 FROM generate_series(1, 10) i;
COMMIT;
SELECT count(*) FROM t;
count
---------------------------------------------------------------------
20
(1 row)
SELECT count(*) FROM cstore.cstore_stripes a, pg_class b
WHERE a.relfilenode = b.relfilenode AND b.relname = 't';
count
---------------------------------------------------------------------
2
(1 row)
DROP FOREIGN TABLE t;

View File

@ -0,0 +1,265 @@
--
-- Test the TRUNCATE TABLE command for cstore_fdw tables.
--
-- print whether we're using version > 10 to make version-specific tests clear
SHOW server_version \gset
SELECT substring(:'server_version', '\d+')::int > 10 AS version_above_ten;
version_above_ten
---------------------------------------------------------------------
t
(1 row)
-- CREATE a cstore_fdw table, fill with some data --
CREATE FOREIGN TABLE cstore_truncate_test (a int, b int) SERVER cstore_server;
CREATE FOREIGN TABLE cstore_truncate_test_second (a int, b int) SERVER cstore_server;
CREATE FOREIGN TABLE cstore_truncate_test_compressed (a int, b int) SERVER cstore_server OPTIONS (compression 'pglz');
CREATE TABLE cstore_truncate_test_regular (a int, b int);
SELECT count(*) AS cstore_data_files_before_truncate FROM cstore.cstore_data_files \gset
INSERT INTO cstore_truncate_test select a, a from generate_series(1, 10) a;
INSERT INTO cstore_truncate_test_compressed select a, a from generate_series(1, 10) a;
INSERT INTO cstore_truncate_test_compressed select a, a from generate_series(1, 10) a;
-- query rows
SELECT * FROM cstore_truncate_test;
a | b
---------------------------------------------------------------------
1 | 1
2 | 2
3 | 3
4 | 4
5 | 5
6 | 6
7 | 7
8 | 8
9 | 9
10 | 10
(10 rows)
TRUNCATE TABLE cstore_truncate_test;
SELECT * FROM cstore_truncate_test;
a | b
---------------------------------------------------------------------
(0 rows)
SELECT COUNT(*) from cstore_truncate_test;
count
---------------------------------------------------------------------
0
(1 row)
SELECT count(*) FROM cstore_truncate_test_compressed;
count
---------------------------------------------------------------------
20
(1 row)
TRUNCATE TABLE cstore_truncate_test_compressed;
SELECT count(*) FROM cstore_truncate_test_compressed;
count
---------------------------------------------------------------------
0
(1 row)
SELECT cstore_table_size('cstore_truncate_test_compressed');
cstore_table_size
---------------------------------------------------------------------
0
(1 row)
INSERT INTO cstore_truncate_test select a, a from generate_series(1, 10) a;
INSERT INTO cstore_truncate_test_regular select a, a from generate_series(10, 20) a;
INSERT INTO cstore_truncate_test_second select a, a from generate_series(20, 30) a;
SELECT * from cstore_truncate_test;
a | b
---------------------------------------------------------------------
1 | 1
2 | 2
3 | 3
4 | 4
5 | 5
6 | 6
7 | 7
8 | 8
9 | 9
10 | 10
(10 rows)
SELECT * from cstore_truncate_test_second;
a | b
---------------------------------------------------------------------
20 | 20
21 | 21
22 | 22
23 | 23
24 | 24
25 | 25
26 | 26
27 | 27
28 | 28
29 | 29
30 | 30
(11 rows)
SELECT * from cstore_truncate_test_regular;
a | b
---------------------------------------------------------------------
10 | 10
11 | 11
12 | 12
13 | 13
14 | 14
15 | 15
16 | 16
17 | 17
18 | 18
19 | 19
20 | 20
(11 rows)
-- make sure multi truncate works
-- notice that the same table might be repeated
TRUNCATE TABLE cstore_truncate_test,
cstore_truncate_test_regular,
cstore_truncate_test_second,
cstore_truncate_test;
SELECT * from cstore_truncate_test;
a | b
---------------------------------------------------------------------
(0 rows)
SELECT * from cstore_truncate_test_second;
a | b
---------------------------------------------------------------------
(0 rows)
SELECT * from cstore_truncate_test_regular;
a | b
---------------------------------------------------------------------
(0 rows)
-- test if truncate on empty table works
TRUNCATE TABLE cstore_truncate_test;
SELECT * from cstore_truncate_test;
a | b
---------------------------------------------------------------------
(0 rows)
-- make sure TRUNATE deletes metadata for old relfilenode
SELECT :cstore_data_files_before_truncate - count(*) FROM cstore.cstore_data_files;
?column?
---------------------------------------------------------------------
0
(1 row)
-- test if truncation in the same transaction that created the table works properly
BEGIN;
CREATE FOREIGN TABLE cstore_same_transaction_truncate(a int) SERVER cstore_server;
INSERT INTO cstore_same_transaction_truncate SELECT * FROM generate_series(1, 100);
TRUNCATE cstore_same_transaction_truncate;
INSERT INTO cstore_same_transaction_truncate SELECT * FROM generate_series(20, 23);
COMMIT;
-- should output "1" for the newly created relation
SELECT count(*) - :cstore_data_files_before_truncate FROM cstore.cstore_data_files;
?column?
---------------------------------------------------------------------
1
(1 row)
SELECT * FROM cstore_same_transaction_truncate;
a
---------------------------------------------------------------------
20
21
22
23
(4 rows)
DROP FOREIGN TABLE cstore_same_transaction_truncate;
-- test if a cached truncate from a pl/pgsql function works
CREATE FUNCTION cstore_truncate_test_regular_func() RETURNS void AS $$
BEGIN
INSERT INTO cstore_truncate_test_regular select a, a from generate_series(1, 10) a;
TRUNCATE TABLE cstore_truncate_test_regular;
END;$$
LANGUAGE plpgsql;
SELECT cstore_truncate_test_regular_func();
cstore_truncate_test_regular_func
---------------------------------------------------------------------
(1 row)
-- the cached plans are used stating from the second call
SELECT cstore_truncate_test_regular_func();
cstore_truncate_test_regular_func
---------------------------------------------------------------------
(1 row)
DROP FUNCTION cstore_truncate_test_regular_func();
DROP FOREIGN TABLE cstore_truncate_test, cstore_truncate_test_second;
DROP TABLE cstore_truncate_test_regular;
DROP FOREIGN TABLE cstore_truncate_test_compressed;
-- test truncate with schema
CREATE SCHEMA truncate_schema;
CREATE FOREIGN TABLE truncate_schema.truncate_tbl (id int) SERVER cstore_server OPTIONS(compression 'pglz');
INSERT INTO truncate_schema.truncate_tbl SELECT generate_series(1, 100);
SELECT COUNT(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
TRUNCATE TABLE truncate_schema.truncate_tbl;
SELECT COUNT(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
0
(1 row)
INSERT INTO truncate_schema.truncate_tbl SELECT generate_series(1, 100);
-- create a user that can not truncate
CREATE USER truncate_user;
NOTICE: not propagating CREATE ROLE/USER commands to worker nodes
HINT: Connect to worker nodes directly to manually create all necessary users and roles.
GRANT USAGE ON SCHEMA truncate_schema TO truncate_user;
GRANT SELECT ON TABLE truncate_schema.truncate_tbl TO truncate_user;
REVOKE TRUNCATE ON TABLE truncate_schema.truncate_tbl FROM truncate_user;
SELECT current_user \gset
\c - truncate_user
-- verify truncate command fails and check number of rows
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
TRUNCATE TABLE truncate_schema.truncate_tbl;
ERROR: permission denied for table truncate_tbl
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
-- switch to super user, grant truncate to truncate_user
\c - :current_user
GRANT TRUNCATE ON TABLE truncate_schema.truncate_tbl TO truncate_user;
-- verify truncate_user can truncate now
\c - truncate_user
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
TRUNCATE TABLE truncate_schema.truncate_tbl;
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
0
(1 row)
\c - :current_user
-- cleanup
DROP SCHEMA truncate_schema CASCADE;
NOTICE: drop cascades to foreign table truncate_schema.truncate_tbl
DROP USER truncate_user;

View File

@ -0,0 +1,262 @@
--
-- Test the TRUNCATE TABLE command for cstore_fdw tables.
--
-- print whether we're using version > 10 to make version-specific tests clear
SHOW server_version \gset
SELECT substring(:'server_version', '\d+')::int > 10 AS version_above_ten;
version_above_ten
---------------------------------------------------------------------
f
(1 row)
-- Check that files for the automatically managed table exist in the
-- cstore_fdw/{databaseoid} directory.
SELECT count(*) FROM (
SELECT pg_ls_dir('cstore_fdw/' || databaseoid ) FROM (
SELECT oid::text databaseoid FROM pg_database WHERE datname = current_database()
) AS q1) AS q2;
count
---------------------------------------------------------------------
0
(1 row)
-- CREATE a cstore_fdw table, fill with some data --
CREATE FOREIGN TABLE cstore_truncate_test (a int, b int) SERVER cstore_server;
CREATE FOREIGN TABLE cstore_truncate_test_second (a int, b int) SERVER cstore_server;
CREATE FOREIGN TABLE cstore_truncate_test_compressed (a int, b int) SERVER cstore_server OPTIONS (compression 'pglz');
CREATE TABLE cstore_truncate_test_regular (a int, b int);
INSERT INTO cstore_truncate_test select a, a from generate_series(1, 10) a;
INSERT INTO cstore_truncate_test_compressed select a, a from generate_series(1, 10) a;
INSERT INTO cstore_truncate_test_compressed select a, a from generate_series(1, 10) a;
-- query rows
SELECT * FROM cstore_truncate_test;
a | b
---------------------------------------------------------------------
1 | 1
2 | 2
3 | 3
4 | 4
5 | 5
6 | 6
7 | 7
8 | 8
9 | 9
10 | 10
(10 rows)
TRUNCATE TABLE cstore_truncate_test;
SELECT * FROM cstore_truncate_test;
a | b
---------------------------------------------------------------------
(0 rows)
SELECT COUNT(*) from cstore_truncate_test;
count
---------------------------------------------------------------------
0
(1 row)
SELECT count(*) FROM cstore_truncate_test_compressed;
count
---------------------------------------------------------------------
20
(1 row)
TRUNCATE TABLE cstore_truncate_test_compressed;
SELECT count(*) FROM cstore_truncate_test_compressed;
count
---------------------------------------------------------------------
0
(1 row)
SELECT cstore_table_size('cstore_truncate_test_compressed');
cstore_table_size
---------------------------------------------------------------------
26
(1 row)
-- make sure data files still present
SELECT count(*) FROM (
SELECT pg_ls_dir('cstore_fdw/' || databaseoid ) FROM (
SELECT oid::text databaseoid FROM pg_database WHERE datname = current_database()
) AS q1) AS q2;
count
---------------------------------------------------------------------
6
(1 row)
INSERT INTO cstore_truncate_test select a, a from generate_series(1, 10) a;
INSERT INTO cstore_truncate_test_regular select a, a from generate_series(10, 20) a;
INSERT INTO cstore_truncate_test_second select a, a from generate_series(20, 30) a;
SELECT * from cstore_truncate_test;
a | b
---------------------------------------------------------------------
1 | 1
2 | 2
3 | 3
4 | 4
5 | 5
6 | 6
7 | 7
8 | 8
9 | 9
10 | 10
(10 rows)
SELECT * from cstore_truncate_test_second;
a | b
---------------------------------------------------------------------
20 | 20
21 | 21
22 | 22
23 | 23
24 | 24
25 | 25
26 | 26
27 | 27
28 | 28
29 | 29
30 | 30
(11 rows)
SELECT * from cstore_truncate_test_regular;
a | b
---------------------------------------------------------------------
10 | 10
11 | 11
12 | 12
13 | 13
14 | 14
15 | 15
16 | 16
17 | 17
18 | 18
19 | 19
20 | 20
(11 rows)
-- make sure multi truncate works
-- notice that the same table might be repeated
TRUNCATE TABLE cstore_truncate_test,
cstore_truncate_test_regular,
cstore_truncate_test_second,
cstore_truncate_test;
SELECT * from cstore_truncate_test;
a | b
---------------------------------------------------------------------
(0 rows)
SELECT * from cstore_truncate_test_second;
a | b
---------------------------------------------------------------------
(0 rows)
SELECT * from cstore_truncate_test_regular;
a | b
---------------------------------------------------------------------
(0 rows)
-- test if truncate on empty table works
TRUNCATE TABLE cstore_truncate_test;
SELECT * from cstore_truncate_test;
a | b
---------------------------------------------------------------------
(0 rows)
-- test if a cached truncate from a pl/pgsql function works
CREATE FUNCTION cstore_truncate_test_regular_func() RETURNS void AS $$
BEGIN
INSERT INTO cstore_truncate_test_regular select a, a from generate_series(1, 10) a;
TRUNCATE TABLE cstore_truncate_test_regular;
END;$$
LANGUAGE plpgsql;
SELECT cstore_truncate_test_regular_func();
cstore_truncate_test_regular_func
---------------------------------------------------------------------
(1 row)
-- the cached plans are used stating from the second call
SELECT cstore_truncate_test_regular_func();
cstore_truncate_test_regular_func
---------------------------------------------------------------------
(1 row)
DROP FUNCTION cstore_truncate_test_regular_func();
DROP FOREIGN TABLE cstore_truncate_test, cstore_truncate_test_second;
DROP TABLE cstore_truncate_test_regular;
DROP FOREIGN TABLE cstore_truncate_test_compressed;
-- test truncate with schema
CREATE SCHEMA truncate_schema;
CREATE FOREIGN TABLE truncate_schema.truncate_tbl (id int) SERVER cstore_server OPTIONS(compression 'pglz');
INSERT INTO truncate_schema.truncate_tbl SELECT generate_series(1, 100);
SELECT COUNT(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
TRUNCATE TABLE truncate_schema.truncate_tbl;
SELECT COUNT(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
0
(1 row)
INSERT INTO truncate_schema.truncate_tbl SELECT generate_series(1, 100);
-- create a user that can not truncate
CREATE USER truncate_user;
GRANT USAGE ON SCHEMA truncate_schema TO truncate_user;
GRANT SELECT ON TABLE truncate_schema.truncate_tbl TO truncate_user;
REVOKE TRUNCATE ON TABLE truncate_schema.truncate_tbl FROM truncate_user;
SELECT current_user \gset
\c - truncate_user
-- verify truncate command fails and check number of rows
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
TRUNCATE TABLE truncate_schema.truncate_tbl;
ERROR: permission denied for relation truncate_tbl
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
-- switch to super user, grant truncate to truncate_user
\c - :current_user
GRANT TRUNCATE ON TABLE truncate_schema.truncate_tbl TO truncate_user;
-- verify truncate_user can truncate now
\c - truncate_user
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
100
(1 row)
TRUNCATE TABLE truncate_schema.truncate_tbl;
SELECT count(*) FROM truncate_schema.truncate_tbl;
count
---------------------------------------------------------------------
0
(1 row)
\c - :current_user
-- cleanup
DROP SCHEMA truncate_schema CASCADE;
NOTICE: drop cascades to foreign table truncate_schema.truncate_tbl
DROP USER truncate_user;
-- verify files are removed
SELECT count(*) FROM (
SELECT pg_ls_dir('cstore_fdw/' || databaseoid ) FROM (
SELECT oid::text databaseoid FROM pg_database WHERE datname = current_database()
) AS q1) AS q2;
count
---------------------------------------------------------------------
0
(1 row)

View File

@ -5,6 +5,16 @@
-- --
-- It'd be nice to script generation of this file, but alas, that's -- It'd be nice to script generation of this file, but alas, that's
-- not done yet. -- not done yet.
-- differentiate the output file for pg11 and versions above, with regards to objects
-- created per citus version depending on the postgres version. Upgrade tests verify the
-- objects are added in citus_finish_pg_upgrade()
SHOW server_version \gset
SELECT substring(:'server_version', '\d+')::int > 11 AS version_above_eleven;
version_above_eleven
---------------------------------------------------------------------
t
(1 row)
SET citus.next_shard_id TO 580000; SET citus.next_shard_id TO 580000;
SELECT $definition$ SELECT $definition$
CREATE OR REPLACE FUNCTION test.maintenance_worker() CREATE OR REPLACE FUNCTION test.maintenance_worker()
@ -80,7 +90,7 @@ FROM pg_depend AS pgd,
WHERE pgd.refclassid = 'pg_extension'::regclass AND WHERE pgd.refclassid = 'pg_extension'::regclass AND
pgd.refobjid = pge.oid AND pgd.refobjid = pge.oid AND
pge.extname = 'citus' AND pge.extname = 'citus' AND
pgio.schema NOT IN ('pg_catalog', 'citus', 'citus_internal', 'test'); pgio.schema NOT IN ('pg_catalog', 'citus', 'citus_internal', 'test', 'cstore');
count count
--------------------------------------------------------------------- ---------------------------------------------------------------------
0 0
@ -465,9 +475,25 @@ SELECT * FROM print_extension_changes();
-- Snapshot of state at 10.0-1 -- Snapshot of state at 10.0-1
ALTER EXTENSION citus UPDATE TO '10.0-1'; ALTER EXTENSION citus UPDATE TO '10.0-1';
SELECT * FROM print_extension_changes(); SELECT * FROM print_extension_changes();
previous_object | current_object previous_object | current_object
--------------------------------------------------------------------- ---------------------------------------------------------------------
(0 rows) | access method cstore_tableam
| event trigger cstore_ddl_event_end
| foreign-data wrapper cstore_fdw
| function alter_cstore_table_reset(regclass,boolean,boolean,boolean)
| function alter_cstore_table_set(regclass,integer,integer,name)
| function citus_internal.cstore_ensure_objects_exist()
| function cstore.cstore_ddl_event_end_trigger()
| function cstore.cstore_fdw_handler()
| function cstore.cstore_fdw_validator(text[],oid)
| function cstore.cstore_tableam_handler(internal)
| function cstore_table_size(regclass)
| schema cstore
| table cstore.cstore_data_files
| table cstore.cstore_skipnodes
| table cstore.cstore_stripes
| view cstore.cstore_options
(16 rows)
DROP TABLE prev_objects, extension_diff; DROP TABLE prev_objects, extension_diff;
-- show running version -- show running version
@ -485,7 +511,7 @@ FROM pg_depend AS pgd,
WHERE pgd.refclassid = 'pg_extension'::regclass AND WHERE pgd.refclassid = 'pg_extension'::regclass AND
pgd.refobjid = pge.oid AND pgd.refobjid = pge.oid AND
pge.extname = 'citus' AND pge.extname = 'citus' AND
pgio.schema NOT IN ('pg_catalog', 'citus', 'citus_internal', 'test'); pgio.schema NOT IN ('pg_catalog', 'citus', 'citus_internal', 'test', 'cstore');
count count
--------------------------------------------------------------------- ---------------------------------------------------------------------
0 0

View File

@ -0,0 +1,829 @@
--
-- MULTI_EXTENSION
--
-- Tests around extension creation / upgrades
--
-- It'd be nice to script generation of this file, but alas, that's
-- not done yet.
-- differentiate the output file for pg11 and versions above, with regards to objects
-- created per citus version depending on the postgres version. Upgrade tests verify the
-- objects are added in citus_finish_pg_upgrade()
SHOW server_version \gset
SELECT substring(:'server_version', '\d+')::int > 11 AS version_above_eleven;
version_above_eleven
---------------------------------------------------------------------
f
(1 row)
SET citus.next_shard_id TO 580000;
SELECT $definition$
CREATE OR REPLACE FUNCTION test.maintenance_worker()
RETURNS pg_stat_activity
LANGUAGE plpgsql
AS $$
DECLARE
activity record;
BEGIN
DO 'BEGIN END'; -- Force maintenance daemon to start
-- we don't want to wait forever; loop will exit after 20 seconds
FOR i IN 1 .. 200 LOOP
PERFORM pg_stat_clear_snapshot();
SELECT * INTO activity FROM pg_stat_activity
WHERE application_name = 'Citus Maintenance Daemon' AND datname = current_database();
IF activity.pid IS NOT NULL THEN
RETURN activity;
ELSE
PERFORM pg_sleep(0.1);
END IF ;
END LOOP;
-- fail if we reach the end of this loop
raise 'Waited too long for maintenance daemon to start';
END;
$$;
$definition$ create_function_test_maintenance_worker
\gset
CREATE TABLE prev_objects(description text);
CREATE TABLE extension_diff(previous_object text COLLATE "C",
current_object text COLLATE "C");
CREATE FUNCTION print_extension_changes()
RETURNS TABLE(previous_object text, current_object text)
AS $func$
BEGIN
TRUNCATE TABLE extension_diff;
CREATE TABLE current_objects AS
SELECT pg_catalog.pg_describe_object(classid, objid, 0) AS description
FROM pg_catalog.pg_depend, pg_catalog.pg_extension e
WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass
AND refobjid = e.oid
AND deptype = 'e'
AND e.extname='citus';
INSERT INTO extension_diff
SELECT p.description previous_object, c.description current_object
FROM current_objects c FULL JOIN prev_objects p
ON p.description = c.description
WHERE p.description is null OR c.description is null;
DROP TABLE prev_objects;
ALTER TABLE current_objects RENAME TO prev_objects;
RETURN QUERY SELECT * FROM extension_diff ORDER BY 1, 2;
END
$func$ LANGUAGE plpgsql;
CREATE SCHEMA test;
:create_function_test_maintenance_worker
-- check maintenance daemon is started
SELECT datname, current_database(),
usename, (SELECT extowner::regrole::text FROM pg_extension WHERE extname = 'citus')
FROM test.maintenance_worker();
datname | current_database | usename | extowner
---------------------------------------------------------------------
regression | regression | postgres | postgres
(1 row)
-- ensure no objects were created outside pg_catalog
SELECT COUNT(*)
FROM pg_depend AS pgd,
pg_extension AS pge,
LATERAL pg_identify_object(pgd.classid, pgd.objid, pgd.objsubid) AS pgio
WHERE pgd.refclassid = 'pg_extension'::regclass AND
pgd.refobjid = pge.oid AND
pge.extname = 'citus' AND
pgio.schema NOT IN ('pg_catalog', 'citus', 'citus_internal', 'test', 'cstore');
count
---------------------------------------------------------------------
0
(1 row)
-- DROP EXTENSION pre-created by the regression suite
DROP EXTENSION citus;
\c
-- these tests switch between citus versions and call ddl's that require pg_dist_object to be created
SET citus.enable_object_propagation TO 'false';
SET citus.enable_version_checks TO 'false';
CREATE EXTENSION citus VERSION '7.0-1';
ALTER EXTENSION citus UPDATE TO '7.0-2';
ALTER EXTENSION citus UPDATE TO '7.0-3';
ALTER EXTENSION citus UPDATE TO '7.0-4';
ALTER EXTENSION citus UPDATE TO '7.0-5';
ALTER EXTENSION citus UPDATE TO '7.0-6';
ALTER EXTENSION citus UPDATE TO '7.0-7';
ALTER EXTENSION citus UPDATE TO '7.0-8';
ALTER EXTENSION citus UPDATE TO '7.0-9';
ALTER EXTENSION citus UPDATE TO '7.0-10';
ALTER EXTENSION citus UPDATE TO '7.0-11';
ALTER EXTENSION citus UPDATE TO '7.0-12';
ALTER EXTENSION citus UPDATE TO '7.0-13';
ALTER EXTENSION citus UPDATE TO '7.0-14';
ALTER EXTENSION citus UPDATE TO '7.0-15';
ALTER EXTENSION citus UPDATE TO '7.1-1';
ALTER EXTENSION citus UPDATE TO '7.1-2';
ALTER EXTENSION citus UPDATE TO '7.1-3';
ALTER EXTENSION citus UPDATE TO '7.1-4';
ALTER EXTENSION citus UPDATE TO '7.2-1';
ALTER EXTENSION citus UPDATE TO '7.2-2';
ALTER EXTENSION citus UPDATE TO '7.2-3';
ALTER EXTENSION citus UPDATE TO '7.3-3';
ALTER EXTENSION citus UPDATE TO '7.4-1';
ALTER EXTENSION citus UPDATE TO '7.4-2';
ALTER EXTENSION citus UPDATE TO '7.4-3';
ALTER EXTENSION citus UPDATE TO '7.5-1';
ALTER EXTENSION citus UPDATE TO '7.5-2';
ALTER EXTENSION citus UPDATE TO '7.5-3';
ALTER EXTENSION citus UPDATE TO '7.5-4';
ALTER EXTENSION citus UPDATE TO '7.5-5';
ALTER EXTENSION citus UPDATE TO '7.5-6';
ALTER EXTENSION citus UPDATE TO '7.5-7';
ALTER EXTENSION citus UPDATE TO '8.0-1';
ALTER EXTENSION citus UPDATE TO '8.0-2';
ALTER EXTENSION citus UPDATE TO '8.0-3';
ALTER EXTENSION citus UPDATE TO '8.0-4';
ALTER EXTENSION citus UPDATE TO '8.0-5';
ALTER EXTENSION citus UPDATE TO '8.0-6';
ALTER EXTENSION citus UPDATE TO '8.0-7';
ALTER EXTENSION citus UPDATE TO '8.0-8';
ALTER EXTENSION citus UPDATE TO '8.0-9';
ALTER EXTENSION citus UPDATE TO '8.0-10';
ALTER EXTENSION citus UPDATE TO '8.0-11';
ALTER EXTENSION citus UPDATE TO '8.0-12';
ALTER EXTENSION citus UPDATE TO '8.0-13';
ALTER EXTENSION citus UPDATE TO '8.1-1';
ALTER EXTENSION citus UPDATE TO '8.2-1';
ALTER EXTENSION citus UPDATE TO '8.2-2';
ALTER EXTENSION citus UPDATE TO '8.2-3';
ALTER EXTENSION citus UPDATE TO '8.2-4';
ALTER EXTENSION citus UPDATE TO '8.3-1';
ALTER EXTENSION citus UPDATE TO '9.0-1';
ALTER EXTENSION citus UPDATE TO '9.0-2';
ALTER EXTENSION citus UPDATE TO '9.1-1';
ALTER EXTENSION citus UPDATE TO '9.2-1';
ALTER EXTENSION citus UPDATE TO '9.2-2';
-- Snapshot of state at 9.2-2
SELECT * FROM print_extension_changes();
previous_object | current_object
---------------------------------------------------------------------
| event trigger citus_cascade_to_partition
| function alter_role_if_exists(text,text)
| function any_value(anyelement)
| function any_value_agg(anyelement,anyelement)
| function array_cat_agg(anyarray)
| function assign_distributed_transaction_id(integer,bigint,timestamp with time zone)
| function authinfo_valid(text)
| function broadcast_intermediate_result(text,text)
| function check_distributed_deadlocks()
| function citus_add_rebalance_strategy(name,regproc,regproc,regproc,real,real)
| function citus_blocking_pids(integer)
| function citus_create_restore_point(text)
| function citus_dist_stat_activity()
| function citus_drop_trigger()
| function citus_executor_name(integer)
| function citus_extradata_container(internal)
| function citus_finish_pg_upgrade()
| function citus_internal.find_groupid_for_node(text,integer)
| function citus_internal.pg_dist_node_trigger_func()
| function citus_internal.pg_dist_rebalance_strategy_enterprise_check()
| function citus_internal.pg_dist_rebalance_strategy_trigger_func()
| function citus_internal.pg_dist_shard_placement_trigger_func()
| function citus_internal.refresh_isolation_tester_prepared_statement()
| function citus_internal.replace_isolation_tester_func()
| function citus_internal.restore_isolation_tester_func()
| function citus_isolation_test_session_is_blocked(integer,integer[])
| function citus_json_concatenate(json,json)
| function citus_json_concatenate_final(json)
| function citus_jsonb_concatenate(jsonb,jsonb)
| function citus_jsonb_concatenate_final(jsonb)
| function citus_node_capacity_1(integer)
| function citus_prepare_pg_upgrade()
| function citus_query_stats()
| function citus_relation_size(regclass)
| function citus_server_id()
| function citus_set_default_rebalance_strategy(text)
| function citus_shard_allowed_on_node_true(bigint,integer)
| function citus_shard_cost_1(bigint)
| function citus_shard_cost_by_disk_size(bigint)
| function citus_stat_statements()
| function citus_stat_statements_reset()
| function citus_table_is_visible(oid)
| function citus_table_size(regclass)
| function citus_text_send_as_jsonb(text)
| function citus_total_relation_size(regclass)
| function citus_truncate_trigger()
| function citus_validate_rebalance_strategy_functions(regproc,regproc,regproc)
| function citus_version()
| function citus_worker_stat_activity()
| function column_name_to_column(regclass,text)
| function column_to_column_name(regclass,text)
| function coord_combine_agg(oid,cstring,anyelement)
| function coord_combine_agg_ffunc(internal,oid,cstring,anyelement)
| function coord_combine_agg_sfunc(internal,oid,cstring,anyelement)
| function create_distributed_function(regprocedure,text,text)
| function create_distributed_table(regclass,text,citus.distribution_type,text)
| function create_intermediate_result(text,text)
| function create_reference_table(regclass)
| function distributed_tables_colocated(regclass,regclass)
| function dump_global_wait_edges()
| function dump_local_wait_edges()
| function fetch_intermediate_results(text[],text,integer)
| function get_all_active_transactions()
| function get_colocated_shard_array(bigint)
| function get_colocated_table_array(regclass)
| function get_current_transaction_id()
| function get_global_active_transactions()
| function get_rebalance_progress()
| function get_rebalance_table_shards_plan(regclass,real,integer,bigint[],boolean,name)
| function get_shard_id_for_distribution_column(regclass,"any")
| function isolate_tenant_to_new_shard(regclass,"any",text)
| function json_cat_agg(json)
| function jsonb_cat_agg(jsonb)
| function lock_relation_if_exists(text,text)
| function lock_shard_metadata(integer,bigint[])
| function lock_shard_resources(integer,bigint[])
| function mark_tables_colocated(regclass,regclass[])
| function master_activate_node(text,integer)
| function master_add_inactive_node(text,integer,integer,noderole,name)
| function master_add_node(text,integer,integer,noderole,name)
| function master_add_secondary_node(text,integer,text,integer,name)
| function master_append_table_to_shard(bigint,text,text,integer)
| function master_apply_delete_command(text)
| function master_conninfo_cache_invalidate()
| function master_copy_shard_placement(bigint,text,integer,text,integer,boolean,citus.shard_transfer_mode)
| function master_create_distributed_table(regclass,text,citus.distribution_type)
| function master_create_empty_shard(text)
| function master_create_worker_shards(text,integer,integer)
| function master_disable_node(text,integer)
| function master_dist_local_group_cache_invalidate()
| function master_dist_node_cache_invalidate()
| function master_dist_object_cache_invalidate()
| function master_dist_partition_cache_invalidate()
| function master_dist_placement_cache_invalidate()
| function master_dist_shard_cache_invalidate()
| function master_drain_node(text,integer,citus.shard_transfer_mode,name)
| function master_drop_all_shards(regclass,text,text)
| function master_drop_sequences(text[])
| function master_get_active_worker_nodes()
| function master_get_new_placementid()
| function master_get_new_shardid()
| function master_get_table_ddl_events(text)
| function master_get_table_metadata(text)
| function master_modify_multiple_shards(text)
| function master_move_shard_placement(bigint,text,integer,text,integer,citus.shard_transfer_mode)
| function master_remove_distributed_table_metadata_from_workers(regclass,text,text)
| function master_remove_node(text,integer)
| function master_remove_partition_metadata(regclass,text,text)
| function master_run_on_worker(text[],integer[],text[],boolean)
| function master_set_node_property(text,integer,text,boolean)
| function master_unmark_object_distributed(oid,oid,integer)
| function master_update_node(integer,text,integer,boolean,integer)
| function master_update_shard_statistics(bigint)
| function master_update_table_statistics(regclass)
| function poolinfo_valid(text)
| function read_intermediate_result(text,citus_copy_format)
| function read_intermediate_results(text[],citus_copy_format)
| function rebalance_table_shards(regclass,real,integer,bigint[],citus.shard_transfer_mode,boolean,name)
| function recover_prepared_transactions()
| function relation_is_a_known_shard(regclass)
| function replicate_table_shards(regclass,integer,integer,bigint[],citus.shard_transfer_mode)
| function role_exists(name)
| function run_command_on_colocated_placements(regclass,regclass,text,boolean)
| function run_command_on_placements(regclass,text,boolean)
| function run_command_on_shards(regclass,text,boolean)
| function run_command_on_workers(text,boolean)
| function shard_name(regclass,bigint)
| function start_metadata_sync_to_node(text,integer)
| function stop_metadata_sync_to_node(text,integer)
| function task_tracker_assign_task(bigint,integer,text)
| function task_tracker_cleanup_job(bigint)
| function task_tracker_conninfo_cache_invalidate()
| function task_tracker_task_status(bigint,integer)
| function upgrade_to_reference_table(regclass)
| function worker_append_table_to_shard(text,text,text,integer)
| function worker_apply_inter_shard_ddl_command(bigint,text,bigint,text,text)
| function worker_apply_sequence_command(text)
| function worker_apply_sequence_command(text,regtype)
| function worker_apply_shard_ddl_command(bigint,text)
| function worker_apply_shard_ddl_command(bigint,text,text)
| function worker_cleanup_job_schema_cache()
| function worker_create_or_replace_object(text)
| function worker_create_schema(bigint,text)
| function worker_create_truncate_trigger(regclass)
| function worker_drop_distributed_table(text)
| function worker_execute_sql_task(bigint,integer,text,boolean)
| function worker_fetch_foreign_file(text,text,bigint,text[],integer[])
| function worker_fetch_partition_file(bigint,integer,integer,integer,text,integer)
| function worker_hash("any")
| function worker_hash_partition_table(bigint,integer,text,text,oid,anyarray)
| function worker_merge_files_and_run_query(bigint,integer,text,text)
| function worker_merge_files_into_table(bigint,integer,text[],text[])
| function worker_partial_agg(oid,anyelement)
| function worker_partial_agg_ffunc(internal)
| function worker_partial_agg_sfunc(internal,oid,anyelement)
| function worker_partition_query_result(text,text,integer,citus.distribution_type,text[],text[],boolean)
| function worker_range_partition_table(bigint,integer,text,text,oid,anyarray)
| function worker_repartition_cleanup(bigint)
| schema citus
| schema citus_internal
| sequence pg_dist_colocationid_seq
| sequence pg_dist_groupid_seq
| sequence pg_dist_node_nodeid_seq
| sequence pg_dist_placement_placementid_seq
| sequence pg_dist_shardid_seq
| table citus.pg_dist_object
| table pg_dist_authinfo
| table pg_dist_colocation
| table pg_dist_local_group
| table pg_dist_node
| table pg_dist_node_metadata
| table pg_dist_partition
| table pg_dist_placement
| table pg_dist_poolinfo
| table pg_dist_rebalance_strategy
| table pg_dist_shard
| table pg_dist_transaction
| type citus.distribution_type
| type citus.shard_transfer_mode
| type citus_copy_format
| type noderole
| view citus_dist_stat_activity
| view citus_lock_waits
| view citus_shard_indexes_on_worker
| view citus_shards_on_worker
| view citus_stat_statements
| view citus_worker_stat_activity
| view pg_dist_shard_placement
(188 rows)
-- Test downgrade to 9.2-2 from 9.2-4
ALTER EXTENSION citus UPDATE TO '9.2-4';
ALTER EXTENSION citus UPDATE TO '9.2-2';
-- Should be empty result since upgrade+downgrade should be a no-op
SELECT * FROM print_extension_changes();
previous_object | current_object
---------------------------------------------------------------------
(0 rows)
/*
* As we mistakenly bumped schema version to 9.3-1 in a bad release, we support
* updating citus schema from 9.3-1 to 9.2-4, but we do not support updates to 9.3-1.
*
* Hence the query below should fail.
*/
ALTER EXTENSION citus UPDATE TO '9.3-1';
ERROR: extension "citus" has no update path from version "9.2-2" to version "9.3-1"
ALTER EXTENSION citus UPDATE TO '9.2-4';
-- Snapshot of state at 9.2-4
SELECT * FROM print_extension_changes();
previous_object | current_object
---------------------------------------------------------------------
(0 rows)
-- Test downgrade to 9.2-4 from 9.3-2
ALTER EXTENSION citus UPDATE TO '9.3-2';
ALTER EXTENSION citus UPDATE TO '9.2-4';
-- Should be empty result since upgrade+downgrade should be a no-op
SELECT * FROM print_extension_changes();
previous_object | current_object
---------------------------------------------------------------------
(0 rows)
-- Snapshot of state at 9.3-2
ALTER EXTENSION citus UPDATE TO '9.3-2';
SELECT * FROM print_extension_changes();
previous_object | current_object
---------------------------------------------------------------------
| function citus_remote_connection_stats()
| function replicate_reference_tables()
| function truncate_local_data_after_distributing_table(regclass)
| function update_distributed_table_colocation(regclass,text)
| function worker_create_or_alter_role(text,text,text)
(5 rows)
-- Test downgrade to 9.3-2 from 9.4-1
ALTER EXTENSION citus UPDATE TO '9.4-1';
ALTER EXTENSION citus UPDATE TO '9.3-2';
-- Should be empty result since upgrade+downgrade should be a no-op
SELECT * FROM print_extension_changes();
previous_object | current_object
---------------------------------------------------------------------
(0 rows)
-- Snapshot of state at 9.4-1
ALTER EXTENSION citus UPDATE TO '9.4-1';
SELECT * FROM print_extension_changes();
previous_object | current_object
---------------------------------------------------------------------
| function worker_last_saved_explain_analyze()
| function worker_save_query_explain_analyze(text,jsonb)
(2 rows)
-- Test downgrade to 9.4-1 from 9.5-1
ALTER EXTENSION citus UPDATE TO '9.5-1';
BEGIN;
SELECT master_add_node('localhost', :master_port, groupId=>0);
master_add_node
---------------------------------------------------------------------
1
(1 row)
CREATE TABLE citus_local_table (a int);
SELECT create_citus_local_table('citus_local_table');
create_citus_local_table
---------------------------------------------------------------------
(1 row)
-- downgrade from 9.5-1 to 9.4-1 should fail as we have a citus local table
ALTER EXTENSION citus UPDATE TO '9.4-1';
ERROR: citus local tables are introduced in Citus 9.5
HINT: To downgrade Citus to an older version, you should first convert each citus local table to a postgres table by executing SELECT undistribute_table("%s")
CONTEXT: PL/pgSQL function inline_code_block line 11 at RAISE
ROLLBACK;
-- now we can downgrade as there is no citus local table
ALTER EXTENSION citus UPDATE TO '9.4-1';
-- Should be empty result since upgrade+downgrade should be a no-op
SELECT * FROM print_extension_changes();
previous_object | current_object
---------------------------------------------------------------------
(0 rows)
-- Snapshot of state at 9.5-1
ALTER EXTENSION citus UPDATE TO '9.5-1';
SELECT * FROM print_extension_changes();
previous_object | current_object
---------------------------------------------------------------------
function master_drop_sequences(text[]) |
function task_tracker_assign_task(bigint,integer,text) |
function task_tracker_cleanup_job(bigint) |
function task_tracker_conninfo_cache_invalidate() |
function task_tracker_task_status(bigint,integer) |
function worker_execute_sql_task(bigint,integer,text,boolean) |
function worker_merge_files_and_run_query(bigint,integer,text,text) |
| function create_citus_local_table(regclass)
| function undistribute_table(regclass)
| function worker_record_sequence_dependency(regclass,regclass,name)
(10 rows)
-- Test downgrade to 9.5-1 from 10.0-1
ALTER EXTENSION citus UPDATE TO '10.0-1';
ALTER EXTENSION citus UPDATE TO '9.5-1';
-- Should be empty result since upgrade+downgrade should be a no-op
SELECT * FROM print_extension_changes();
previous_object | current_object
---------------------------------------------------------------------
(0 rows)
-- Snapshot of state at 10.0-1
ALTER EXTENSION citus UPDATE TO '10.0-1';
SELECT * FROM print_extension_changes();
previous_object | current_object
---------------------------------------------------------------------
| event trigger cstore_ddl_event_end
| foreign-data wrapper cstore_fdw
| function citus_internal.cstore_ensure_objects_exist()
| function cstore.cstore_ddl_event_end_trigger()
| function cstore.cstore_fdw_handler()
| function cstore.cstore_fdw_validator(text[],oid)
| function cstore_table_size(regclass)
| schema cstore
| table cstore.cstore_data_files
| table cstore.cstore_skipnodes
| table cstore.cstore_stripes
| view cstore.cstore_options
(12 rows)
DROP TABLE prev_objects, extension_diff;
-- show running version
SHOW citus.version;
citus.version
---------------------------------------------------------------------
10.0devel
(1 row)
-- ensure no objects were created outside pg_catalog
SELECT COUNT(*)
FROM pg_depend AS pgd,
pg_extension AS pge,
LATERAL pg_identify_object(pgd.classid, pgd.objid, pgd.objsubid) AS pgio
WHERE pgd.refclassid = 'pg_extension'::regclass AND
pgd.refobjid = pge.oid AND
pge.extname = 'citus' AND
pgio.schema NOT IN ('pg_catalog', 'citus', 'citus_internal', 'test', 'cstore');
count
---------------------------------------------------------------------
0
(1 row)
-- see incompatible version errors out
RESET citus.enable_version_checks;
DROP EXTENSION citus;
CREATE EXTENSION citus VERSION '7.0-1';
ERROR: specified version incompatible with loaded Citus library
DETAIL: Loaded library requires 10.0, but 7.0-1 was specified.
HINT: If a newer library is present, restart the database and try the command again.
-- Test non-distributed queries work even in version mismatch
SET citus.enable_version_checks TO 'false';
CREATE EXTENSION citus VERSION '7.1-1';
SET citus.enable_version_checks TO 'true';
-- Test CREATE TABLE
CREATE TABLE version_mismatch_table(column1 int);
-- Test COPY
\copy version_mismatch_table FROM STDIN;
-- Test INSERT
INSERT INTO version_mismatch_table(column1) VALUES(5);
-- Test SELECT
SELECT * FROM version_mismatch_table ORDER BY column1;
column1
---------------------------------------------------------------------
0
1
2
3
4
5
(6 rows)
-- Test SELECT from pg_catalog
SELECT d.datname as "Name",
pg_catalog.pg_get_userbyid(d.datdba) as "Owner",
pg_catalog.array_to_string(d.datacl, E'\n') AS "Access privileges"
FROM pg_catalog.pg_database d
ORDER BY 1;
Name | Owner | Access privileges
---------------------------------------------------------------------
postgres | postgres |
regression | postgres |
template0 | postgres | =c/postgres +
| | postgres=CTc/postgres
template1 | postgres | =c/postgres +
| | postgres=CTc/postgres
(4 rows)
-- We should not distribute table in version mistmatch
SELECT create_distributed_table('version_mismatch_table', 'column1');
ERROR: loaded Citus library version differs from installed extension version
DETAIL: Loaded library requires 10.0, but the installed extension version is 7.1-1.
HINT: Run ALTER EXTENSION citus UPDATE and try again.
-- This function will cause fail in next ALTER EXTENSION
CREATE OR REPLACE FUNCTION pg_catalog.master_dist_authinfo_cache_invalidate()
RETURNS void LANGUAGE plpgsql
AS $function$
BEGIN
END;
$function$;
SET citus.enable_version_checks TO 'false';
-- This will fail because of previous function declaration
ALTER EXTENSION citus UPDATE TO '8.1-1';
ERROR: function "master_dist_authinfo_cache_invalidate" already exists with same argument types
-- We can DROP problematic function and continue ALTER EXTENSION even when version checks are on
SET citus.enable_version_checks TO 'true';
DROP FUNCTION pg_catalog.master_dist_authinfo_cache_invalidate();
SET citus.enable_version_checks TO 'false';
ALTER EXTENSION citus UPDATE TO '8.1-1';
-- Test updating to the latest version without specifying the version number
ALTER EXTENSION citus UPDATE;
-- re-create in newest version
DROP EXTENSION citus;
\c
CREATE EXTENSION citus;
-- test cache invalidation in workers
\c - - - :worker_1_port
DROP EXTENSION citus;
SET citus.enable_version_checks TO 'false';
CREATE EXTENSION citus VERSION '7.0-1';
SET citus.enable_version_checks TO 'true';
-- during ALTER EXTENSION, we should invalidate the cache
ALTER EXTENSION citus UPDATE;
-- if cache is invalidated succesfull, this \d should work without any problem
\d
List of relations
Schema | Name | Type | Owner
---------------------------------------------------------------------
(0 rows)
\c - - - :master_port
-- test https://github.com/citusdata/citus/issues/3409
CREATE USER testuser2 SUPERUSER;
NOTICE: not propagating CREATE ROLE/USER commands to worker nodes
HINT: Connect to worker nodes directly to manually create all necessary users and roles.
SET ROLE testuser2;
DROP EXTENSION Citus;
-- Loop until we see there's no maintenance daemon running
DO $$begin
for i in 0 .. 100 loop
if i = 100 then raise 'Waited too long'; end if;
PERFORM pg_stat_clear_snapshot();
perform * from pg_stat_activity where application_name = 'Citus Maintenance Daemon';
if not found then exit; end if;
perform pg_sleep(0.1);
end loop;
end$$;
SELECT datid, datname, usename FROM pg_stat_activity WHERE application_name = 'Citus Maintenance Daemon';
datid | datname | usename
---------------------------------------------------------------------
(0 rows)
CREATE EXTENSION Citus;
-- Loop until we there's a maintenance daemon running
DO $$begin
for i in 0 .. 100 loop
if i = 100 then raise 'Waited too long'; end if;
PERFORM pg_stat_clear_snapshot();
perform * from pg_stat_activity where application_name = 'Citus Maintenance Daemon';
if found then exit; end if;
perform pg_sleep(0.1);
end loop;
end$$;
SELECT datid, datname, usename FROM pg_stat_activity WHERE application_name = 'Citus Maintenance Daemon';
datid | datname | usename
---------------------------------------------------------------------
16384 | regression | testuser2
(1 row)
RESET ROLE;
-- check that maintenance daemon gets (re-)started for the right user
DROP EXTENSION citus;
CREATE USER testuser SUPERUSER;
SET ROLE testuser;
CREATE EXTENSION citus;
SELECT datname, current_database(),
usename, (SELECT extowner::regrole::text FROM pg_extension WHERE extname = 'citus')
FROM test.maintenance_worker();
datname | current_database | usename | extowner
---------------------------------------------------------------------
regression | regression | testuser | testuser
(1 row)
-- and recreate as the right owner
RESET ROLE;
DROP EXTENSION citus;
CREATE EXTENSION citus;
-- Check that maintenance daemon can also be started in another database
CREATE DATABASE another;
NOTICE: Citus partially supports CREATE DATABASE for distributed databases
DETAIL: Citus does not propagate CREATE DATABASE command to workers
HINT: You can manually create a database and its extensions on workers.
\c another
CREATE EXTENSION citus;
CREATE SCHEMA test;
:create_function_test_maintenance_worker
-- see that the daemon started
SELECT datname, current_database(),
usename, (SELECT extowner::regrole::text FROM pg_extension WHERE extname = 'citus')
FROM test.maintenance_worker();
datname | current_database | usename | extowner
---------------------------------------------------------------------
another | another | postgres | postgres
(1 row)
-- Test that database with active worker can be dropped.
\c regression
CREATE SCHEMA test_daemon;
-- we create a similar function on the regression database
-- note that this function checks for the existence of the daemon
-- when not found, returns true else tries for 5 times and
-- returns false
CREATE OR REPLACE FUNCTION test_daemon.maintenance_daemon_died(p_dbname text)
RETURNS boolean
LANGUAGE plpgsql
AS $$
DECLARE
activity record;
BEGIN
PERFORM pg_stat_clear_snapshot();
SELECT * INTO activity FROM pg_stat_activity
WHERE application_name = 'Citus Maintenance Daemon' AND datname = p_dbname;
IF activity.pid IS NULL THEN
RETURN true;
ELSE
RETURN false;
END IF;
END;
$$;
-- drop the database and see that the daemon is dead
DROP DATABASE another;
SELECT
*
FROM
test_daemon.maintenance_daemon_died('another');
maintenance_daemon_died
---------------------------------------------------------------------
t
(1 row)
-- we don't need the schema and the function anymore
DROP SCHEMA test_daemon CASCADE;
NOTICE: drop cascades to function test_daemon.maintenance_daemon_died(text)
-- verify citus does not crash while creating a table when run against an older worker
-- create_distributed_table piggybacks multiple commands into single one, if one worker
-- did not have the required UDF it should fail instead of crash.
-- create a test database, configure citus with single node
CREATE DATABASE another;
NOTICE: Citus partially supports CREATE DATABASE for distributed databases
DETAIL: Citus does not propagate CREATE DATABASE command to workers
HINT: You can manually create a database and its extensions on workers.
\c - - - :worker_1_port
CREATE DATABASE another;
NOTICE: Citus partially supports CREATE DATABASE for distributed databases
DETAIL: Citus does not propagate CREATE DATABASE command to workers
HINT: You can manually create a database and its extensions on workers.
\c - - - :master_port
\c another
CREATE EXTENSION citus;
SET citus.enable_object_propagation TO off; -- prevent distributed transactions during add node
SELECT FROM master_add_node('localhost', :worker_1_port);
WARNING: citus.enable_object_propagation is off, not creating distributed objects on worker
DETAIL: distributed objects are only kept in sync when citus.enable_object_propagation is set to on. Newly activated nodes will not get these objects created
--
(1 row)
\c - - - :worker_1_port
CREATE EXTENSION citus;
ALTER FUNCTION assign_distributed_transaction_id(initiator_node_identifier integer, transaction_number bigint, transaction_stamp timestamp with time zone)
RENAME TO dummy_assign_function;
\c - - - :master_port
SET citus.shard_replication_factor to 1;
-- create_distributed_table command should fail
CREATE TABLE t1(a int, b int);
SET client_min_messages TO ERROR;
DO $$
BEGIN
BEGIN
SELECT create_distributed_table('t1', 'a');
EXCEPTION WHEN OTHERS THEN
RAISE 'create distributed table failed';
END;
END;
$$;
ERROR: create distributed table failed
CONTEXT: PL/pgSQL function inline_code_block line 6 at RAISE
\c regression
\c - - - :master_port
DROP DATABASE another;
\c - - - :worker_1_port
DROP DATABASE another;
\c - - - :master_port
-- only the regression database should have a maintenance daemon
SELECT count(*) FROM pg_stat_activity WHERE application_name = 'Citus Maintenance Daemon';
count
---------------------------------------------------------------------
1
(1 row)
-- recreate the extension immediately after the maintenancae daemon errors
SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE application_name = 'Citus Maintenance Daemon';
pg_cancel_backend
---------------------------------------------------------------------
t
(1 row)
DROP EXTENSION citus;
CREATE EXTENSION citus;
-- wait for maintenance daemon restart
SELECT datname, current_database(),
usename, (SELECT extowner::regrole::text FROM pg_extension WHERE extname = 'citus')
FROM test.maintenance_worker();
datname | current_database | usename | extowner
---------------------------------------------------------------------
regression | regression | postgres | postgres
(1 row)
-- confirm that there is only one maintenance daemon
SELECT count(*) FROM pg_stat_activity WHERE application_name = 'Citus Maintenance Daemon';
count
---------------------------------------------------------------------
1
(1 row)
-- kill the maintenance daemon
SELECT pg_cancel_backend(pid) FROM pg_stat_activity WHERE application_name = 'Citus Maintenance Daemon';
pg_cancel_backend
---------------------------------------------------------------------
t
(1 row)
-- reconnect
\c - - - :master_port
-- run something that goes through planner hook and therefore kicks of maintenance daemon
SELECT 1;
?column?
---------------------------------------------------------------------
1
(1 row)
-- wait for maintenance daemon restart
SELECT datname, current_database(),
usename, (SELECT extowner::regrole::text FROM pg_extension WHERE extname = 'citus')
FROM test.maintenance_worker();
datname | current_database | usename | extowner
---------------------------------------------------------------------
regression | regression | postgres | postgres
(1 row)
-- confirm that there is only one maintenance daemon
SELECT count(*) FROM pg_stat_activity WHERE application_name = 'Citus Maintenance Daemon';
count
---------------------------------------------------------------------
1
(1 row)
DROP TABLE version_mismatch_table;

View File

@ -0,0 +1,227 @@
-- print version above 11 (eg. 12 and above)
SHOW server_version \gset
SELECT substring(:'server_version', '\d+')::int > 11 AS version_above_eleven;
version_above_eleven
---------------------------------------------------------------------
t
(1 row)
-- list all postgres objects belonging to the citus extension
SELECT pg_catalog.pg_describe_object(classid, objid, 0) AS description
FROM pg_catalog.pg_depend, pg_catalog.pg_extension e
WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass
AND refobjid = e.oid
AND deptype = 'e'
AND e.extname='citus'
ORDER BY 1;
description
---------------------------------------------------------------------
access method cstore_tableam
event trigger citus_cascade_to_partition
event trigger cstore_ddl_event_end
foreign-data wrapper cstore_fdw
function alter_cstore_table_reset(regclass,boolean,boolean,boolean)
function alter_cstore_table_set(regclass,integer,integer,name)
function alter_role_if_exists(text,text)
function any_value(anyelement)
function any_value_agg(anyelement,anyelement)
function array_cat_agg(anyarray)
function assign_distributed_transaction_id(integer,bigint,timestamp with time zone)
function authinfo_valid(text)
function broadcast_intermediate_result(text,text)
function check_distributed_deadlocks()
function citus_add_rebalance_strategy(name,regproc,regproc,regproc,real,real)
function citus_blocking_pids(integer)
function citus_create_restore_point(text)
function citus_dist_stat_activity()
function citus_drop_trigger()
function citus_executor_name(integer)
function citus_extradata_container(internal)
function citus_finish_pg_upgrade()
function citus_internal.cstore_ensure_objects_exist()
function citus_internal.find_groupid_for_node(text,integer)
function citus_internal.pg_dist_node_trigger_func()
function citus_internal.pg_dist_rebalance_strategy_enterprise_check()
function citus_internal.pg_dist_rebalance_strategy_trigger_func()
function citus_internal.pg_dist_shard_placement_trigger_func()
function citus_internal.refresh_isolation_tester_prepared_statement()
function citus_internal.replace_isolation_tester_func()
function citus_internal.restore_isolation_tester_func()
function citus_isolation_test_session_is_blocked(integer,integer[])
function citus_json_concatenate(json,json)
function citus_json_concatenate_final(json)
function citus_jsonb_concatenate(jsonb,jsonb)
function citus_jsonb_concatenate_final(jsonb)
function citus_node_capacity_1(integer)
function citus_prepare_pg_upgrade()
function citus_query_stats()
function citus_relation_size(regclass)
function citus_remote_connection_stats()
function citus_server_id()
function citus_set_default_rebalance_strategy(text)
function citus_shard_allowed_on_node_true(bigint,integer)
function citus_shard_cost_1(bigint)
function citus_shard_cost_by_disk_size(bigint)
function citus_stat_statements()
function citus_stat_statements_reset()
function citus_table_is_visible(oid)
function citus_table_size(regclass)
function citus_text_send_as_jsonb(text)
function citus_total_relation_size(regclass)
function citus_truncate_trigger()
function citus_validate_rebalance_strategy_functions(regproc,regproc,regproc)
function citus_version()
function citus_worker_stat_activity()
function column_name_to_column(regclass,text)
function column_to_column_name(regclass,text)
function coord_combine_agg(oid,cstring,anyelement)
function coord_combine_agg_ffunc(internal,oid,cstring,anyelement)
function coord_combine_agg_sfunc(internal,oid,cstring,anyelement)
function create_citus_local_table(regclass)
function create_distributed_function(regprocedure,text,text)
function create_distributed_table(regclass,text,citus.distribution_type,text)
function create_intermediate_result(text,text)
function create_reference_table(regclass)
function cstore.cstore_ddl_event_end_trigger()
function cstore.cstore_fdw_handler()
function cstore.cstore_fdw_validator(text[],oid)
function cstore.cstore_tableam_handler(internal)
function cstore_table_size(regclass)
function distributed_tables_colocated(regclass,regclass)
function dump_global_wait_edges()
function dump_local_wait_edges()
function fetch_intermediate_results(text[],text,integer)
function get_all_active_transactions()
function get_colocated_shard_array(bigint)
function get_colocated_table_array(regclass)
function get_current_transaction_id()
function get_global_active_transactions()
function get_rebalance_progress()
function get_rebalance_table_shards_plan(regclass,real,integer,bigint[],boolean,name)
function get_shard_id_for_distribution_column(regclass,"any")
function isolate_tenant_to_new_shard(regclass,"any",text)
function json_cat_agg(json)
function jsonb_cat_agg(jsonb)
function lock_relation_if_exists(text,text)
function lock_shard_metadata(integer,bigint[])
function lock_shard_resources(integer,bigint[])
function mark_tables_colocated(regclass,regclass[])
function master_activate_node(text,integer)
function master_add_inactive_node(text,integer,integer,noderole,name)
function master_add_node(text,integer,integer,noderole,name)
function master_add_secondary_node(text,integer,text,integer,name)
function master_append_table_to_shard(bigint,text,text,integer)
function master_apply_delete_command(text)
function master_conninfo_cache_invalidate()
function master_copy_shard_placement(bigint,text,integer,text,integer,boolean,citus.shard_transfer_mode)
function master_create_distributed_table(regclass,text,citus.distribution_type)
function master_create_empty_shard(text)
function master_create_worker_shards(text,integer,integer)
function master_disable_node(text,integer)
function master_dist_local_group_cache_invalidate()
function master_dist_node_cache_invalidate()
function master_dist_object_cache_invalidate()
function master_dist_partition_cache_invalidate()
function master_dist_placement_cache_invalidate()
function master_dist_shard_cache_invalidate()
function master_drain_node(text,integer,citus.shard_transfer_mode,name)
function master_drop_all_shards(regclass,text,text)
function master_get_active_worker_nodes()
function master_get_new_placementid()
function master_get_new_shardid()
function master_get_table_ddl_events(text)
function master_get_table_metadata(text)
function master_modify_multiple_shards(text)
function master_move_shard_placement(bigint,text,integer,text,integer,citus.shard_transfer_mode)
function master_remove_distributed_table_metadata_from_workers(regclass,text,text)
function master_remove_node(text,integer)
function master_remove_partition_metadata(regclass,text,text)
function master_run_on_worker(text[],integer[],text[],boolean)
function master_set_node_property(text,integer,text,boolean)
function master_unmark_object_distributed(oid,oid,integer)
function master_update_node(integer,text,integer,boolean,integer)
function master_update_shard_statistics(bigint)
function master_update_table_statistics(regclass)
function poolinfo_valid(text)
function read_intermediate_result(text,citus_copy_format)
function read_intermediate_results(text[],citus_copy_format)
function rebalance_table_shards(regclass,real,integer,bigint[],citus.shard_transfer_mode,boolean,name)
function recover_prepared_transactions()
function relation_is_a_known_shard(regclass)
function replicate_reference_tables()
function replicate_table_shards(regclass,integer,integer,bigint[],citus.shard_transfer_mode)
function role_exists(name)
function run_command_on_colocated_placements(regclass,regclass,text,boolean)
function run_command_on_placements(regclass,text,boolean)
function run_command_on_shards(regclass,text,boolean)
function run_command_on_workers(text,boolean)
function shard_name(regclass,bigint)
function start_metadata_sync_to_node(text,integer)
function stop_metadata_sync_to_node(text,integer)
function truncate_local_data_after_distributing_table(regclass)
function undistribute_table(regclass)
function update_distributed_table_colocation(regclass,text)
function upgrade_to_reference_table(regclass)
function worker_append_table_to_shard(text,text,text,integer)
function worker_apply_inter_shard_ddl_command(bigint,text,bigint,text,text)
function worker_apply_sequence_command(text)
function worker_apply_sequence_command(text,regtype)
function worker_apply_shard_ddl_command(bigint,text)
function worker_apply_shard_ddl_command(bigint,text,text)
function worker_cleanup_job_schema_cache()
function worker_create_or_alter_role(text,text,text)
function worker_create_or_replace_object(text)
function worker_create_schema(bigint,text)
function worker_create_truncate_trigger(regclass)
function worker_drop_distributed_table(text)
function worker_fetch_foreign_file(text,text,bigint,text[],integer[])
function worker_fetch_partition_file(bigint,integer,integer,integer,text,integer)
function worker_hash("any")
function worker_hash_partition_table(bigint,integer,text,text,oid,anyarray)
function worker_last_saved_explain_analyze()
function worker_merge_files_into_table(bigint,integer,text[],text[])
function worker_partial_agg(oid,anyelement)
function worker_partial_agg_ffunc(internal)
function worker_partial_agg_sfunc(internal,oid,anyelement)
function worker_partition_query_result(text,text,integer,citus.distribution_type,text[],text[],boolean)
function worker_range_partition_table(bigint,integer,text,text,oid,anyarray)
function worker_record_sequence_dependency(regclass,regclass,name)
function worker_repartition_cleanup(bigint)
function worker_save_query_explain_analyze(text,jsonb)
schema citus
schema citus_internal
schema cstore
sequence pg_dist_colocationid_seq
sequence pg_dist_groupid_seq
sequence pg_dist_node_nodeid_seq
sequence pg_dist_placement_placementid_seq
sequence pg_dist_shardid_seq
table citus.pg_dist_object
table cstore.cstore_data_files
table cstore.cstore_skipnodes
table cstore.cstore_stripes
table pg_dist_authinfo
table pg_dist_colocation
table pg_dist_local_group
table pg_dist_node
table pg_dist_node_metadata
table pg_dist_partition
table pg_dist_placement
table pg_dist_poolinfo
table pg_dist_rebalance_strategy
table pg_dist_shard
table pg_dist_transaction
type citus.distribution_type
type citus.shard_transfer_mode
type citus_copy_format
type noderole
view citus_dist_stat_activity
view citus_lock_waits
view citus_shard_indexes_on_worker
view citus_shards_on_worker
view citus_stat_statements
view citus_worker_stat_activity
view cstore.cstore_options
view pg_dist_shard_placement
(207 rows)

View File

@ -0,0 +1,223 @@
-- print version above 11 (eg. 12 and above)
SHOW server_version \gset
SELECT substring(:'server_version', '\d+')::int > 11 AS version_above_eleven;
version_above_eleven
---------------------------------------------------------------------
f
(1 row)
-- list all postgres objects belonging to the citus extension
SELECT pg_catalog.pg_describe_object(classid, objid, 0) AS description
FROM pg_catalog.pg_depend, pg_catalog.pg_extension e
WHERE refclassid = 'pg_catalog.pg_extension'::pg_catalog.regclass
AND refobjid = e.oid
AND deptype = 'e'
AND e.extname='citus'
ORDER BY 1;
description
---------------------------------------------------------------------
event trigger citus_cascade_to_partition
event trigger cstore_ddl_event_end
foreign-data wrapper cstore_fdw
function alter_role_if_exists(text,text)
function any_value(anyelement)
function any_value_agg(anyelement,anyelement)
function array_cat_agg(anyarray)
function assign_distributed_transaction_id(integer,bigint,timestamp with time zone)
function authinfo_valid(text)
function broadcast_intermediate_result(text,text)
function check_distributed_deadlocks()
function citus_add_rebalance_strategy(name,regproc,regproc,regproc,real,real)
function citus_blocking_pids(integer)
function citus_create_restore_point(text)
function citus_dist_stat_activity()
function citus_drop_trigger()
function citus_executor_name(integer)
function citus_extradata_container(internal)
function citus_finish_pg_upgrade()
function citus_internal.cstore_ensure_objects_exist()
function citus_internal.find_groupid_for_node(text,integer)
function citus_internal.pg_dist_node_trigger_func()
function citus_internal.pg_dist_rebalance_strategy_enterprise_check()
function citus_internal.pg_dist_rebalance_strategy_trigger_func()
function citus_internal.pg_dist_shard_placement_trigger_func()
function citus_internal.refresh_isolation_tester_prepared_statement()
function citus_internal.replace_isolation_tester_func()
function citus_internal.restore_isolation_tester_func()
function citus_isolation_test_session_is_blocked(integer,integer[])
function citus_json_concatenate(json,json)
function citus_json_concatenate_final(json)
function citus_jsonb_concatenate(jsonb,jsonb)
function citus_jsonb_concatenate_final(jsonb)
function citus_node_capacity_1(integer)
function citus_prepare_pg_upgrade()
function citus_query_stats()
function citus_relation_size(regclass)
function citus_remote_connection_stats()
function citus_server_id()
function citus_set_default_rebalance_strategy(text)
function citus_shard_allowed_on_node_true(bigint,integer)
function citus_shard_cost_1(bigint)
function citus_shard_cost_by_disk_size(bigint)
function citus_stat_statements()
function citus_stat_statements_reset()
function citus_table_is_visible(oid)
function citus_table_size(regclass)
function citus_text_send_as_jsonb(text)
function citus_total_relation_size(regclass)
function citus_truncate_trigger()
function citus_validate_rebalance_strategy_functions(regproc,regproc,regproc)
function citus_version()
function citus_worker_stat_activity()
function column_name_to_column(regclass,text)
function column_to_column_name(regclass,text)
function coord_combine_agg(oid,cstring,anyelement)
function coord_combine_agg_ffunc(internal,oid,cstring,anyelement)
function coord_combine_agg_sfunc(internal,oid,cstring,anyelement)
function create_citus_local_table(regclass)
function create_distributed_function(regprocedure,text,text)
function create_distributed_table(regclass,text,citus.distribution_type,text)
function create_intermediate_result(text,text)
function create_reference_table(regclass)
function cstore.cstore_ddl_event_end_trigger()
function cstore.cstore_fdw_handler()
function cstore.cstore_fdw_validator(text[],oid)
function cstore_table_size(regclass)
function distributed_tables_colocated(regclass,regclass)
function dump_global_wait_edges()
function dump_local_wait_edges()
function fetch_intermediate_results(text[],text,integer)
function get_all_active_transactions()
function get_colocated_shard_array(bigint)
function get_colocated_table_array(regclass)
function get_current_transaction_id()
function get_global_active_transactions()
function get_rebalance_progress()
function get_rebalance_table_shards_plan(regclass,real,integer,bigint[],boolean,name)
function get_shard_id_for_distribution_column(regclass,"any")
function isolate_tenant_to_new_shard(regclass,"any",text)
function json_cat_agg(json)
function jsonb_cat_agg(jsonb)
function lock_relation_if_exists(text,text)
function lock_shard_metadata(integer,bigint[])
function lock_shard_resources(integer,bigint[])
function mark_tables_colocated(regclass,regclass[])
function master_activate_node(text,integer)
function master_add_inactive_node(text,integer,integer,noderole,name)
function master_add_node(text,integer,integer,noderole,name)
function master_add_secondary_node(text,integer,text,integer,name)
function master_append_table_to_shard(bigint,text,text,integer)
function master_apply_delete_command(text)
function master_conninfo_cache_invalidate()
function master_copy_shard_placement(bigint,text,integer,text,integer,boolean,citus.shard_transfer_mode)
function master_create_distributed_table(regclass,text,citus.distribution_type)
function master_create_empty_shard(text)
function master_create_worker_shards(text,integer,integer)
function master_disable_node(text,integer)
function master_dist_local_group_cache_invalidate()
function master_dist_node_cache_invalidate()
function master_dist_object_cache_invalidate()
function master_dist_partition_cache_invalidate()
function master_dist_placement_cache_invalidate()
function master_dist_shard_cache_invalidate()
function master_drain_node(text,integer,citus.shard_transfer_mode,name)
function master_drop_all_shards(regclass,text,text)
function master_get_active_worker_nodes()
function master_get_new_placementid()
function master_get_new_shardid()
function master_get_table_ddl_events(text)
function master_get_table_metadata(text)
function master_modify_multiple_shards(text)
function master_move_shard_placement(bigint,text,integer,text,integer,citus.shard_transfer_mode)
function master_remove_distributed_table_metadata_from_workers(regclass,text,text)
function master_remove_node(text,integer)
function master_remove_partition_metadata(regclass,text,text)
function master_run_on_worker(text[],integer[],text[],boolean)
function master_set_node_property(text,integer,text,boolean)
function master_unmark_object_distributed(oid,oid,integer)
function master_update_node(integer,text,integer,boolean,integer)
function master_update_shard_statistics(bigint)
function master_update_table_statistics(regclass)
function poolinfo_valid(text)
function read_intermediate_result(text,citus_copy_format)
function read_intermediate_results(text[],citus_copy_format)
function rebalance_table_shards(regclass,real,integer,bigint[],citus.shard_transfer_mode,boolean,name)
function recover_prepared_transactions()
function relation_is_a_known_shard(regclass)
function replicate_reference_tables()
function replicate_table_shards(regclass,integer,integer,bigint[],citus.shard_transfer_mode)
function role_exists(name)
function run_command_on_colocated_placements(regclass,regclass,text,boolean)
function run_command_on_placements(regclass,text,boolean)
function run_command_on_shards(regclass,text,boolean)
function run_command_on_workers(text,boolean)
function shard_name(regclass,bigint)
function start_metadata_sync_to_node(text,integer)
function stop_metadata_sync_to_node(text,integer)
function truncate_local_data_after_distributing_table(regclass)
function undistribute_table(regclass)
function update_distributed_table_colocation(regclass,text)
function upgrade_to_reference_table(regclass)
function worker_append_table_to_shard(text,text,text,integer)
function worker_apply_inter_shard_ddl_command(bigint,text,bigint,text,text)
function worker_apply_sequence_command(text)
function worker_apply_sequence_command(text,regtype)
function worker_apply_shard_ddl_command(bigint,text)
function worker_apply_shard_ddl_command(bigint,text,text)
function worker_cleanup_job_schema_cache()
function worker_create_or_alter_role(text,text,text)
function worker_create_or_replace_object(text)
function worker_create_schema(bigint,text)
function worker_create_truncate_trigger(regclass)
function worker_drop_distributed_table(text)
function worker_fetch_foreign_file(text,text,bigint,text[],integer[])
function worker_fetch_partition_file(bigint,integer,integer,integer,text,integer)
function worker_hash("any")
function worker_hash_partition_table(bigint,integer,text,text,oid,anyarray)
function worker_last_saved_explain_analyze()
function worker_merge_files_into_table(bigint,integer,text[],text[])
function worker_partial_agg(oid,anyelement)
function worker_partial_agg_ffunc(internal)
function worker_partial_agg_sfunc(internal,oid,anyelement)
function worker_partition_query_result(text,text,integer,citus.distribution_type,text[],text[],boolean)
function worker_range_partition_table(bigint,integer,text,text,oid,anyarray)
function worker_record_sequence_dependency(regclass,regclass,name)
function worker_repartition_cleanup(bigint)
function worker_save_query_explain_analyze(text,jsonb)
schema citus
schema citus_internal
schema cstore
sequence pg_dist_colocationid_seq
sequence pg_dist_groupid_seq
sequence pg_dist_node_nodeid_seq
sequence pg_dist_placement_placementid_seq
sequence pg_dist_shardid_seq
table citus.pg_dist_object
table cstore.cstore_data_files
table cstore.cstore_skipnodes
table cstore.cstore_stripes
table pg_dist_authinfo
table pg_dist_colocation
table pg_dist_local_group
table pg_dist_node
table pg_dist_node_metadata
table pg_dist_partition
table pg_dist_placement
table pg_dist_poolinfo
table pg_dist_rebalance_strategy
table pg_dist_shard
table pg_dist_transaction
type citus.distribution_type
type citus.shard_transfer_mode
type citus_copy_format
type noderole
view citus_dist_stat_activity
view citus_lock_waits
view citus_shard_indexes_on_worker
view citus_shards_on_worker
view citus_stat_statements
view citus_worker_stat_activity
view cstore.cstore_options
view pg_dist_shard_placement
(203 rows)

View File

@ -0,0 +1,73 @@
--
-- Test block filtering in cstore_fdw using min/max values in stripe skip lists.
--
--
-- filtered_row_count returns number of rows filtered by the WHERE clause.
-- If blocks get filtered by cstore_fdw, less rows are passed to WHERE
-- clause, so this function should return a lower number.
--
CREATE OR REPLACE FUNCTION filtered_row_count (query text) RETURNS bigint AS
$$
DECLARE
result bigint;
rec text;
BEGIN
result := 0;
FOR rec IN EXECUTE 'EXPLAIN ANALYZE ' || query LOOP
IF rec ~ '^\s+Rows Removed by Filter' then
result := regexp_replace(rec, '[^0-9]*', '', 'g');
END IF;
END LOOP;
RETURN result;
END;
$$ LANGUAGE PLPGSQL;
-- Create and load data
-- block_row_count '1000', stripe_row_count '2000'
set cstore.stripe_row_count = 2000;
set cstore.block_row_count = 1000;
CREATE TABLE test_block_filtering (a int)
USING cstore_tableam;
COPY test_block_filtering FROM '@abs_srcdir@/data/block_filtering.csv' WITH CSV;
-- Verify that filtered_row_count is less than 1000 for the following queries
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 200');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a > 200');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 9900');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a > 9900');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 0');
-- Verify that filtered_row_count is less than 2000 for the following queries
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a BETWEEN 1 AND 10');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a BETWEEN 990 AND 2010');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a BETWEEN -10 AND 0');
-- Load data for second time and verify that filtered_row_count is exactly twice as before
COPY test_block_filtering FROM '@abs_srcdir@/data/block_filtering.csv' WITH CSV;
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 200');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 0');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a BETWEEN 990 AND 2010');
set cstore.stripe_row_count to default;
set cstore.block_row_count to default;
-- Verify that we are fine with collations which use a different alphabet order
CREATE TABLE collation_block_filtering_test(A text collate "da_DK")
USING cstore_tableam;
COPY collation_block_filtering_test FROM STDIN;
A
Å
B
\.
SELECT * FROM collation_block_filtering_test WHERE A > 'B';

View File

@ -0,0 +1,17 @@
--
-- Test copying data from cstore_fdw tables.
--
CREATE TABLE test_contestant(handle TEXT, birthdate DATE, rating INT,
percentile FLOAT, country CHAR(3), achievements TEXT[])
USING cstore_tableam;
-- load table data from file
COPY test_contestant FROM '@abs_srcdir@/data/contestants.1.csv' WITH CSV;
-- export using COPY table TO ...
COPY test_contestant TO STDOUT;
-- export using COPY (SELECT * FROM table) TO ...
COPY (select * from test_contestant) TO STDOUT;
DROP TABLE test_contestant CASCADE;

View File

@ -0,0 +1,20 @@
--
-- Test the CREATE statements related to cstore.
--
-- Create uncompressed table
CREATE TABLE contestant (handle TEXT, birthdate DATE, rating INT,
percentile FLOAT, country CHAR(3), achievements TEXT[])
USING cstore_tableam;
-- Create compressed table with automatically determined file path
-- COMPRESSED
CREATE TABLE contestant_compressed (handle TEXT, birthdate DATE, rating INT,
percentile FLOAT, country CHAR(3), achievements TEXT[])
USING cstore_tableam;
-- Test that querying an empty table works
ANALYZE contestant;
SELECT count(*) FROM contestant;

View File

@ -0,0 +1,68 @@
--
-- Test loading and reading different data types to/from cstore_fdw foreign tables.
--
-- Settings to make the result deterministic
SET datestyle = "ISO, YMD";
SET timezone to 'GMT';
SET intervalstyle TO 'POSTGRES_VERBOSE';
-- Test array types
CREATE TABLE test_array_types (int_array int[], bigint_array bigint[],
text_array text[]) USING cstore_tableam;
COPY test_array_types FROM '@abs_srcdir@/data/array_types.csv' WITH CSV;
SELECT * FROM test_array_types;
-- Test date/time types
CREATE TABLE test_datetime_types (timestamp timestamp,
timestamp_with_timezone timestamp with time zone, date date, time time,
interval interval) USING cstore_tableam;
COPY test_datetime_types FROM '@abs_srcdir@/data/datetime_types.csv' WITH CSV;
SELECT * FROM test_datetime_types;
-- Test enum and composite types
CREATE TYPE enum_type AS ENUM ('a', 'b', 'c');
CREATE TYPE composite_type AS (a int, b text);
CREATE TABLE test_enum_and_composite_types (enum enum_type,
composite composite_type) USING cstore_tableam;
COPY test_enum_and_composite_types FROM
'@abs_srcdir@/data/enum_and_composite_types.csv' WITH CSV;
SELECT * FROM test_enum_and_composite_types;
-- Test range types
CREATE TABLE test_range_types (int4range int4range, int8range int8range,
numrange numrange, tsrange tsrange) USING cstore_tableam;
COPY test_range_types FROM '@abs_srcdir@/data/range_types.csv' WITH CSV;
SELECT * FROM test_range_types;
-- Test other types
CREATE TABLE test_other_types (bool boolean, bytea bytea, money money,
inet inet, bitstring bit varying(5), uuid uuid, json json) USING cstore_tableam;
COPY test_other_types FROM '@abs_srcdir@/data/other_types.csv' WITH CSV;
SELECT * FROM test_other_types;
-- Test null values
CREATE TABLE test_null_values (a int, b int[], c composite_type)
USING cstore_tableam;
COPY test_null_values FROM '@abs_srcdir@/data/null_values.csv' WITH CSV;
SELECT * FROM test_null_values;

View File

@ -0,0 +1,46 @@
--
-- Test loading data into cstore_fdw tables.
--
-- COPY with incorrect delimiter
COPY contestant FROM '@abs_srcdir@/data/contestants.1.csv'
WITH DELIMITER '|'; -- ERROR
-- COPY with invalid program
COPY contestant FROM PROGRAM 'invalid_program' WITH CSV; -- ERROR
-- COPY into uncompressed table from file
COPY contestant FROM '@abs_srcdir@/data/contestants.1.csv' WITH CSV;
-- COPY into uncompressed table from program
COPY contestant FROM PROGRAM 'cat @abs_srcdir@/data/contestants.2.csv' WITH CSV;
-- COPY into compressed table
set cstore.compression = 'pglz';
COPY contestant_compressed FROM '@abs_srcdir@/data/contestants.1.csv' WITH CSV;
-- COPY into uncompressed table from program
COPY contestant_compressed FROM PROGRAM 'cat @abs_srcdir@/data/contestants.2.csv'
WITH CSV;
set cstore.compression to default;
-- Test column list
CREATE TABLE famous_constants (id int, name text, value real)
USING cstore_tableam;
COPY famous_constants (value, name, id) FROM STDIN WITH CSV;
3.141,pi,1
2.718,e,2
0.577,gamma,3
5.291e-11,bohr radius,4
\.
COPY famous_constants (name, value) FROM STDIN WITH CSV;
avagadro,6.022e23
electron mass,9.109e-31
proton mass,1.672e-27
speed of light,2.997e8
\.
SELECT * FROM famous_constants ORDER BY id, name;
DROP TABLE famous_constants;

View File

@ -0,0 +1,69 @@
--
-- Test block filtering in cstore_fdw using min/max values in stripe skip lists.
--
--
-- filtered_row_count returns number of rows filtered by the WHERE clause.
-- If blocks get filtered by cstore_fdw, less rows are passed to WHERE
-- clause, so this function should return a lower number.
--
CREATE OR REPLACE FUNCTION filtered_row_count (query text) RETURNS bigint AS
$$
DECLARE
result bigint;
rec text;
BEGIN
result := 0;
FOR rec IN EXECUTE 'EXPLAIN ANALYZE ' || query LOOP
IF rec ~ '^\s+Rows Removed by Filter' then
result := regexp_replace(rec, '[^0-9]*', '', 'g');
END IF;
END LOOP;
RETURN result;
END;
$$ LANGUAGE PLPGSQL;
-- Create and load data
CREATE FOREIGN TABLE test_block_filtering (a int)
SERVER cstore_server
OPTIONS(block_row_count '1000', stripe_row_count '2000');
COPY test_block_filtering FROM '@abs_srcdir@/data/block_filtering.csv' WITH CSV;
-- Verify that filtered_row_count is less than 1000 for the following queries
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 200');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a > 200');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 9900');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a > 9900');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 0');
-- Verify that filtered_row_count is less than 2000 for the following queries
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a BETWEEN 1 AND 10');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a BETWEEN 990 AND 2010');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a BETWEEN -10 AND 0');
-- Load data for second time and verify that filtered_row_count is exactly twice as before
COPY test_block_filtering FROM '@abs_srcdir@/data/block_filtering.csv' WITH CSV;
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 200');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 0');
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a BETWEEN 990 AND 2010');
-- Verify that we are fine with collations which use a different alphabet order
CREATE FOREIGN TABLE collation_block_filtering_test(A text collate "da_DK")
SERVER cstore_server;
COPY collation_block_filtering_test FROM STDIN;
A
Å
B
\.
SELECT * FROM collation_block_filtering_test WHERE A > 'B';

View File

@ -0,0 +1,17 @@
--
-- Test copying data from cstore_fdw tables.
--
CREATE FOREIGN TABLE test_contestant(handle TEXT, birthdate DATE, rating INT,
percentile FLOAT, country CHAR(3), achievements TEXT[])
SERVER cstore_server;
-- load table data from file
COPY test_contestant FROM '@abs_srcdir@/data/contestants.1.csv' WITH CSV;
-- export using COPY table TO ...
COPY test_contestant TO STDOUT;
-- export using COPY (SELECT * FROM table) TO ...
COPY (select * from test_contestant) TO STDOUT;
DROP FOREIGN TABLE test_contestant CASCADE;

View File

@ -0,0 +1,39 @@
--
-- Test the CREATE statements related to cstore_fdw.
--
CREATE SERVER cstore_server FOREIGN DATA WRAPPER cstore_fdw;
-- Validator tests
CREATE FOREIGN TABLE test_validator_invalid_option ()
SERVER cstore_server
OPTIONS(bad_option_name '1'); -- ERROR
CREATE FOREIGN TABLE test_validator_invalid_stripe_row_count ()
SERVER cstore_server
OPTIONS(stripe_row_count '0'); -- ERROR
CREATE FOREIGN TABLE test_validator_invalid_block_row_count ()
SERVER cstore_server
OPTIONS(block_row_count '0'); -- ERROR
CREATE FOREIGN TABLE test_validator_invalid_compression_type ()
SERVER cstore_server
OPTIONS(compression 'invalid_compression'); -- ERROR
-- Create uncompressed table
CREATE FOREIGN TABLE contestant (handle TEXT, birthdate DATE, rating INT,
percentile FLOAT, country CHAR(3), achievements TEXT[])
SERVER cstore_server;
-- Create compressed table with automatically determined file path
CREATE FOREIGN TABLE contestant_compressed (handle TEXT, birthdate DATE, rating INT,
percentile FLOAT, country CHAR(3), achievements TEXT[])
SERVER cstore_server
OPTIONS(compression 'pglz');
-- Test that querying an empty table works
ANALYZE contestant;
SELECT count(*) FROM contestant;

View File

@ -0,0 +1,68 @@
--
-- Test loading and reading different data types to/from cstore_fdw foreign tables.
--
-- Settings to make the result deterministic
SET datestyle = "ISO, YMD";
SET timezone to 'GMT';
SET intervalstyle TO 'POSTGRES_VERBOSE';
-- Test array types
CREATE FOREIGN TABLE test_array_types (int_array int[], bigint_array bigint[],
text_array text[]) SERVER cstore_server;
COPY test_array_types FROM '@abs_srcdir@/data/array_types.csv' WITH CSV;
SELECT * FROM test_array_types;
-- Test date/time types
CREATE FOREIGN TABLE test_datetime_types (timestamp timestamp,
timestamp_with_timezone timestamp with time zone, date date, time time,
interval interval) SERVER cstore_server;
COPY test_datetime_types FROM '@abs_srcdir@/data/datetime_types.csv' WITH CSV;
SELECT * FROM test_datetime_types;
-- Test enum and composite types
CREATE TYPE enum_type AS ENUM ('a', 'b', 'c');
CREATE TYPE composite_type AS (a int, b text);
CREATE FOREIGN TABLE test_enum_and_composite_types (enum enum_type,
composite composite_type) SERVER cstore_server;
COPY test_enum_and_composite_types FROM
'@abs_srcdir@/data/enum_and_composite_types.csv' WITH CSV;
SELECT * FROM test_enum_and_composite_types;
-- Test range types
CREATE FOREIGN TABLE test_range_types (int4range int4range, int8range int8range,
numrange numrange, tsrange tsrange) SERVER cstore_server;
COPY test_range_types FROM '@abs_srcdir@/data/range_types.csv' WITH CSV;
SELECT * FROM test_range_types;
-- Test other types
CREATE FOREIGN TABLE test_other_types (bool boolean, bytea bytea, money money,
inet inet, bitstring bit varying(5), uuid uuid, json json) SERVER cstore_server;
COPY test_other_types FROM '@abs_srcdir@/data/other_types.csv' WITH CSV;
SELECT * FROM test_other_types;
-- Test null values
CREATE FOREIGN TABLE test_null_values (a int, b int[], c composite_type)
SERVER cstore_server;
COPY test_null_values FROM '@abs_srcdir@/data/null_values.csv' WITH CSV;
SELECT * FROM test_null_values;

View File

@ -0,0 +1,44 @@
--
-- Test loading data into cstore_fdw tables.
--
-- COPY with incorrect delimiter
COPY contestant FROM '@abs_srcdir@/data/contestants.1.csv'
WITH DELIMITER '|'; -- ERROR
-- COPY with invalid program
COPY contestant FROM PROGRAM 'invalid_program' WITH CSV; -- ERROR
-- COPY into uncompressed table from file
COPY contestant FROM '@abs_srcdir@/data/contestants.1.csv' WITH CSV;
-- COPY into uncompressed table from program
COPY contestant FROM PROGRAM 'cat @abs_srcdir@/data/contestants.2.csv' WITH CSV;
-- COPY into compressed table
COPY contestant_compressed FROM '@abs_srcdir@/data/contestants.1.csv' WITH CSV;
-- COPY into uncompressed table from program
COPY contestant_compressed FROM PROGRAM 'cat @abs_srcdir@/data/contestants.2.csv'
WITH CSV;
-- Test column list
CREATE FOREIGN TABLE famous_constants (id int, name text, value real)
SERVER cstore_server;
COPY famous_constants (value, name, id) FROM STDIN WITH CSV;
3.141,pi,1
2.718,e,2
0.577,gamma,3
5.291e-11,bohr radius,4
\.
COPY famous_constants (name, value) FROM STDIN WITH CSV;
avagadro,6.022e23
electron mass,9.109e-31
proton mass,1.672e-27
speed of light,2.997e8
\.
SELECT * FROM famous_constants ORDER BY id, name;
DROP FOREIGN TABLE famous_constants;

View File

@ -0,0 +1,120 @@
--
-- Test block filtering in cstore_fdw using min/max values in stripe skip lists.
--
--
-- filtered_row_count returns number of rows filtered by the WHERE clause.
-- If blocks get filtered by cstore_fdw, less rows are passed to WHERE
-- clause, so this function should return a lower number.
--
CREATE OR REPLACE FUNCTION filtered_row_count (query text) RETURNS bigint AS
$$
DECLARE
result bigint;
rec text;
BEGIN
result := 0;
FOR rec IN EXECUTE 'EXPLAIN ANALYZE ' || query LOOP
IF rec ~ '^\s+Rows Removed by Filter' then
result := regexp_replace(rec, '[^0-9]*', '', 'g');
END IF;
END LOOP;
RETURN result;
END;
$$ LANGUAGE PLPGSQL;
-- Create and load data
-- block_row_count '1000', stripe_row_count '2000'
set cstore.stripe_row_count = 2000;
set cstore.block_row_count = 1000;
CREATE TABLE test_block_filtering (a int)
USING cstore_tableam;
COPY test_block_filtering FROM '@abs_srcdir@/data/block_filtering.csv' WITH CSV;
-- Verify that filtered_row_count is less than 1000 for the following queries
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering');
filtered_row_count
--------------------
0
(1 row)
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 200');
filtered_row_count
--------------------
801
(1 row)
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a > 200');
filtered_row_count
--------------------
200
(1 row)
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 9900');
filtered_row_count
--------------------
101
(1 row)
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a > 9900');
filtered_row_count
--------------------
900
(1 row)
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 0');
filtered_row_count
--------------------
0
(1 row)
-- Verify that filtered_row_count is less than 2000 for the following queries
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a BETWEEN 1 AND 10');
filtered_row_count
--------------------
990
(1 row)
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a BETWEEN 990 AND 2010');
filtered_row_count
--------------------
1979
(1 row)
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a BETWEEN -10 AND 0');
filtered_row_count
--------------------
0
(1 row)
-- Load data for second time and verify that filtered_row_count is exactly twice as before
COPY test_block_filtering FROM '@abs_srcdir@/data/block_filtering.csv' WITH CSV;
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 200');
filtered_row_count
--------------------
1602
(1 row)
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a < 0');
filtered_row_count
--------------------
0
(1 row)
SELECT filtered_row_count('SELECT count(*) FROM test_block_filtering WHERE a BETWEEN 990 AND 2010');
filtered_row_count
--------------------
3958
(1 row)
set cstore.stripe_row_count to default;
set cstore.block_row_count to default;
-- Verify that we are fine with collations which use a different alphabet order
CREATE TABLE collation_block_filtering_test(A text collate "da_DK")
USING cstore_tableam;
COPY collation_block_filtering_test FROM STDIN;
SELECT * FROM collation_block_filtering_test WHERE A > 'B';
a
---
Å
(1 row)

View File

@ -0,0 +1,23 @@
--
-- Test copying data from cstore_fdw tables.
--
CREATE TABLE test_contestant(handle TEXT, birthdate DATE, rating INT,
percentile FLOAT, country CHAR(3), achievements TEXT[])
USING cstore_tableam;
-- load table data from file
COPY test_contestant FROM '@abs_srcdir@/data/contestants.1.csv' WITH CSV;
-- export using COPY table TO ...
COPY test_contestant TO STDOUT;
a 01-10-1990 2090 97.1 XA {a}
b 11-01-1990 2203 98.1 XA {a,b}
c 11-01-1988 2907 99.4 XB {w,y}
d 05-05-1985 2314 98.3 XB {}
e 05-05-1995 2236 98.2 XC {a}
-- export using COPY (SELECT * FROM table) TO ...
COPY (select * from test_contestant) TO STDOUT;
a 01-10-1990 2090 97.1 XA {a}
b 11-01-1990 2203 98.1 XA {a,b}
c 11-01-1988 2907 99.4 XB {w,y}
d 05-05-1985 2314 98.3 XB {}
e 05-05-1995 2236 98.2 XC {a}
DROP TABLE test_contestant CASCADE;

Some files were not shown because too many files have changed in this diff Show More