mirror of https://github.com/citusdata/citus.git
Merge pull request #4311 from citusdata/merge-cstore
Merge cstore into the citus repopull/4319/head
commit
2e09116b30
|
@ -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]
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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])
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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-*/
|
|
@ -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;
|
||||||
|
}
|
|
@ -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";
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
@ -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
|
@ -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;
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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;
|
|
@ -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;
|
|
@ -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';
|
|
@ -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';
|
|
@ -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';
|
|
@ -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';
|
|
@ -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';
|
|
@ -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';
|
|
@ -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;
|
|
@ -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;
|
|
@ -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 +=
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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';
|
|
@ -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$;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 */
|
|
@ -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 */
|
|
@ -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 */
|
|
@ -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
|
|
@ -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 */
|
|
@ -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 */
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
test: am_write_concurrency
|
||||||
|
test: am_vacuum_vs_insert
|
|
@ -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
|
|
@ -0,0 +1 @@
|
||||||
|
# just an empty file now, please remove when we have a test
|
|
@ -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
|
|
@ -0,0 +1,3 @@
|
||||||
|
"{1,2,3}","{1,2,3}","{a,b,c}"
|
||||||
|
{},{},{}
|
||||||
|
"{-2147483648,2147483647}","{-9223372036854775808,9223372036854775807}","{""""}"
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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}
|
|
|
@ -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}"
|
|
|
@ -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
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
a,"(2,b)"
|
||||||
|
b,"(3,c)"
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
,{NULL},"(,)"
|
||||||
|
,,
|
|
|
@ -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,[]
|
|
|
@ -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
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
||||||
|
Parsed test spec with 1 sessions
|
||||||
|
|
||||||
|
starting permutation: s1a
|
||||||
|
step s1a:
|
||||||
|
CREATE EXTENSION cstore_fdw;
|
||||||
|
|
|
@ -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;
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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';
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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';
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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)
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue