diff --git a/.github/gh_matrix_builder.py b/.github/gh_matrix_builder.py index f16e30659b6..75579201d8f 100755 --- a/.github/gh_matrix_builder.py +++ b/.github/gh_matrix_builder.py @@ -89,7 +89,7 @@ def build_debug_config(overrides): "ignored_tests": default_ignored_tests, "name": "Debug", "os": "ubuntu-22.04", - "pg_extra_args": "--enable-debug --enable-cassert --with-llvm LLVM_CONFIG=llvm-config-14", + "pg_extra_args": "CFLAGS=-march=native --enable-debug --enable-cassert --with-llvm LLVM_CONFIG=llvm-config-14", "pg_extensions": "postgres_fdw test_decoding pageinspect pgstattuple", "pginstallcheck": True, "tsdb_build_args": "-DWARNINGS_AS_ERRORS=ON -DREQUIRE_ALL_TESTS=ON", diff --git a/.unreleased/hash-grouping-multiple b/.unreleased/hash-grouping-multiple new file mode 100644 index 00000000000..56ae3608ea0 --- /dev/null +++ b/.unreleased/hash-grouping-multiple @@ -0,0 +1 @@ +Implements: #7754 Vectorized aggregation with grouping by several columns diff --git a/tsl/src/import/ts_simplehash.h b/tsl/src/import/ts_simplehash.h new file mode 100644 index 00000000000..a2a1451753b --- /dev/null +++ b/tsl/src/import/ts_simplehash.h @@ -0,0 +1,1045 @@ +/* + * This file and its contents are licensed under the Timescale License. + * Please see the included NOTICE for copyright information and + * LICENSE-TIMESCALE for a copy of the license. + */ + +/* + * This file contains source code that was copied and/or modified from + * the PostgreSQL database, which is licensed under the open-source + * PostgreSQL License. Please see the NOTICE at the top level + * directory for a copy of the PostgreSQL License. + */ + +/* + * simplehash.h + * + * When included this file generates a "templated" (by way of macros) + * open-addressing hash table implementation specialized to user-defined + * types. + * + * It's probably not worthwhile to generate such a specialized implementation + * for hash tables that aren't performance or space sensitive. + * + * Compared to dynahash, simplehash has the following benefits: + * + * - Due to the "templated" code generation has known structure sizes and no + * indirect function calls (which show up substantially in dynahash + * profiles). These features considerably increase speed for small + * entries. + * - Open addressing has better CPU cache behavior than dynahash's chained + * hashtables. + * - The generated interface is type-safe and easier to use than dynahash, + * though at the cost of more complex setup. + * - Allocates memory in a MemoryContext or another allocator with a + * malloc/free style interface (which isn't easily usable in a shared + * memory context) + * - Does not require the overhead of a separate memory context. + * + * Usage notes: + * + * To generate a hash-table and associated functions for a use case several + * macros have to be #define'ed before this file is included. Including + * the file #undef's all those, so a new hash table can be generated + * afterwards. + * The relevant parameters are: + * - SH_PREFIX - prefix for all symbol names generated. A prefix of 'foo' + * will result in hash table type 'foo_hash' and functions like + * 'foo_insert'/'foo_lookup' and so forth. + * - SH_ELEMENT_TYPE - type of the contained elements + * - SH_KEY_TYPE - type of the hashtable's key + * - SH_DECLARE - if defined function prototypes and type declarations are + * generated + * - SH_DEFINE - if defined function definitions are generated + * - SH_SCOPE - in which scope (e.g. extern, static inline) do function + * declarations reside + * - SH_RAW_ALLOCATOR - if defined, memory contexts are not used; instead, + * use this to allocate bytes. The allocator must zero the returned space. + * - SH_USE_NONDEFAULT_ALLOCATOR - if defined no element allocator functions + * are defined, so you can supply your own + * The following parameters are only relevant when SH_DEFINE is defined: + * - SH_KEY - name of the element in SH_ELEMENT_TYPE containing the hash key + * - SH_EQUAL(table, a, b) - compare two table keys + * - SH_HASH_KEY(table, key) - generate hash for the key + * - SH_STORE_HASH - if defined the hash is stored in the elements + * - SH_GET_HASH(tb, a) - return the field to store the hash in + * + * While SH_STORE_HASH (and subsequently SH_GET_HASH) are optional, because + * the hash table implementation needs to compare hashes to move elements + * (particularly when growing the hash), it's preferable, if possible, to + * store the element's hash in the element's data type. If the hash is so + * stored, the hash table will also compare hashes before calling SH_EQUAL + * when comparing two keys. + * + * For convenience the hash table create functions accept a void pointer + * that will be stored in the hash table type's member private_data. This + * allows callbacks to reference caller provided data. + * + * For examples of usage look at tidbitmap.c (file local definition) and + * execnodes.h/execGrouping.c (exposed declaration, file local + * implementation). + * + * Hash table design: + * + * The hash table design chosen is a variant of linear open-addressing. The + * reason for doing so is that linear addressing is CPU cache & pipeline + * friendly. The biggest disadvantage of simple linear addressing schemes + * are highly variable lookup times due to clustering, and deletions + * leaving a lot of tombstones around. To address these issues a variant + * of "robin hood" hashing is employed. Robin hood hashing optimizes + * chaining lengths by moving elements close to their optimal bucket + * ("rich" elements), out of the way if a to-be-inserted element is further + * away from its optimal position (i.e. it's "poor"). While that can make + * insertions slower, the average lookup performance is a lot better, and + * higher fill factors can be used in a still performant manner. To avoid + * tombstones - which normally solve the issue that a deleted node's + * presence is relevant to determine whether a lookup needs to continue + * looking or is done - buckets following a deleted element are shifted + * backwards, unless they're empty or already at their optimal position. + * + * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/include/lib/simplehash.h + */ + +#include "port/pg_bitutils.h" + +/* helpers */ +#define SH_MAKE_PREFIX(a) CppConcat(a, _) +#define SH_MAKE_NAME(name) SH_MAKE_NAME_(SH_MAKE_PREFIX(SH_PREFIX), name) +#define SH_MAKE_NAME_(a, b) CppConcat(a, b) + +/* name macros for: */ + +/* type declarations */ +#define SH_TYPE SH_MAKE_NAME(hash) +#define SH_ITERATOR SH_MAKE_NAME(iterator) + +/* function declarations */ +#define SH_CREATE SH_MAKE_NAME(create) +#define SH_DESTROY SH_MAKE_NAME(destroy) +#define SH_RESET SH_MAKE_NAME(reset) +#define SH_INSERT SH_MAKE_NAME(insert) +#define SH_INSERT_HASH SH_MAKE_NAME(insert_hash) +#define SH_LOOKUP SH_MAKE_NAME(lookup) +#define SH_LOOKUP_HASH SH_MAKE_NAME(lookup_hash) +#define SH_GROW SH_MAKE_NAME(grow) +#define SH_START_ITERATE SH_MAKE_NAME(start_iterate) +#define SH_START_ITERATE_AT SH_MAKE_NAME(start_iterate_at) +#define SH_ITERATE SH_MAKE_NAME(iterate) +#define SH_ALLOCATE SH_MAKE_NAME(allocate) +#define SH_FREE SH_MAKE_NAME(free) +#define SH_STAT SH_MAKE_NAME(stat) + +/* internal helper functions (no externally visible prototypes) */ +#define SH_COMPUTE_PARAMETERS SH_MAKE_NAME(compute_parameters) +#define SH_NEXT SH_MAKE_NAME(next) +#define SH_PREV SH_MAKE_NAME(prev) +#define SH_DISTANCE_FROM_OPTIMAL SH_MAKE_NAME(distance) +#define SH_INITIAL_BUCKET SH_MAKE_NAME(initial_bucket) +#define SH_ENTRY_HASH SH_MAKE_NAME(entry_hash) +#define SH_INSERT_HASH_INTERNAL SH_MAKE_NAME(insert_hash_internal) +#define SH_LOOKUP_HASH_INTERNAL SH_MAKE_NAME(lookup_hash_internal) + +/* generate forward declarations necessary to use the hash table */ +#ifdef SH_DECLARE + +/* type definitions */ +typedef struct SH_TYPE +{ + /* + * Size of data / bucket array, 64 bits to handle UINT32_MAX sized hash + * tables. Note that the maximum number of elements is lower + * (SH_MAX_FILLFACTOR) + */ + uint64 size; + + /* how many elements have valid contents */ + uint32 members; + + /* mask for bucket and size calculations, based on size */ + uint32 sizemask; + + /* boundary after which to grow hashtable */ + uint32 grow_threshold; + + /* hash buckets */ + SH_ELEMENT_TYPE *restrict data; + +#ifndef SH_RAW_ALLOCATOR + /* memory context to use for allocations */ + MemoryContext ctx; +#endif + + /* user defined data, useful for callbacks */ + void *private_data; +} SH_TYPE; + +typedef struct SH_ITERATOR +{ + uint32 cur; /* current element */ + uint32 end; + bool done; /* iterator exhausted? */ +} SH_ITERATOR; + +/* externally visible function prototypes */ +#ifdef SH_RAW_ALLOCATOR +/* _hash _create(uint32 nelements, void *private_data) */ +SH_SCOPE SH_TYPE *SH_CREATE(uint32 nelements, void *private_data); +#else +/* + * _hash _create(MemoryContext ctx, uint32 nelements, + * void *private_data) + */ +SH_SCOPE SH_TYPE *SH_CREATE(MemoryContext ctx, uint32 nelements, void *private_data); +#endif + +/* void _destroy(_hash *tb) */ +SH_SCOPE void SH_DESTROY(SH_TYPE *tb); + +/* void _reset(_hash *tb) */ +SH_SCOPE void SH_RESET(SH_TYPE *tb); + +/* void _grow(_hash *tb, uint64 newsize) */ +SH_SCOPE void SH_GROW(SH_TYPE *tb, uint64 newsize); + +/* *_insert(_hash *tb, key, bool *found) */ +SH_SCOPE SH_ELEMENT_TYPE *SH_INSERT(SH_TYPE *tb, SH_KEY_TYPE key, bool *found); + +/* + * *_insert_hash(_hash *tb, key, uint32 hash, + * bool *found) + */ +SH_SCOPE SH_ELEMENT_TYPE *SH_INSERT_HASH(SH_TYPE *tb, SH_KEY_TYPE key, uint32 hash, bool *found); + +/* *_lookup(_hash *tb, key) */ +SH_SCOPE SH_ELEMENT_TYPE *SH_LOOKUP(SH_TYPE *tb, SH_KEY_TYPE key); + +/* *_lookup_hash(_hash *tb, key, uint32 hash) */ +SH_SCOPE SH_ELEMENT_TYPE *SH_LOOKUP_HASH(SH_TYPE *tb, SH_KEY_TYPE key, uint32 hash); + +/* void _start_iterate(_hash *tb, _iterator *iter) */ +SH_SCOPE void SH_START_ITERATE(SH_TYPE *tb, SH_ITERATOR *iter); + +/* + * void _start_iterate_at(_hash *tb, _iterator *iter, + * uint32 at) + */ +SH_SCOPE void SH_START_ITERATE_AT(SH_TYPE *tb, SH_ITERATOR *iter, uint32 at); + +/* *_iterate(_hash *tb, _iterator *iter) */ +SH_SCOPE SH_ELEMENT_TYPE *SH_ITERATE(SH_TYPE *tb, SH_ITERATOR *iter); + +/* void _stat(_hash *tb */ +SH_SCOPE void SH_STAT(SH_TYPE *tb); + +#endif /* SH_DECLARE */ + +/* generate implementation of the hash table */ +#ifdef SH_DEFINE + +#ifndef SH_RAW_ALLOCATOR +#include "utils/memutils.h" +#endif + +/* max data array size,we allow up to PG_UINT32_MAX buckets, including 0 */ +#define SH_MAX_SIZE (((uint64) PG_UINT32_MAX) + 1) + +/* normal fillfactor, unless already close to maximum */ +#ifndef SH_FILLFACTOR +#define SH_FILLFACTOR (0.9) +#endif +/* increase fillfactor if we otherwise would error out */ +#define SH_MAX_FILLFACTOR (0.98) +/* grow if actual and optimal location bigger than */ +#ifndef SH_GROW_MAX_DIB +#define SH_GROW_MAX_DIB 25 +#endif +/* grow if more than elements to move when inserting */ +#ifndef SH_GROW_MAX_MOVE +#define SH_GROW_MAX_MOVE 150 +#endif +#ifndef SH_GROW_MIN_FILLFACTOR +/* but do not grow due to SH_GROW_MAX_* if below */ +#define SH_GROW_MIN_FILLFACTOR 0.1 +#endif + +#ifdef SH_STORE_HASH +#define SH_COMPARE_KEYS(tb, ahash, akey, b) \ + (ahash == SH_GET_HASH(tb, b) && SH_EQUAL(tb, b->SH_KEY, akey)) +#else +#define SH_COMPARE_KEYS(tb, ahash, akey, b) (SH_EQUAL(tb, b->SH_KEY, akey)) +#endif + +/* + * Wrap the following definitions in include guards, to avoid multiple + * definition errors if this header is included more than once. The rest of + * the file deliberately has no include guards, because it can be included + * with different parameters to define functions and types with non-colliding + * names. + */ +#ifndef SIMPLEHASH_H +#define SIMPLEHASH_H + +#ifdef FRONTEND +#define sh_error(...) pg_fatal(__VA_ARGS__) +#define sh_log(...) pg_log_info(__VA_ARGS__) +#else +#define sh_error(...) elog(ERROR, __VA_ARGS__) +#define sh_log(...) elog(LOG, __VA_ARGS__) +#endif + +#endif + +/* + * Compute sizing parameters for hashtable. Called when creating and growing + * the hashtable. + */ +static inline void +SH_COMPUTE_PARAMETERS(SH_TYPE *tb, uint64 newsize) +{ + uint64 size; + + /* supporting zero sized hashes would complicate matters */ + size = Max(newsize, 2); + + /* round up size to the next power of 2, that's how bucketing works */ + size = pg_nextpower2_64(size); + Assert(size <= SH_MAX_SIZE); + + /* + * Verify that allocation of ->data is possible on this platform, without + * overflowing Size. + */ + if (unlikely((((uint64) sizeof(SH_ELEMENT_TYPE)) * size) >= SIZE_MAX / 2)) + sh_error("hash table too large"); + + /* now set size */ + tb->size = size; + tb->sizemask = (uint32) (size - 1); + + /* + * Compute the next threshold at which we need to grow the hash table + * again. + */ + if (tb->size == SH_MAX_SIZE) + tb->grow_threshold = ((double) tb->size) * SH_MAX_FILLFACTOR; + else + tb->grow_threshold = ((double) tb->size) * SH_FILLFACTOR; +} + +/* return the optimal bucket for the hash */ +static pg_attribute_always_inline uint32 +SH_INITIAL_BUCKET(SH_TYPE *tb, uint32 hash) +{ + return hash & tb->sizemask; +} + +/* return next bucket after the current, handling wraparound */ +static inline uint32 +SH_NEXT(SH_TYPE *tb, uint32 curelem, uint32 startelem) +{ + curelem = (curelem + 1) & tb->sizemask; + + Assert(curelem != startelem); + + return curelem; +} + +/* return bucket before the current, handling wraparound */ +static inline uint32 +SH_PREV(SH_TYPE *tb, uint32 curelem, uint32 startelem) +{ + curelem = (curelem - 1) & tb->sizemask; + + Assert(curelem != startelem); + + return curelem; +} + +/* return distance between bucket and its optimal position */ +static inline uint32 +SH_DISTANCE_FROM_OPTIMAL(SH_TYPE *tb, uint32 optimal, uint32 bucket) +{ + if (optimal <= bucket) + return bucket - optimal; + else + return (tb->size + bucket) - optimal; +} + +static inline uint32 +SH_ENTRY_HASH(SH_TYPE *tb, SH_ELEMENT_TYPE *entry) +{ +#ifdef SH_STORE_HASH + return SH_GET_HASH(tb, entry); +#else + return SH_HASH_KEY(tb, entry->SH_KEY); +#endif +} + +/* default memory allocator function */ +static inline void *SH_ALLOCATE(SH_TYPE *type, Size size); +static inline void SH_FREE(SH_TYPE *type, void *pointer); + +#ifndef SH_USE_NONDEFAULT_ALLOCATOR + +/* default memory allocator function */ +static inline void * +SH_ALLOCATE(SH_TYPE *type, Size size) +{ +#ifdef SH_RAW_ALLOCATOR + return SH_RAW_ALLOCATOR(size); +#else + return MemoryContextAllocExtended(type->ctx, size, MCXT_ALLOC_HUGE | MCXT_ALLOC_ZERO); +#endif +} + +/* default memory free function */ +static inline void +SH_FREE(SH_TYPE *type, void *pointer) +{ + pfree(pointer); +} + +#endif + +/* + * Create a hash table with enough space for `nelements` distinct members. + * Memory for the hash table is allocated from the passed-in context. If + * desired, the array of elements can be allocated using a passed-in allocator; + * this could be useful in order to place the array of elements in a shared + * memory, or in a context that will outlive the rest of the hash table. + * Memory other than for the array of elements will still be allocated from + * the passed-in context. + */ +#ifdef SH_RAW_ALLOCATOR +SH_SCOPE SH_TYPE * +SH_CREATE(uint32 nelements, void *private_data) +#else +SH_SCOPE SH_TYPE * +SH_CREATE(MemoryContext ctx, uint32 nelements, void *private_data) +#endif +{ + SH_TYPE *tb; + uint64 size; + +#ifdef SH_RAW_ALLOCATOR + tb = (SH_TYPE *) SH_RAW_ALLOCATOR(sizeof(SH_TYPE)); +#else + tb = (SH_TYPE *) MemoryContextAllocZero(ctx, sizeof(SH_TYPE)); + tb->ctx = ctx; +#endif + tb->private_data = private_data; + + /* increase nelements by fillfactor, want to store nelements elements */ + size = Min((double) SH_MAX_SIZE, ((double) nelements) / SH_FILLFACTOR); + + SH_COMPUTE_PARAMETERS(tb, size); + + tb->data = (SH_ELEMENT_TYPE *) SH_ALLOCATE(tb, sizeof(SH_ELEMENT_TYPE) * tb->size); + + return tb; +} + +/* destroy a previously created hash table */ +SH_SCOPE void +SH_DESTROY(SH_TYPE *tb) +{ + SH_FREE(tb, tb->data); + pfree(tb); +} + +/* reset the contents of a previously created hash table */ +SH_SCOPE void +SH_RESET(SH_TYPE *tb) +{ + memset(tb->data, 0, sizeof(SH_ELEMENT_TYPE) * tb->size); + tb->members = 0; +} + +/* + * Grow a hash table to at least `newsize` buckets. + * + * Usually this will automatically be called by insertions/deletions, when + * necessary. But resizing to the exact input size can be advantageous + * performance-wise, when known at some point. + */ +SH_SCOPE void +SH_GROW(SH_TYPE *tb, uint64 newsize) +{ + uint64 oldsize = tb->size; + SH_ELEMENT_TYPE *olddata = tb->data; + SH_ELEMENT_TYPE *newdata; + uint32 i; + uint32 startelem = 0; + uint32 copyelem; + + Assert(oldsize == pg_nextpower2_64(oldsize)); + Assert(oldsize != SH_MAX_SIZE); + Assert(oldsize < newsize); + + /* compute parameters for new table */ + SH_COMPUTE_PARAMETERS(tb, newsize); + + tb->data = (SH_ELEMENT_TYPE *) SH_ALLOCATE(tb, sizeof(SH_ELEMENT_TYPE) * tb->size); + + newdata = tb->data; + + /* + * Copy entries from the old data to newdata. We theoretically could use + * SH_INSERT here, to avoid code duplication, but that's more general than + * we need. We neither want tb->members increased, nor do we need to do + * deal with deleted elements, nor do we need to compare keys. So a + * special-cased implementation is lot faster. As resizing can be time + * consuming and frequent, that's worthwhile to optimize. + * + * To be able to simply move entries over, we have to start not at the + * first bucket (i.e olddata[0]), but find the first bucket that's either + * empty, or is occupied by an entry at its optimal position. Such a + * bucket has to exist in any table with a load factor under 1, as not all + * buckets are occupied, i.e. there always has to be an empty bucket. By + * starting at such a bucket we can move the entries to the larger table, + * without having to deal with conflicts. + */ + + /* search for the first element in the hash that's not wrapped around */ + for (i = 0; i < oldsize; i++) + { + SH_ELEMENT_TYPE *oldentry = &olddata[i]; + uint32 hash; + uint32 optimal; + + if (SH_ENTRY_EMPTY(oldentry)) + { + startelem = i; + break; + } + + hash = SH_ENTRY_HASH(tb, oldentry); + optimal = SH_INITIAL_BUCKET(tb, hash); + + if (optimal == i) + { + startelem = i; + break; + } + } + + /* and copy all elements in the old table */ + copyelem = startelem; + for (i = 0; i < oldsize; i++) + { + SH_ELEMENT_TYPE *oldentry = &olddata[copyelem]; + + if (!SH_ENTRY_EMPTY(oldentry)) + { + uint32 hash; + uint32 startelem; + uint32 curelem; + SH_ELEMENT_TYPE *newentry; + + hash = SH_ENTRY_HASH(tb, oldentry); + startelem = SH_INITIAL_BUCKET(tb, hash); + curelem = startelem; + + /* find empty element to put data into */ + while (true) + { + newentry = &newdata[curelem]; + + if (SH_ENTRY_EMPTY(newentry)) + { + break; + } + + curelem = SH_NEXT(tb, curelem, startelem); + } + + /* copy entry to new slot */ + memcpy(newentry, oldentry, sizeof(SH_ELEMENT_TYPE)); + } + + /* can't use SH_NEXT here, would use new size */ + copyelem++; + if (copyelem >= oldsize) + { + copyelem = 0; + } + } + + SH_FREE(tb, olddata); +} + +/* + * This is a separate static inline function, so it can be reliably be inlined + * into its wrapper functions even if SH_SCOPE is extern. + */ +static pg_attribute_always_inline SH_ELEMENT_TYPE * +SH_INSERT_HASH_INTERNAL(SH_TYPE *restrict tb, SH_KEY_TYPE key, uint32 hash, bool *found) +{ + /* + * We do the grow check even if the key is actually present, to avoid + * doing the check inside the loop. This also lets us avoid having to + * re-find our position in the hashtable after resizing. + * + * Note that this also reached when resizing the table due to + * SH_GROW_MAX_DIB / SH_GROW_MAX_MOVE. + */ + if (unlikely(tb->members >= tb->grow_threshold)) + { + if (unlikely(tb->size == SH_MAX_SIZE)) + sh_error("hash table size exceeded"); + + /* + * When optimizing, it can be very useful to print these out. + */ + /* SH_STAT(tb); */ + SH_GROW(tb, tb->size * 2); + /* SH_STAT(tb); */ + } + + SH_ELEMENT_TYPE *restrict data = tb->data; + + /* perform insert, start bucket search at optimal location */ + const uint32 startelem = SH_INITIAL_BUCKET(tb, hash); + uint32 curelem = startelem; + uint32 insertdist = 0; + while (true) + { + SH_ELEMENT_TYPE *entry = &data[curelem]; + + /* any empty bucket can directly be used */ + if (SH_ENTRY_EMPTY(entry)) + { + tb->members++; + entry->SH_KEY = key; +#ifdef SH_STORE_HASH + SH_GET_HASH(tb, entry) = hash; +#endif + *found = false; + return entry; + } + + /* + * If the bucket is not empty, we either found a match (in which case + * we're done), or we have to decide whether to skip over or move the + * colliding entry. When the colliding element's distance to its + * optimal position is smaller than the to-be-inserted entry's, we + * shift the colliding entry (and its followers) forward by one. + */ + + if (SH_COMPARE_KEYS(tb, hash, key, entry)) + { + Assert(!SH_ENTRY_EMPTY(entry)); + *found = true; + return entry; + } + + const uint32 curhash = SH_ENTRY_HASH(tb, entry); + const uint32 curoptimal = SH_INITIAL_BUCKET(tb, curhash); + const uint32 curdist = SH_DISTANCE_FROM_OPTIMAL(tb, curoptimal, curelem); + + if (insertdist > curdist) + { + /* We're going to insert at this position. */ + break; + } + + curelem = SH_NEXT(tb, curelem, startelem); + insertdist++; + + /* + * To avoid negative consequences from overly imbalanced hashtables, + * grow the hashtable if collisions lead to large runs. The most + * likely cause of such imbalance is filling a (currently) small + * table, from a currently big one, in hash-table order. Don't grow + * if the hashtable would be too empty, to prevent quick space + * explosion for some weird edge cases. + */ + if (unlikely(insertdist > SH_GROW_MAX_DIB) && + ((double) tb->members / tb->size) >= SH_GROW_MIN_FILLFACTOR) + { + SH_GROW(tb, tb->size * 2); + return SH_INSERT_HASH(tb, key, hash, found); + } + } + + /* Actually insert. */ + SH_ELEMENT_TYPE *entry = &data[curelem]; + SH_ELEMENT_TYPE *lastentry = entry; + uint32 emptyelem = curelem; + int32 emptydist = 0; + + /* find next empty bucket */ + while (true) + { + SH_ELEMENT_TYPE *emptyentry; + + emptyelem = SH_NEXT(tb, emptyelem, startelem); + emptyentry = &data[emptyelem]; + + if (SH_ENTRY_EMPTY(emptyentry)) + { + lastentry = emptyentry; + break; + } + + /* + * To avoid negative consequences from overly imbalanced + * hashtables, grow the hashtable if collisions would require + * us to move a lot of entries. The most likely cause of such + * imbalance is filling a (currently) small table, from a + * currently big one, in hash-table order. Don't grow if the + * hashtable would be too empty, to prevent quick space + * explosion for some weird edge cases. + */ + if (unlikely(++emptydist > SH_GROW_MAX_MOVE) && + ((double) tb->members / tb->size) >= SH_GROW_MIN_FILLFACTOR) + { + SH_GROW(tb, tb->size * 2); + return SH_INSERT_HASH(tb, key, hash, found); + } + } + + /* shift forward, starting at last occupied element */ + + /* + * TODO: This could be optimized to be one memcpy in many cases, + * excepting wrapping around at the end of ->data. Hasn't shown up + * in profiles so far though. + */ + uint32 moveelem = emptyelem; + while (moveelem != curelem) + { + SH_ELEMENT_TYPE *moveentry; + + moveelem = SH_PREV(tb, moveelem, startelem); + moveentry = &data[moveelem]; + + memcpy(lastentry, moveentry, sizeof(SH_ELEMENT_TYPE)); + lastentry = moveentry; + } + + /* and fill the now empty spot */ + tb->members++; + + entry->SH_KEY = key; +#ifdef SH_STORE_HASH + SH_GET_HASH(tb, entry) = hash; +#endif + *found = false; + return entry; +} + +/* + * Insert the key key into the hash-table, set *found to true if the key + * already exists, false otherwise. Returns the hash-table entry in either + * case. + */ +static pg_attribute_always_inline SH_ELEMENT_TYPE * +SH_INSERT(SH_TYPE *tb, SH_KEY_TYPE key, bool *found) +{ + uint32 hash = SH_HASH_KEY(tb, key); + + return SH_INSERT_HASH_INTERNAL(tb, key, hash, found); +} + +/* + * Insert the key key into the hash-table using an already-calculated + * hash. Set *found to true if the key already exists, false + * otherwise. Returns the hash-table entry in either case. + */ +SH_SCOPE SH_ELEMENT_TYPE * +SH_INSERT_HASH(SH_TYPE *tb, SH_KEY_TYPE key, uint32 hash, bool *found) +{ + return SH_INSERT_HASH_INTERNAL(tb, key, hash, found); +} + +/* + * This is a separate static inline function, so it can be reliably be inlined + * into its wrapper functions even if SH_SCOPE is extern. + */ +static inline SH_ELEMENT_TYPE * +SH_LOOKUP_HASH_INTERNAL(SH_TYPE *tb, SH_KEY_TYPE key, uint32 hash) +{ + const uint32 startelem = SH_INITIAL_BUCKET(tb, hash); + uint32 curelem = startelem; + + while (true) + { + SH_ELEMENT_TYPE *entry = &tb->data[curelem]; + + if (SH_ENTRY_EMPTY(entry)) + { + return NULL; + } + + Assert(!SH_ENTRY_EMPTY(entry)); + + if (SH_COMPARE_KEYS(tb, hash, key, entry)) + return entry; + + /* + * TODO: we could stop search based on distance. If the current + * buckets's distance-from-optimal is smaller than what we've skipped + * already, the entry doesn't exist. Probably only do so if + * SH_STORE_HASH is defined, to avoid re-computing hashes? + */ + + curelem = SH_NEXT(tb, curelem, startelem); + } +} + +/* + * Lookup up entry in hash table. Returns NULL if key not present. + */ +SH_SCOPE SH_ELEMENT_TYPE * +SH_LOOKUP(SH_TYPE *tb, SH_KEY_TYPE key) +{ + uint32 hash = SH_HASH_KEY(tb, key); + + return SH_LOOKUP_HASH_INTERNAL(tb, key, hash); +} + +/* + * Lookup up entry in hash table using an already-calculated hash. + * + * Returns NULL if key not present. + */ +SH_SCOPE SH_ELEMENT_TYPE * +SH_LOOKUP_HASH(SH_TYPE *tb, SH_KEY_TYPE key, uint32 hash) +{ + return SH_LOOKUP_HASH_INTERNAL(tb, key, hash); +} + +/* + * Initialize iterator. + */ +SH_SCOPE void +SH_START_ITERATE(SH_TYPE *tb, SH_ITERATOR *iter) +{ + uint64 startelem = PG_UINT64_MAX; + + /* + * Search for the first empty element. As deletions during iterations are + * supported, we want to start/end at an element that cannot be affected + * by elements being shifted. + */ + for (uint32 i = 0; i < tb->size; i++) + { + SH_ELEMENT_TYPE *entry = &tb->data[i]; + + if (SH_ENTRY_EMPTY(entry)) + { + startelem = i; + break; + } + } + + /* we should have found an empty element */ + Assert(startelem < SH_MAX_SIZE); + + /* + * Iterate backwards, that allows the current element to be deleted, even + * if there are backward shifts + */ + iter->cur = startelem; + iter->end = iter->cur; + iter->done = false; +} + +/* + * Initialize iterator to a specific bucket. That's really only useful for + * cases where callers are partially iterating over the hashspace, and that + * iteration deletes and inserts elements based on visited entries. Doing that + * repeatedly could lead to an unbalanced keyspace when always starting at the + * same position. + */ +SH_SCOPE void +SH_START_ITERATE_AT(SH_TYPE *tb, SH_ITERATOR *iter, uint32 at) +{ + /* + * Iterate backwards, that allows the current element to be deleted, even + * if there are backward shifts. + */ + iter->cur = at & tb->sizemask; /* ensure at is within a valid range */ + iter->end = iter->cur; + iter->done = false; +} + +/* + * Iterate over all entries in the hash-table. Return the next occupied entry, + * or NULL if done. + * + * During iteration the current entry in the hash table may be deleted, + * without leading to elements being skipped or returned twice. Additionally + * the rest of the table may be modified (i.e. there can be insertions or + * deletions), but if so, there's neither a guarantee that all nodes are + * visited at least once, nor a guarantee that a node is visited at most once. + */ +SH_SCOPE SH_ELEMENT_TYPE * +SH_ITERATE(SH_TYPE *tb, SH_ITERATOR *iter) +{ + while (!iter->done) + { + SH_ELEMENT_TYPE *elem; + + elem = &tb->data[iter->cur]; + + /* next element in backward direction */ + iter->cur = (iter->cur - 1) & tb->sizemask; + + if ((iter->cur & tb->sizemask) == (iter->end & tb->sizemask)) + iter->done = true; + if (!SH_ENTRY_EMPTY(elem)) + { + return elem; + } + } + + return NULL; +} + +/* + * Report some statistics about the state of the hashtable. For + * debugging/profiling purposes only. + */ +SH_SCOPE void +SH_STAT(SH_TYPE *tb) +{ + uint32 max_chain_length = 0; + uint32 total_chain_length = 0; + double avg_chain_length; + double fillfactor; + uint32 i; + + uint32 *collisions = (uint32 *) palloc0(tb->size * sizeof(uint32)); + uint32 total_collisions = 0; + uint32 max_collisions = 0; + double avg_collisions; + + for (i = 0; i < tb->size; i++) + { + uint32 hash; + uint32 optimal; + uint32 dist; + SH_ELEMENT_TYPE *elem; + + elem = &tb->data[i]; + + if (SH_ENTRY_EMPTY(elem)) + continue; + + hash = SH_ENTRY_HASH(tb, elem); + optimal = SH_INITIAL_BUCKET(tb, hash); + dist = SH_DISTANCE_FROM_OPTIMAL(tb, optimal, i); + + if (dist > max_chain_length) + max_chain_length = dist; + total_chain_length += dist; + + collisions[optimal]++; + } + + for (i = 0; i < tb->size; i++) + { + uint32 curcoll = collisions[i]; + + if (curcoll == 0) + continue; + + /* single contained element is not a collision */ + curcoll--; + total_collisions += curcoll; + if (curcoll > max_collisions) + max_collisions = curcoll; + } + + /* large enough to be worth freeing, even if just used for debugging */ + pfree(collisions); + + if (tb->members > 0) + { + fillfactor = tb->members / ((double) tb->size); + avg_chain_length = ((double) total_chain_length) / tb->members; + avg_collisions = ((double) total_collisions) / tb->members; + } + else + { + fillfactor = 0; + avg_chain_length = 0; + avg_collisions = 0; + } + + sh_log("size: " UINT64_FORMAT + ", members: %u, filled: %f, total chain: %u, max chain: %u, avg chain: %f, " + "total_collisions: %u, max_collisions: %u, avg_collisions: %f", + tb->size, + tb->members, + fillfactor, + total_chain_length, + max_chain_length, + avg_chain_length, + total_collisions, + max_collisions, + avg_collisions); +} + +#endif /* SH_DEFINE */ + +/* undefine external parameters, so next hash table can be defined */ +#undef SH_PREFIX +#undef SH_KEY_TYPE +#undef SH_KEY +#undef SH_ELEMENT_TYPE +#undef SH_HASH_KEY +#undef SH_SCOPE +#undef SH_DECLARE +#undef SH_DEFINE +#undef SH_GET_HASH +#undef SH_STORE_HASH +#undef SH_USE_NONDEFAULT_ALLOCATOR +#undef SH_EQUAL + +/* undefine locally declared macros */ +#undef SH_MAKE_PREFIX +#undef SH_MAKE_NAME +#undef SH_MAKE_NAME_ +#undef SH_FILLFACTOR +#undef SH_MAX_FILLFACTOR +#undef SH_GROW_MAX_DIB +#undef SH_GROW_MAX_MOVE +#undef SH_GROW_MIN_FILLFACTOR +#undef SH_MAX_SIZE + +/* types */ +#undef SH_TYPE +#undef SH_ITERATOR + +/* external function names */ +#undef SH_CREATE +#undef SH_DESTROY +#undef SH_RESET +#undef SH_INSERT +#undef SH_INSERT_HASH +#undef SH_LOOKUP +#undef SH_LOOKUP_HASH +#undef SH_GROW +#undef SH_START_ITERATE +#undef SH_START_ITERATE_AT +#undef SH_ITERATE +#undef SH_ALLOCATE +#undef SH_FREE +#undef SH_STAT + +/* internal function names */ +#undef SH_COMPUTE_PARAMETERS +#undef SH_COMPARE_KEYS +#undef SH_INITIAL_BUCKET +#undef SH_NEXT +#undef SH_PREV +#undef SH_DISTANCE_FROM_OPTIMAL +#undef SH_ENTRY_HASH +#undef SH_INSERT_HASH_INTERNAL +#undef SH_LOOKUP_HASH_INTERNAL diff --git a/tsl/src/nodes/decompress_chunk/exec.c b/tsl/src/nodes/decompress_chunk/exec.c index 2856a279aa1..413c014f4a5 100644 --- a/tsl/src/nodes/decompress_chunk/exec.c +++ b/tsl/src/nodes/decompress_chunk/exec.c @@ -522,7 +522,12 @@ decompress_chunk_explain(CustomScanState *node, List *ancestors, ExplainState *e ExplainPropertyBool("Batch Sorted Merge", dcontext->batch_sorted_merge, es); } - if (es->analyze && (es->verbose || es->format != EXPLAIN_FORMAT_TEXT)) + if (dcontext->reverse) + { + ExplainPropertyBool("Reverse", dcontext->reverse, es); + } + + if (es->analyze) { ExplainPropertyBool("Bulk Decompression", chunk_state->decompress_context.enable_bulk_decompression, diff --git a/tsl/src/nodes/vector_agg/exec.c b/tsl/src/nodes/vector_agg/exec.c index 228dafe96af..59e81403831 100644 --- a/tsl/src/nodes/vector_agg/exec.c +++ b/tsl/src/nodes/vector_agg/exec.c @@ -61,6 +61,25 @@ get_input_offset_decompress_chunk(const DecompressChunkState *decompress_state, return index; } +static int +grouping_column_comparator(const void *a_ptr, const void *b_ptr) +{ + const GroupingColumn *a = (GroupingColumn *) a_ptr; + const GroupingColumn *b = (GroupingColumn *) b_ptr; + + if (a->value_bytes == b->value_bytes) + { + return 0; + } + + if (a->value_bytes > b->value_bytes) + { + return -1; + } + + return 1; +} + static void get_column_storage_properties_decompress_chunk(const DecompressChunkState *state, int input_offset, GroupingColumn *result) @@ -238,6 +257,16 @@ vector_agg_begin(CustomScanState *node, EState *estate, int eflags) } } + /* + * Sort grouping columns by descending column size, variable size last. This + * helps improve branch predictability and key packing when we use hashed + * serialized multi-column keys. + */ + qsort(vector_agg_state->grouping_columns, + vector_agg_state->num_grouping_columns, + sizeof(GroupingColumn), + grouping_column_comparator); + /* * Create the grouping policy chosen at plan time. */ diff --git a/tsl/src/nodes/vector_agg/grouping_policy.h b/tsl/src/nodes/vector_agg/grouping_policy.h index 9154c8dd500..16d634ad844 100644 --- a/tsl/src/nodes/vector_agg/grouping_policy.h +++ b/tsl/src/nodes/vector_agg/grouping_policy.h @@ -66,7 +66,8 @@ typedef enum VAGT_HashSingleFixed2, VAGT_HashSingleFixed4, VAGT_HashSingleFixed8, - VAGT_HashSingleText + VAGT_HashSingleText, + VAGT_HashSerialized, } VectorAggGroupingType; extern GroupingPolicy *create_grouping_policy_batch(int num_agg_defs, VectorAggDef *agg_defs, diff --git a/tsl/src/nodes/vector_agg/grouping_policy_hash.c b/tsl/src/nodes/vector_agg/grouping_policy_hash.c index 92ecfce11bc..e810fd95b21 100644 --- a/tsl/src/nodes/vector_agg/grouping_policy_hash.c +++ b/tsl/src/nodes/vector_agg/grouping_policy_hash.c @@ -38,6 +38,7 @@ extern HashingStrategy single_fixed_4_strategy; extern HashingStrategy single_fixed_8_strategy; #ifdef TS_USE_UMASH extern HashingStrategy single_text_strategy; +extern HashingStrategy serialized_strategy; #endif static const GroupingPolicy grouping_policy_hash_functions; @@ -74,6 +75,9 @@ create_grouping_policy_hash(int num_agg_defs, VectorAggDef *agg_defs, int num_gr switch (grouping_type) { #ifdef TS_USE_UMASH + case VAGT_HashSerialized: + policy->hashing = serialized_strategy; + break; case VAGT_HashSingleText: policy->hashing = single_text_strategy; break; @@ -110,8 +114,6 @@ gp_hash_reset(GroupingPolicy *obj) policy->hashing.reset(&policy->hashing); - policy->last_used_key_index = 0; - policy->stat_input_valid_rows = 0; policy->stat_input_total_rows = 0; policy->stat_bulk_filtered_rows = 0; @@ -225,7 +227,7 @@ add_one_range(GroupingPolicyHash *policy, TupleTableSlot *vector_slot, const int * Remember which aggregation states have already existed, and which we * have to initialize. State index zero is invalid. */ - const uint32 last_initialized_key_index = policy->last_used_key_index; + const uint32 last_initialized_key_index = policy->hashing.last_used_key_index; Assert(last_initialized_key_index <= policy->num_allocated_per_key_agg_states); /* @@ -246,13 +248,13 @@ add_one_range(GroupingPolicyHash *policy, TupleTableSlot *vector_slot, const int * If we added new keys, initialize the aggregate function states for * them. */ - if (policy->last_used_key_index > last_initialized_key_index) + if (policy->hashing.last_used_key_index > last_initialized_key_index) { /* * If the aggregate function states don't fit into the existing * storage, reallocate it. */ - if (policy->last_used_key_index >= policy->num_allocated_per_key_agg_states) + if (policy->hashing.last_used_key_index >= policy->num_allocated_per_key_agg_states) { policy->per_agg_per_key_states[agg_index] = repalloc(policy->per_agg_per_key_states[agg_index], @@ -263,7 +265,8 @@ add_one_range(GroupingPolicyHash *policy, TupleTableSlot *vector_slot, const int agg_def->func.state_bytes * (last_initialized_key_index + 1) + (char *) policy->per_agg_per_key_states[agg_index]; agg_def->func.agg_init(first_uninitialized_state, - policy->last_used_key_index - last_initialized_key_index); + policy->hashing.last_used_key_index - + last_initialized_key_index); } /* @@ -281,7 +284,7 @@ add_one_range(GroupingPolicyHash *policy, TupleTableSlot *vector_slot, const int * Record the newly allocated number of aggregate function states in case we * had to reallocate. */ - if (policy->last_used_key_index >= policy->num_allocated_per_key_agg_states) + if (policy->hashing.last_used_key_index >= policy->num_allocated_per_key_agg_states) { Assert(new_aggstate_rows > policy->num_allocated_per_key_agg_states); policy->num_allocated_per_key_agg_states = new_aggstate_rows; @@ -421,7 +424,7 @@ gp_hash_should_emit(GroupingPolicy *gp) { GroupingPolicyHash *policy = (GroupingPolicyHash *) gp; - if (policy->last_used_key_index > UINT32_MAX - GLOBAL_MAX_ROWS_PER_COMPRESSION) + if (policy->hashing.last_used_key_index > UINT32_MAX - GLOBAL_MAX_ROWS_PER_COMPRESSION) { /* * The max valid key index is UINT32_MAX, so we have to spill if the next @@ -450,7 +453,7 @@ gp_hash_do_emit(GroupingPolicy *gp, TupleTableSlot *aggregated_slot) policy->returning_results = true; policy->last_returned_key = 1; - const float keys = policy->last_used_key_index; + const float keys = policy->hashing.last_used_key_index; if (keys > 0) { DEBUG_LOG("spill after %ld input, %ld valid, %ld bulk filtered, %ld cons, %.0f keys, " @@ -471,7 +474,7 @@ gp_hash_do_emit(GroupingPolicy *gp, TupleTableSlot *aggregated_slot) } const uint32 current_key = policy->last_returned_key; - const uint32 keys_end = policy->last_used_key_index + 1; + const uint32 keys_end = policy->hashing.last_used_key_index + 1; if (current_key >= keys_end) { policy->returning_results = false; diff --git a/tsl/src/nodes/vector_agg/grouping_policy_hash.h b/tsl/src/nodes/vector_agg/grouping_policy_hash.h index 3516efb8896..30b132ce559 100644 --- a/tsl/src/nodes/vector_agg/grouping_policy_hash.h +++ b/tsl/src/nodes/vector_agg/grouping_policy_hash.h @@ -78,11 +78,6 @@ typedef struct GroupingPolicyHash */ HashingStrategy hashing; - /* - * The last used index of an unique grouping key. Key index 0 is invalid. - */ - uint32 last_used_key_index; - /* * Temporary storage of unique indexes of keys corresponding to a given row * of the compressed batch that is currently being aggregated. We keep it in @@ -133,6 +128,19 @@ typedef struct GroupingPolicyHash uint64 stat_input_valid_rows; uint64 stat_bulk_filtered_rows; uint64 stat_consecutive_keys; + + /* + * FIXME all the stuff below should be moved out. + */ + + /* + * For single text key that uses dictionary encoding, in some cases we first + * calculate the key indexes for the dictionary entries, and then translate + * it to the actual rows. + */ + uint32 *restrict key_index_for_dict; + uint64 num_key_index_for_dict; + bool use_key_index_for_dict; } GroupingPolicyHash; //#define DEBUG_PRINT(...) fprintf(stderr, __VA_ARGS__) diff --git a/tsl/src/nodes/vector_agg/hashing/CMakeLists.txt b/tsl/src/nodes/vector_agg/hashing/CMakeLists.txt index 401e5f22025..f0e6dfe038b 100644 --- a/tsl/src/nodes/vector_agg/hashing/CMakeLists.txt +++ b/tsl/src/nodes/vector_agg/hashing/CMakeLists.txt @@ -5,7 +5,8 @@ set(SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/hash_strategy_common.c) if(USE_UMASH) - list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/hash_strategy_single_text.c) + list(APPEND SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/hash_strategy_single_text.c + ${CMAKE_CURRENT_SOURCE_DIR}/hash_strategy_serialized.c) endif() target_sources(${TSL_LIBRARY_NAME} PRIVATE ${SOURCES}) diff --git a/tsl/src/nodes/vector_agg/hashing/batch_hashing_params.h b/tsl/src/nodes/vector_agg/hashing/batch_hashing_params.h index ed4c0ed12bb..4965c8cf2e6 100644 --- a/tsl/src/nodes/vector_agg/hashing/batch_hashing_params.h +++ b/tsl/src/nodes/vector_agg/hashing/batch_hashing_params.h @@ -18,7 +18,18 @@ typedef struct BatchHashingParams const uint64 *batch_filter; CompressedColumnValues single_grouping_column; + int num_grouping_columns; + const CompressedColumnValues *grouping_column_values; + + /* + * Whether we have any scalar or nullable grouping columns in the current + * batch. This is used to select the more efficient implementation when we + * have none. + */ + bool have_scalar_or_nullable_columns; + GroupingPolicyHash *restrict policy; + HashingStrategy *restrict hashing; uint32 *restrict result_key_indexes; } BatchHashingParams; @@ -29,12 +40,26 @@ build_batch_hashing_params(GroupingPolicyHash *policy, TupleTableSlot *vector_sl uint16 nrows; BatchHashingParams params = { .policy = policy, + .hashing = &policy->hashing, .batch_filter = vector_slot_get_qual_result(vector_slot, &nrows), + .num_grouping_columns = policy->num_grouping_columns, + .grouping_column_values = policy->current_batch_grouping_column_values, .result_key_indexes = policy->key_index_for_row, }; - Assert(policy->num_grouping_columns == 1); - params.single_grouping_column = policy->current_batch_grouping_column_values[0]; + Assert(policy->num_grouping_columns > 0); + if (policy->num_grouping_columns == 1) + { + params.single_grouping_column = policy->current_batch_grouping_column_values[0]; + } + + for (int i = 0; i < policy->num_grouping_columns; i++) + { + params.have_scalar_or_nullable_columns = + params.have_scalar_or_nullable_columns || + (policy->current_batch_grouping_column_values[i].decompression_type == DT_Scalar || + policy->current_batch_grouping_column_values[i].buffers[0] != NULL); + } return params; } diff --git a/tsl/src/nodes/vector_agg/hashing/bytes_view.h b/tsl/src/nodes/vector_agg/hashing/bytes_view.h new file mode 100644 index 00000000000..f7149c52568 --- /dev/null +++ b/tsl/src/nodes/vector_agg/hashing/bytes_view.h @@ -0,0 +1,24 @@ +/* + * This file and its contents are licensed under the Timescale License. + * Please see the included NOTICE for copyright information and + * LICENSE-TIMESCALE for a copy of the license. + */ +#pragma once + +#include + +#include + +typedef struct BytesView +{ + const uint8 *data; + uint32 len; +} BytesView; + +static pg_attribute_always_inline uint32 +hash_bytes_view(BytesView view) +{ + uint32 val = -1; + COMP_CRC32C(val, view.data, view.len); + return val; +} diff --git a/tsl/src/nodes/vector_agg/hashing/hash_strategy_common.c b/tsl/src/nodes/vector_agg/hashing/hash_strategy_common.c index c743a3171e8..590da8fdcc7 100644 --- a/tsl/src/nodes/vector_agg/hashing/hash_strategy_common.c +++ b/tsl/src/nodes/vector_agg/hashing/hash_strategy_common.c @@ -18,7 +18,7 @@ void hash_strategy_output_key_alloc(GroupingPolicyHash *policy, uint16 nrows) { HashingStrategy *hashing = &policy->hashing; - const uint32 num_possible_keys = policy->last_used_key_index + 1 + nrows; + const uint32 num_possible_keys = hashing->last_used_key_index + 1 + nrows; if (num_possible_keys > hashing->num_allocated_output_keys) { diff --git a/tsl/src/nodes/vector_agg/hashing/hash_strategy_impl.c b/tsl/src/nodes/vector_agg/hashing/hash_strategy_impl.c index 0cc6426cbae..35ca81c3952 100644 --- a/tsl/src/nodes/vector_agg/hashing/hash_strategy_impl.c +++ b/tsl/src/nodes/vector_agg/hashing/hash_strategy_impl.c @@ -18,8 +18,6 @@ typedef struct FUNCTION_NAME(entry) /* Key index 0 is invalid. */ uint32 key_index; - uint8 status; - HASH_TABLE_KEY_TYPE hash_table_key; } FUNCTION_NAME(entry); @@ -32,7 +30,8 @@ typedef struct FUNCTION_NAME(entry) #define SH_SCOPE static inline #define SH_DECLARE #define SH_DEFINE -#include +#define SH_ENTRY_EMPTY(entry) ((entry)->key_index == 0) +#include "import/ts_simplehash.h" struct FUNCTION_NAME(hash); @@ -57,7 +56,17 @@ FUNCTION_NAME(hash_strategy_reset)(HashingStrategy *hashing) { struct FUNCTION_NAME(hash) *table = (struct FUNCTION_NAME(hash) *) hashing->table; FUNCTION_NAME(reset)(table); + + hashing->last_used_key_index = 0; + hashing->null_key_index = 0; + + /* + * Have to reset this because it's in the key body context which is also + * reset here. + */ + hashing->tmp_key_storage = NULL; + hashing->num_tmp_key_storage_bytes = 0; } static void @@ -76,8 +85,7 @@ FUNCTION_NAME(hash_strategy_prepare_for_batch)(GroupingPolicyHash *policy, static pg_attribute_always_inline void FUNCTION_NAME(fill_offsets_impl)(BatchHashingParams params, int start_row, int end_row) { - GroupingPolicyHash *policy = params.policy; - HashingStrategy *hashing = &policy->hashing; + HashingStrategy *restrict hashing = params.hashing; uint32 *restrict indexes = params.result_key_indexes; @@ -90,7 +98,7 @@ FUNCTION_NAME(fill_offsets_impl)(BatchHashingParams params, int start_row, int e if (!arrow_row_is_valid(params.batch_filter, row)) { /* The row doesn't pass the filter. */ - DEBUG_PRINT("%p: row %d doesn't pass batch filter\n", policy, row); + DEBUG_PRINT("%p: row %d doesn't pass batch filter\n", hashing, row); continue; } @@ -109,10 +117,10 @@ FUNCTION_NAME(fill_offsets_impl)(BatchHashingParams params, int start_row, int e /* The key is null. */ if (hashing->null_key_index == 0) { - hashing->null_key_index = ++policy->last_used_key_index; + hashing->null_key_index = ++hashing->last_used_key_index; } indexes[row] = hashing->null_key_index; - DEBUG_PRINT("%p: row %d null key index %d\n", policy, row, hashing->null_key_index); + DEBUG_PRINT("%p: row %d null key index %d\n", hashing, row, hashing->null_key_index); continue; } @@ -128,9 +136,9 @@ FUNCTION_NAME(fill_offsets_impl)(BatchHashingParams params, int start_row, int e */ indexes[row] = previous_key_index; #ifndef NDEBUG - policy->stat_consecutive_keys++; + params.policy->stat_consecutive_keys++; #endif - DEBUG_PRINT("%p: row %d consecutive key index %d\n", policy, row, previous_key_index); + DEBUG_PRINT("%p: row %d consecutive key index %d\n", hashing, row, previous_key_index); continue; } @@ -144,14 +152,14 @@ FUNCTION_NAME(fill_offsets_impl)(BatchHashingParams params, int start_row, int e /* * New key, have to store it persistently. */ - const uint32 index = ++policy->last_used_key_index; + const uint32 index = ++hashing->last_used_key_index; entry->key_index = index; - FUNCTION_NAME(key_hashing_store_new)(policy, index, output_key); - DEBUG_PRINT("%p: row %d new key index %d\n", policy, row, index); + FUNCTION_NAME(key_hashing_store_new)(hashing, index, output_key); + DEBUG_PRINT("%p: row %d new key index %d\n", hashing, row, index); } else { - DEBUG_PRINT("%p: row %d old key index %d\n", policy, row, entry->key_index); + DEBUG_PRINT("%p: row %d old key index %d\n", hashing, row, entry->key_index); } indexes[row] = entry->key_index; @@ -160,6 +168,101 @@ FUNCTION_NAME(fill_offsets_impl)(BatchHashingParams params, int start_row, int e } } +/* + * For some configurations of hashing, we want to generate dedicated + * implementations that will be more efficient. For example, for 2-byte keys + * when all the batch and key rows are valid. + */ +#define APPLY_FOR_BATCH_FILTER(X, NAME, COND) \ + X(NAME##_nofilter, (COND) && (params.batch_filter == NULL)) \ + X(NAME##_filter, (COND) && (params.batch_filter != NULL)) + +#define APPLY_FOR_NULLABILITY(X, NAME, COND) \ + APPLY_FOR_BATCH_FILTER(X, \ + NAME##_notnull, \ + (COND) && params.single_grouping_column.buffers[0] == NULL) \ + APPLY_FOR_BATCH_FILTER(X, \ + NAME##_nullable, \ + (COND) && params.single_grouping_column.buffers[0] != NULL) + +#define APPLY_FOR_SCALARS(X, NAME, COND) \ + APPLY_FOR_BATCH_FILTER(X, \ + NAME##_noscalar_notnull, \ + (COND) && !params.have_scalar_or_nullable_columns) \ + APPLY_FOR_BATCH_FILTER(X, \ + NAME##_scalar_or_nullable, \ + (COND) && params.have_scalar_or_nullable_columns) + +#define APPLY_FOR_TYPE(X, NAME, COND) \ + APPLY_FOR_NULLABILITY(X, \ + NAME##_byval, \ + (COND) && params.single_grouping_column.decompression_type == \ + sizeof(OUTPUT_KEY_TYPE)) \ + APPLY_FOR_NULLABILITY(X, \ + NAME##_text, \ + (COND) && \ + params.single_grouping_column.decompression_type == DT_ArrowText) \ + APPLY_FOR_NULLABILITY(X, \ + NAME##_dict, \ + (COND) && params.single_grouping_column.decompression_type == \ + DT_ArrowTextDict) \ + APPLY_FOR_SCALARS(X, \ + NAME##_multi, \ + (COND) && params.single_grouping_column.decompression_type == DT_Invalid) + +#define APPLY_FOR_SPECIALIZATIONS(X) APPLY_FOR_TYPE(X, index, true) + +#define DEFINE(NAME, CONDITION) \ + static pg_noinline void FUNCTION_NAME( \ + NAME)(BatchHashingParams params, int start_row, int end_row) \ + { \ + if (!(CONDITION)) \ + { \ + pg_unreachable(); \ + } \ + \ + FUNCTION_NAME(fill_offsets_impl)(params, start_row, end_row); \ + } + +APPLY_FOR_SPECIALIZATIONS(DEFINE) + +#undef DEFINE + +static void +FUNCTION_NAME(dispatch_for_params)(BatchHashingParams params, int start_row, int end_row) +{ + if (params.num_grouping_columns == 0) + { + pg_unreachable(); + } + + if ((params.num_grouping_columns == 1) != + (params.single_grouping_column.decompression_type != DT_Invalid)) + { + pg_unreachable(); + } + +#define DISPATCH(NAME, CONDITION) \ + if (CONDITION) \ + { \ + FUNCTION_NAME(NAME)(params, start_row, end_row); \ + } \ + else + + APPLY_FOR_SPECIALIZATIONS(DISPATCH) + { + /* Use a generic implementation if no specializations matched. */ + FUNCTION_NAME(fill_offsets_impl)(params, start_row, end_row); + } +#undef DISPATCH +} + +#undef APPLY_FOR_SPECIALIZATIONS + +/* + * In some special cases we call a more efficient specialization of the grouping + * function. + */ static void FUNCTION_NAME(fill_offsets)(GroupingPolicyHash *policy, TupleTableSlot *vector_slot, int start_row, int end_row) @@ -168,7 +271,16 @@ FUNCTION_NAME(fill_offsets)(GroupingPolicyHash *policy, TupleTableSlot *vector_s BatchHashingParams params = build_batch_hashing_params(policy, vector_slot); - FUNCTION_NAME(fill_offsets_impl)(params, start_row, end_row); +#ifdef USE_DICT_HASHING + if (policy->use_key_index_for_dict) + { + Assert(params.single_grouping_column.decompression_type == DT_ArrowTextDict); + single_text_offsets_translate(params, start_row, end_row); + return; + } +#endif + + FUNCTION_NAME(dispatch_for_params)(params, start_row, end_row); } HashingStrategy FUNCTION_NAME(strategy) = { diff --git a/tsl/src/nodes/vector_agg/hashing/hash_strategy_impl_single_fixed_key.c b/tsl/src/nodes/vector_agg/hashing/hash_strategy_impl_single_fixed_key.c index 1e0ddabc187..57d57bea3bb 100644 --- a/tsl/src/nodes/vector_agg/hashing/hash_strategy_impl_single_fixed_key.c +++ b/tsl/src/nodes/vector_agg/hashing/hash_strategy_impl_single_fixed_key.c @@ -53,10 +53,10 @@ FUNCTION_NAME(key_hashing_get_key)(BatchHashingParams params, int row, } static pg_attribute_always_inline void -FUNCTION_NAME(key_hashing_store_new)(GroupingPolicyHash *restrict policy, uint32 new_key_index, +FUNCTION_NAME(key_hashing_store_new)(HashingStrategy *restrict hashing, uint32 new_key_index, OUTPUT_KEY_TYPE output_key) { - policy->hashing.output_keys[new_key_index] = OUTPUT_KEY_TO_DATUM(output_key); + hashing->output_keys[new_key_index] = OUTPUT_KEY_TO_DATUM(output_key); } static void diff --git a/tsl/src/nodes/vector_agg/hashing/hash_strategy_serialized.c b/tsl/src/nodes/vector_agg/hashing/hash_strategy_serialized.c new file mode 100644 index 00000000000..60c16f161f6 --- /dev/null +++ b/tsl/src/nodes/vector_agg/hashing/hash_strategy_serialized.c @@ -0,0 +1,454 @@ +/* + * This file and its contents are licensed under the Timescale License. + * Please see the included NOTICE for copyright information and + * LICENSE-TIMESCALE for a copy of the license. + */ + +/* + * Implementation of column hashing for multiple serialized columns. + */ + +#include + +#include + +#include "bytes_view.h" +#include "compression/arrow_c_data_interface.h" +#include "nodes/decompress_chunk/compressed_batch.h" +#include "nodes/vector_agg/exec.h" +#include "nodes/vector_agg/grouping_policy_hash.h" +#include "template_helper.h" + +#include "batch_hashing_params.h" + +#include "umash_fingerprint_key.h" + +#define EXPLAIN_NAME "serialized" +#define KEY_VARIANT serialized +#define OUTPUT_KEY_TYPE text * + +static void +serialized_key_hashing_init(HashingStrategy *hashing) +{ + hashing->umash_params = umash_key_hashing_init(); +} + +static void +serialized_key_hashing_prepare_for_batch(GroupingPolicyHash *policy, TupleTableSlot *vector_slot) +{ +} + +static pg_attribute_always_inline bool +byte_bitmap_row_is_valid(const uint8 *bitmap, size_t row_number) +{ + const size_t byte_index = row_number / 8; + const size_t bit_index = row_number % 8; + const uint8 mask = ((uint8) 1) << bit_index; + return bitmap[byte_index] & mask; +} + +static pg_attribute_always_inline void +byte_bitmap_set_row_validity(uint8 *bitmap, size_t row_number, bool value) +{ + const size_t byte_index = row_number / 8; + const size_t bit_index = row_number % 8; + const uint8 mask = ((uint8) 1) << bit_index; + const uint8 new_bit = ((uint8) value) << bit_index; + + bitmap[byte_index] = (bitmap[byte_index] & ~mask) | new_bit; + + Assert(byte_bitmap_row_is_valid(bitmap, row_number) == value); +} + +static pg_attribute_always_inline void +serialized_key_hashing_get_key(BatchHashingParams params, int row, void *restrict output_key_ptr, + void *restrict hash_table_key_ptr, bool *restrict valid) +{ + HashingStrategy *hashing = params.hashing; + + text **restrict output_key = (text **) output_key_ptr; + HASH_TABLE_KEY_TYPE *restrict hash_table_key = (HASH_TABLE_KEY_TYPE *) hash_table_key_ptr; + + const int num_columns = params.num_grouping_columns; + + const size_t bitmap_bytes = (num_columns + 7) / 8; + + /* + * Loop through the grouping columns to determine the length of the key. We + * need that to allocate memory to store it. + * + * The key has the null bitmap at the beginning. + */ + size_t num_bytes = bitmap_bytes; + for (int column_index = 0; column_index < num_columns; column_index++) + { + const CompressedColumnValues *column_values = ¶ms.grouping_column_values[column_index]; + + if (params.have_scalar_or_nullable_columns && + column_values->decompression_type == DT_Scalar) + { + if (!*column_values->output_isnull) + { + const GroupingColumn *def = ¶ms.policy->grouping_columns[column_index]; + if (def->by_value) + { + num_bytes += def->value_bytes; + } + else + { + /* + * The default value always has a long varlena header, but + * we are going to use short if it fits. + */ + const int32 value_bytes = VARSIZE_ANY_EXHDR(*column_values->output_value); + if (value_bytes + VARHDRSZ_SHORT <= VARATT_SHORT_MAX) + { + /* Short varlena, unaligned. */ + const int total_bytes = value_bytes + VARHDRSZ_SHORT; + num_bytes += total_bytes; + } + else + { + /* Long varlena, requires alignment. */ + const int total_bytes = value_bytes + VARHDRSZ; + num_bytes = TYPEALIGN(4, num_bytes) + total_bytes; + } + } + } + + continue; + } + + const bool is_valid = !params.have_scalar_or_nullable_columns || + arrow_row_is_valid(column_values->buffers[0], row); + if (!is_valid) + { + continue; + } + + if (column_values->decompression_type > 0) + { + num_bytes += column_values->decompression_type; + } + else + { + Assert(column_values->decompression_type == DT_ArrowText || + column_values->decompression_type == DT_ArrowTextDict); + Assert((column_values->decompression_type == DT_ArrowTextDict) == + (column_values->buffers[3] != NULL)); + + const uint32 data_row = (column_values->decompression_type == DT_ArrowTextDict) ? + ((int16 *) column_values->buffers[3])[row] : + row; + const uint32 start = ((uint32 *) column_values->buffers[1])[data_row]; + const int32 value_bytes = ((uint32 *) column_values->buffers[1])[data_row + 1] - start; + + if (value_bytes + VARHDRSZ_SHORT <= VARATT_SHORT_MAX) + { + /* Short varlena, unaligned. */ + const int total_bytes = value_bytes + VARHDRSZ_SHORT; + num_bytes += total_bytes; + } + else + { + /* Long varlena, requires alignment. */ + const int total_bytes = value_bytes + VARHDRSZ; + num_bytes = TYPEALIGN(4, num_bytes) + total_bytes; + } + } + } + + /* + * The key has short or long varlena header. This is a little tricky, we + * decide the header length after we have counted all the columns, but we + * put it at the beginning. Technically it could change the length because + * of the alignment. In practice, we only use alignment by 4 bytes for long + * varlena strings, and if we have at least one long varlena string column, + * the key is also going to use the long varlena header which is 4 bytes, so + * the alignment is not affected. If we use the short varlena header for the + * key, it necessarily means that there were no long varlena columns and + * therefore no alignment is needed. + */ + const bool key_uses_short_header = num_bytes + VARHDRSZ_SHORT <= VARATT_SHORT_MAX; + num_bytes += key_uses_short_header ? VARHDRSZ_SHORT : VARHDRSZ; + + /* + * Use temporary storage for the new key, reallocate if it's too small. + */ + if (num_bytes > hashing->num_tmp_key_storage_bytes) + { + if (hashing->tmp_key_storage != NULL) + { + pfree(hashing->tmp_key_storage); + } + hashing->tmp_key_storage = MemoryContextAlloc(hashing->key_body_mctx, num_bytes); + hashing->num_tmp_key_storage_bytes = num_bytes; + } + uint8 *restrict serialized_key_storage = hashing->tmp_key_storage; + + /* + * Build the actual grouping key. + */ + uint32 offset = 0; + offset += key_uses_short_header ? VARHDRSZ_SHORT : VARHDRSZ; + + /* + * We must always save the validity bitmap, even when there are no + * null words, so that the key is uniquely deserializable. Otherwise a key + * with some nulls might collide with a key with no nulls. + */ + uint8 *restrict serialized_key_validity_bitmap = &serialized_key_storage[offset]; + offset += bitmap_bytes; + + /* + * Loop through the grouping columns again and add their values to the + * grouping key. + */ + for (int column_index = 0; column_index < num_columns; column_index++) + { + const CompressedColumnValues *column_values = ¶ms.grouping_column_values[column_index]; + + if (params.have_scalar_or_nullable_columns && + column_values->decompression_type == DT_Scalar) + { + const bool is_valid = !*column_values->output_isnull; + byte_bitmap_set_row_validity(serialized_key_validity_bitmap, column_index, is_valid); + if (is_valid) + { + const GroupingColumn *def = ¶ms.policy->grouping_columns[column_index]; + if (def->by_value) + { + memcpy(&serialized_key_storage[offset], + column_values->output_value, + def->value_bytes); + + offset += def->value_bytes; + } + else + { + /* + * The default value always has a long varlena header, but + * we are going to use short if it fits. + */ + const int32 value_bytes = VARSIZE_ANY_EXHDR(*column_values->output_value); + if (value_bytes + VARHDRSZ_SHORT <= VARATT_SHORT_MAX) + { + /* Short varlena, no alignment. */ + const int32 total_bytes = value_bytes + VARHDRSZ_SHORT; + SET_VARSIZE_SHORT(&serialized_key_storage[offset], total_bytes); + offset += VARHDRSZ_SHORT; + } + else + { + /* Long varlena, requires alignment. Zero out the alignment bytes. */ + memset(&serialized_key_storage[offset], 0, 4); + offset = TYPEALIGN(4, offset); + const int32 total_bytes = value_bytes + VARHDRSZ; + SET_VARSIZE(&serialized_key_storage[offset], total_bytes); + offset += VARHDRSZ; + } + + memcpy(&serialized_key_storage[offset], + VARDATA_ANY(*column_values->output_value), + value_bytes); + + offset += value_bytes; + } + } + continue; + } + + const bool is_valid = !params.have_scalar_or_nullable_columns || + arrow_row_is_valid(column_values->buffers[0], row); + byte_bitmap_set_row_validity(serialized_key_validity_bitmap, column_index, is_valid); + + if (!is_valid) + { + continue; + } + + if (column_values->decompression_type > 0) + { + Assert(offset <= UINT_MAX - column_values->decompression_type); + + switch ((int) column_values->decompression_type) + { + case 2: + memcpy(&serialized_key_storage[offset], + row + (int16 *) column_values->buffers[1], + 2); + break; + case 4: + memcpy(&serialized_key_storage[offset], + row + (int32 *) column_values->buffers[1], + 4); + break; + case 8: + memcpy(&serialized_key_storage[offset], + row + (int64 *) column_values->buffers[1], + 8); + break; + default: + pg_unreachable(); + break; + } + + offset += column_values->decompression_type; + + continue; + } + + Assert(column_values->decompression_type == DT_ArrowText || + column_values->decompression_type == DT_ArrowTextDict); + + const uint32 data_row = column_values->decompression_type == DT_ArrowTextDict ? + ((int16 *) column_values->buffers[3])[row] : + row; + const uint32 start = ((uint32 *) column_values->buffers[1])[data_row]; + const int32 value_bytes = ((uint32 *) column_values->buffers[1])[data_row + 1] - start; + + if (value_bytes + VARHDRSZ_SHORT <= VARATT_SHORT_MAX) + { + /* Short varlena, unaligned. */ + const int32 total_bytes = value_bytes + VARHDRSZ_SHORT; + SET_VARSIZE_SHORT(&serialized_key_storage[offset], total_bytes); + offset += VARHDRSZ_SHORT; + } + else + { + /* Long varlena, requires alignment. Zero out the alignment bytes. */ + memset(&serialized_key_storage[offset], 0, 4); + offset = TYPEALIGN(4, offset); + const int32 total_bytes = value_bytes + VARHDRSZ; + SET_VARSIZE(&serialized_key_storage[offset], total_bytes); + offset += VARHDRSZ; + } + memcpy(&serialized_key_storage[offset], + &((uint8 *) column_values->buffers[2])[start], + value_bytes); + + offset += value_bytes; + } + + Assert(offset == num_bytes); + + if (key_uses_short_header) + { + SET_VARSIZE_SHORT(serialized_key_storage, offset); + } + else + { + SET_VARSIZE(serialized_key_storage, offset); + } + + DEBUG_PRINT("key is %d bytes: ", offset); + for (size_t i = 0; i < offset; i++) + { + DEBUG_PRINT("%.2x.", serialized_key_storage[i]); + } + DEBUG_PRINT("\n"); + + *output_key = (text *) serialized_key_storage; + + Assert(VARSIZE_ANY(*output_key) == num_bytes); + + /* + * The multi-column key is always considered non-null, and the null flags + * for the individual columns are stored in a bitmap that is part of the + * key. + */ + *valid = true; + + const struct umash_fp fp = umash_fprint(params.hashing->umash_params, + /* seed = */ ~0ULL, + serialized_key_storage, + num_bytes); + *hash_table_key = umash_fingerprint_get_key(fp); +} + +static pg_attribute_always_inline void +serialized_key_hashing_store_new(HashingStrategy *restrict hashing, uint32 new_key_index, + text *output_key) +{ + /* + * We will store this key so we have to consume the temporary storage that + * was used for it. The subsequent keys will need to allocate new memory. + */ + Assert(hashing->tmp_key_storage == (void *) output_key); + hashing->tmp_key_storage = NULL; + hashing->num_tmp_key_storage_bytes = 0; + + hashing->output_keys[new_key_index] = PointerGetDatum(output_key); +} + +static void +serialized_emit_key(GroupingPolicyHash *policy, uint32 current_key, TupleTableSlot *aggregated_slot) +{ + const HashingStrategy *hashing = &policy->hashing; + const int num_key_columns = policy->num_grouping_columns; + const Datum serialized_key_datum = hashing->output_keys[current_key]; + const uint8 *serialized_key = (const uint8 *) VARDATA_ANY(serialized_key_datum); + PG_USED_FOR_ASSERTS_ONLY const int key_data_bytes = VARSIZE_ANY_EXHDR(serialized_key_datum); + const uint8 *restrict ptr = serialized_key; + + /* + * We have the column validity bitmap at the beginning of the key. + */ + const int bitmap_bytes = (num_key_columns + 7) / 8; + Assert(bitmap_bytes <= key_data_bytes); + const uint8 *restrict key_validity_bitmap = serialized_key; + ptr += bitmap_bytes; + + DEBUG_PRINT("emit key #%d, with header %ld without %d bytes: ", + current_key, + VARSIZE_ANY(serialized_key_datum), + key_data_bytes); + for (size_t i = 0; i < VARSIZE_ANY(serialized_key_datum); i++) + { + DEBUG_PRINT("%.2x.", ((const uint8 *) serialized_key_datum)[i]); + } + DEBUG_PRINT("\n"); + + for (int column_index = 0; column_index < num_key_columns; column_index++) + { + const GroupingColumn *col = &policy->grouping_columns[column_index]; + const bool isnull = !byte_bitmap_row_is_valid(key_validity_bitmap, column_index); + + aggregated_slot->tts_isnull[col->output_offset] = isnull; + + if (isnull) + { + continue; + } + + Datum *output = &aggregated_slot->tts_values[col->output_offset]; + if (col->value_bytes > 0) + { + Assert(col->by_value); + Assert((size_t) col->value_bytes <= sizeof(Datum)); + *output = 0; + memcpy(output, ptr, col->value_bytes); + ptr += col->value_bytes; + } + else + { + Assert(col->value_bytes == -1); + Assert(!col->by_value); + if (VARATT_IS_SHORT(ptr)) + { + *output = PointerGetDatum(ptr); + ptr += VARSIZE_SHORT(ptr); + } + else + { + ptr = (const uint8 *) TYPEALIGN(4, ptr); + *output = PointerGetDatum(ptr); + ptr += VARSIZE(ptr); + } + } + } + + Assert(ptr == serialized_key + key_data_bytes); +} + +#include "hash_strategy_impl.c" diff --git a/tsl/src/nodes/vector_agg/hashing/hash_strategy_single_text.c b/tsl/src/nodes/vector_agg/hashing/hash_strategy_single_text.c index 5b54970b595..b997c27fe57 100644 --- a/tsl/src/nodes/vector_agg/hashing/hash_strategy_single_text.c +++ b/tsl/src/nodes/vector_agg/hashing/hash_strategy_single_text.c @@ -89,9 +89,9 @@ single_text_key_hashing_get_key(BatchHashingParams params, int row, void *restri } DEBUG_PRINT("%p consider key row %d key index %d is %d bytes: ", - params.policy, + hashing, row, - params.policy->last_used_key_index + 1, + hashing->last_used_key_index + 1, output_key->len); for (size_t i = 0; i < output_key->len; i++) { @@ -107,14 +107,14 @@ single_text_key_hashing_get_key(BatchHashingParams params, int row, void *restri } static pg_attribute_always_inline void -single_text_key_hashing_store_new(GroupingPolicyHash *restrict policy, uint32 new_key_index, +single_text_key_hashing_store_new(HashingStrategy *restrict hashing, uint32 new_key_index, BytesView output_key) { const int total_bytes = output_key.len + VARHDRSZ; - text *restrict stored = (text *) MemoryContextAlloc(policy->hashing.key_body_mctx, total_bytes); + text *restrict stored = (text *) MemoryContextAlloc(hashing->key_body_mctx, total_bytes); SET_VARSIZE(stored, total_bytes); memcpy(VARDATA(stored), output_key.data, output_key.len); - policy->hashing.output_keys[new_key_index] = PointerGetDatum(stored); + hashing->output_keys[new_key_index] = PointerGetDatum(stored); } /* @@ -127,9 +127,284 @@ single_text_emit_key(GroupingPolicyHash *policy, uint32 current_key, return hash_strategy_output_key_single_emit(policy, current_key, aggregated_slot); } +/* + * We use a special batch preparation function to sometimes hash the dictionary- + * encoded column using the dictionary. + */ + +#define USE_DICT_HASHING + +static pg_attribute_always_inline void single_text_dispatch_for_params(BatchHashingParams params, + int start_row, int end_row); + static void single_text_key_hashing_prepare_for_batch(GroupingPolicyHash *policy, TupleTableSlot *vector_slot) { + /* + * Determine whether we're going to use the dictionary for hashing. + */ + policy->use_key_index_for_dict = false; + + BatchHashingParams params = build_batch_hashing_params(policy, vector_slot); + if (params.single_grouping_column.decompression_type != DT_ArrowTextDict) + { + return; + } + + uint16 batch_rows; + const uint64 *row_filter = vector_slot_get_qual_result(vector_slot, &batch_rows); + + const int dict_rows = params.single_grouping_column.arrow->dictionary->length; + if ((size_t) dict_rows > arrow_num_valid(row_filter, batch_rows)) + { + return; + } + + /* + * Remember which aggregation states have already existed, and which we have + * to initialize. State index zero is invalid. + */ + const uint32 last_initialized_key_index = params.hashing->last_used_key_index; + Assert(last_initialized_key_index <= policy->num_allocated_per_key_agg_states); + + /* + * Initialize the array for storing the aggregate state offsets corresponding + * to a given batch row. We don't need the offsets for the previous batch + * that are currently stored there, so we don't need to use repalloc. + */ + if ((size_t) dict_rows > policy->num_key_index_for_dict) + { + if (policy->key_index_for_dict != NULL) + { + pfree(policy->key_index_for_dict); + } + policy->num_key_index_for_dict = dict_rows; + policy->key_index_for_dict = + palloc(sizeof(policy->key_index_for_dict[0]) * policy->num_key_index_for_dict); + } + + /* + * We shouldn't add the dictionary entries that are not used by any matching + * rows. Translate the batch filter bitmap to dictionary rows. + */ + if (row_filter != NULL) + { + uint64 *restrict dict_filter = policy->tmp_filter; + const size_t dict_words = (dict_rows + 63) / 64; + memset(dict_filter, 0, sizeof(*dict_filter) * dict_words); + + bool *restrict tmp = (bool *) policy->key_index_for_dict; + Assert(sizeof(*tmp) <= sizeof(*policy->key_index_for_dict)); + memset(tmp, 0, sizeof(*tmp) * dict_rows); + + int outer; + for (outer = 0; outer < batch_rows / 64; outer++) + { +#define INNER_LOOP(INNER_MAX) \ + const uint64 word = row_filter[outer]; \ + for (int inner = 0; inner < INNER_MAX; inner++) \ + { \ + const int16 index = \ + ((int16 *) params.single_grouping_column.buffers[3])[outer * 64 + inner]; \ + tmp[index] = tmp[index] || (word & (1ull << inner)); \ + } + + INNER_LOOP(64) + } + + if (batch_rows % 64) + { + INNER_LOOP(batch_rows % 64) + } +#undef INNER_LOOP + + for (outer = 0; outer < dict_rows / 64; outer++) + { +#define INNER_LOOP(INNER_MAX) \ + uint64 word = 0; \ + for (int inner = 0; inner < INNER_MAX; inner++) \ + { \ + word |= (tmp[outer * 64 + inner] ? 1ull : 0ull) << inner; \ + } \ + dict_filter[outer] = word; + + INNER_LOOP(64) + } + if (dict_rows % 64) + { + INNER_LOOP(dict_rows % 64) + } +#undef INNER_LOOP + + params.batch_filter = dict_filter; + } + else + { + params.batch_filter = NULL; + } + + /* + * The dictionary contains no null entries, so we will be adding the null + * key separately. Determine if we have any null key that also passes the + * batch filter. + */ + bool have_null_key = false; + if (row_filter != NULL) + { + if (params.single_grouping_column.arrow->null_count > 0) + { + Assert(params.single_grouping_column.buffers[0] != NULL); + const size_t batch_words = (batch_rows + 63) / 64; + for (size_t i = 0; i < batch_words; i++) + { + have_null_key = have_null_key || + (row_filter[i] & + (~((uint64 *) params.single_grouping_column.buffers[0])[i])) != 0; + } + } + } + else + { + if (params.single_grouping_column.arrow->null_count > 0) + { + Assert(params.single_grouping_column.buffers[0] != NULL); + have_null_key = true; + } + } + + /* + * Build key indexes for the dictionary entries as for normal non-nullable + * text values. + */ + Assert(params.single_grouping_column.decompression_type = DT_ArrowTextDict); + Assert((size_t) dict_rows <= policy->num_key_index_for_dict); + memset(policy->key_index_for_dict, 0, sizeof(*policy->key_index_for_dict) * dict_rows); + + params.single_grouping_column.decompression_type = DT_ArrowText; + params.single_grouping_column.buffers[0] = NULL; + params.have_scalar_or_nullable_columns = false; + params.result_key_indexes = policy->key_index_for_dict; + + single_text_dispatch_for_params(params, 0, dict_rows); + + /* + * The dictionary doesn't store nulls, so add the null key separately if we + * have one. + * + * FIXME doesn't respect nulls last/first in GroupAggregate. Add a test. + */ + if (have_null_key && policy->hashing.null_key_index == 0) + { + policy->hashing.null_key_index = ++params.hashing->last_used_key_index; + policy->hashing.output_keys[policy->hashing.null_key_index] = PointerGetDatum(NULL); + } + + policy->use_key_index_for_dict = true; + + /* + * Initialize the new keys if we added any. + */ + if (params.hashing->last_used_key_index > last_initialized_key_index) + { + const uint64 new_aggstate_rows = policy->num_allocated_per_key_agg_states * 2 + 1; + const int num_fns = policy->num_agg_defs; + for (int i = 0; i < num_fns; i++) + { + const VectorAggDef *agg_def = &policy->agg_defs[i]; + if (params.hashing->last_used_key_index >= policy->num_allocated_per_key_agg_states) + { + policy->per_agg_per_key_states[i] = + repalloc(policy->per_agg_per_key_states[i], + new_aggstate_rows * agg_def->func.state_bytes); + } + + /* + * Initialize the aggregate function states for the newly added keys. + */ + void *first_uninitialized_state = + agg_def->func.state_bytes * (last_initialized_key_index + 1) + + (char *) policy->per_agg_per_key_states[i]; + agg_def->func.agg_init(first_uninitialized_state, + params.hashing->last_used_key_index - last_initialized_key_index); + } + + /* + * Record the newly allocated number of rows in case we had to reallocate. + */ + if (params.hashing->last_used_key_index >= policy->num_allocated_per_key_agg_states) + { + Assert(new_aggstate_rows > policy->num_allocated_per_key_agg_states); + policy->num_allocated_per_key_agg_states = new_aggstate_rows; + } + } + + DEBUG_PRINT("computed the dict offsets\n"); } +static pg_attribute_always_inline void +single_text_offsets_translate_impl(BatchHashingParams params, int start_row, int end_row) +{ + GroupingPolicyHash *policy = params.policy; + Assert(policy->use_key_index_for_dict); + + uint32 *restrict indexes_for_rows = params.result_key_indexes; + uint32 *restrict indexes_for_dict = policy->key_index_for_dict; + + for (int row = start_row; row < end_row; row++) + { + const bool row_valid = arrow_row_is_valid(params.single_grouping_column.buffers[0], row); + const int16 dict_index = ((int16 *) params.single_grouping_column.buffers[3])[row]; + + if (row_valid) + { + indexes_for_rows[row] = indexes_for_dict[dict_index]; + } + else + { + indexes_for_rows[row] = policy->hashing.null_key_index; + } + + Assert(indexes_for_rows[row] != 0 || !arrow_row_is_valid(params.batch_filter, row)); + } +} + +#define APPLY_FOR_VALIDITY(X, NAME, COND) \ + X(NAME##_notnull, (COND) && (params.single_grouping_column.buffers[0] == NULL)) \ + X(NAME##_nullable, (COND) && (params.single_grouping_column.buffers[0] != NULL)) + +#define APPLY_FOR_SPECIALIZATIONS(X) APPLY_FOR_VALIDITY(X, single_text_offsets_translate, true) + +#define DEFINE(NAME, CONDITION) \ + static pg_noinline void NAME(BatchHashingParams params, int start_row, int end_row) \ + { \ + if (!(CONDITION)) \ + { \ + pg_unreachable(); \ + } \ + \ + single_text_offsets_translate_impl(params, start_row, end_row); \ + } + +APPLY_FOR_SPECIALIZATIONS(DEFINE) + +#undef DEFINE + +static void +single_text_offsets_translate(BatchHashingParams params, int start_row, int end_row) +{ +#define DISPATCH(NAME, CONDITION) \ + if (CONDITION) \ + { \ + NAME(params, start_row, end_row); \ + } \ + else + + APPLY_FOR_SPECIALIZATIONS(DISPATCH) { pg_unreachable(); } +#undef DISPATCH +} + +#undef APPLY_FOR_SPECIALIZATIONS +#undef APPLY_FOR_VALIDITY +#undef APPLY_FOR_BATCH_FILTER + #include "hash_strategy_impl.c" diff --git a/tsl/src/nodes/vector_agg/hashing/hashing_strategy.h b/tsl/src/nodes/vector_agg/hashing/hashing_strategy.h index bb1cfcb61a5..f34bdda5435 100644 --- a/tsl/src/nodes/vector_agg/hashing/hashing_strategy.h +++ b/tsl/src/nodes/vector_agg/hashing/hashing_strategy.h @@ -49,6 +49,11 @@ typedef struct HashingStrategy uint64 num_allocated_output_keys; MemoryContext key_body_mctx; + /* + * The last used index of an unique grouping key. Key index 0 is invalid. + */ + uint32 last_used_key_index; + /* * In single-column grouping, we store the null key outside of the hash * table, and its index is given by this value. Key index 0 is invalid. @@ -63,6 +68,13 @@ typedef struct HashingStrategy */ struct umash_params *umash_params; #endif + + /* + * Temporary key storages. Some hashing strategies need to put the key in a + * separate memory area, we don't want to alloc/free it on each row. + */ + uint8 *tmp_key_storage; + uint64 num_tmp_key_storage_bytes; } HashingStrategy; void hash_strategy_output_key_alloc(GroupingPolicyHash *policy, uint16 nrows); diff --git a/tsl/src/nodes/vector_agg/plan.c b/tsl/src/nodes/vector_agg/plan.c index 8ae45d372d3..cd8842b27f0 100644 --- a/tsl/src/nodes/vector_agg/plan.c +++ b/tsl/src/nodes/vector_agg/plan.c @@ -396,7 +396,14 @@ get_vectorized_grouping_type(const VectorQualInfo *vqinfo, Agg *agg, List *resol #endif } +#ifdef TS_USE_UMASH + /* + * Use hashing of serialized keys when we have many grouping columns. + */ + return VAGT_HashSerialized; +#else return VAGT_Invalid; +#endif } /* diff --git a/tsl/test/expected/hypercore_vectoragg.out b/tsl/test/expected/hypercore_vectoragg.out index 496d80e616c..8287a44ed93 100644 --- a/tsl/test/expected/hypercore_vectoragg.out +++ b/tsl/test/expected/hypercore_vectoragg.out @@ -314,13 +314,11 @@ select location, count(*) from aggdata where location=1 group by location; -- -- Test ordering/grouping on segmentby, orderby columns -- --- This grouping is currently NOT supported by VectorAgg --- set timescaledb.enable_vectorized_aggregation=true; explain (verbose, costs off) select time, device, sum(temp) from aggdata where device is not null group by time, device order by time, device limit 10; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------- Limit Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, (sum(_hyper_1_1_chunk.temp)) -> Sort @@ -330,9 +328,9 @@ select time, device, sum(temp) from aggdata where device is not null group by ti Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, sum(_hyper_1_1_chunk.temp) Group Key: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device -> Append - -> Partial HashAggregate - Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, PARTIAL sum(_hyper_1_1_chunk.temp) - Group Key: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device + -> Custom Scan (VectorAgg) + Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, (PARTIAL sum(_hyper_1_1_chunk.temp)) + Grouping Policy: hashed with serialized key -> Custom Scan (ColumnarScan) on _timescaledb_internal._hyper_1_1_chunk Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp Filter: (_hyper_1_1_chunk.device IS NOT NULL) @@ -344,7 +342,6 @@ select time, device, sum(temp) from aggdata where device is not null group by ti Filter: (_hyper_1_2_chunk.device IS NOT NULL) (21 rows) -set timescaledb.debug_require_vector_agg to 'forbid'; select time, device, sum(temp) from aggdata where device is not null group by time, device order by time, device limit 10; time | device | sum ------------------------------+--------+----- @@ -355,11 +352,10 @@ select time, device, sum(temp) from aggdata where device is not null group by ti (4 rows) set timecaledb.enable_vectorized_aggregation=false; -reset timescaledb.debug_require_vector_agg; explain (verbose, costs off) select time, device, sum(temp) from aggdata where device is not null group by time, device order by time, device limit 10; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------- + QUERY PLAN +-------------------------------------------------------------------------------------------------------------------------- Limit Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, (sum(_hyper_1_1_chunk.temp)) -> Sort @@ -369,9 +365,9 @@ select time, device, sum(temp) from aggdata where device is not null group by ti Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, sum(_hyper_1_1_chunk.temp) Group Key: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device -> Append - -> Partial HashAggregate - Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, PARTIAL sum(_hyper_1_1_chunk.temp) - Group Key: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device + -> Custom Scan (VectorAgg) + Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, (PARTIAL sum(_hyper_1_1_chunk.temp)) + Grouping Policy: hashed with serialized key -> Custom Scan (ColumnarScan) on _timescaledb_internal._hyper_1_1_chunk Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp Filter: (_hyper_1_1_chunk.device IS NOT NULL) @@ -395,8 +391,8 @@ select time, device, sum(temp) from aggdata where device is not null group by ti set timescaledb.enable_vectorized_aggregation=true; explain (verbose, costs off) select time, device, sum(temp) filter (where device is not null) from aggdata group by time, device order by time, device desc limit 10; - QUERY PLAN ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + QUERY PLAN +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Limit Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, (sum(_hyper_1_1_chunk.temp) FILTER (WHERE (_hyper_1_1_chunk.device IS NOT NULL))) -> Sort @@ -406,9 +402,9 @@ select time, device, sum(temp) filter (where device is not null) from aggdata gr Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, sum(_hyper_1_1_chunk.temp) FILTER (WHERE (_hyper_1_1_chunk.device IS NOT NULL)) Group Key: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device -> Append - -> Partial HashAggregate - Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, PARTIAL sum(_hyper_1_1_chunk.temp) FILTER (WHERE (_hyper_1_1_chunk.device IS NOT NULL)) - Group Key: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device + -> Custom Scan (VectorAgg) + Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, (PARTIAL sum(_hyper_1_1_chunk.temp) FILTER (WHERE (_hyper_1_1_chunk.device IS NOT NULL))) + Grouping Policy: hashed with serialized key -> Custom Scan (ColumnarScan) on _timescaledb_internal._hyper_1_1_chunk Output: _hyper_1_1_chunk."time", _hyper_1_1_chunk.device, _hyper_1_1_chunk.temp -> Partial HashAggregate @@ -418,7 +414,6 @@ select time, device, sum(temp) filter (where device is not null) from aggdata gr Output: _hyper_1_2_chunk."time", _hyper_1_2_chunk.device, _hyper_1_2_chunk.temp (19 rows) -set timescaledb.debug_require_vector_agg to 'forbid'; select time, device, sum(temp) filter (where device is not null) from aggdata group by time, device order by time, device desc limit 10; time | device | sum ------------------------------+--------+----- @@ -430,7 +425,6 @@ select time, device, sum(temp) filter (where device is not null) from aggdata gr (5 rows) set timescaledb.enable_vectorized_aggregation=false; -reset timescaledb.debug_require_vector_agg; explain (verbose, costs off) select time, device, sum(temp) filter (where device is not null) from aggdata group by time, device order by time, device desc limit 10; QUERY PLAN diff --git a/tsl/test/expected/vector_agg_grouping.out b/tsl/test/expected/vector_agg_grouping.out new file mode 100644 index 00000000000..23b5a91a869 --- /dev/null +++ b/tsl/test/expected/vector_agg_grouping.out @@ -0,0 +1,2102 @@ +-- This file and its contents are licensed under the Timescale License. +-- Please see the included NOTICE for copyright information and +-- LICENSE-TIMESCALE for a copy of the license. +\c :TEST_DBNAME :ROLE_SUPERUSER +-- helper function: float -> pseudorandom float [-0.5..0.5] +CREATE OR REPLACE FUNCTION mix(x anyelement) RETURNS float8 AS $$ + SELECT hashfloat8(x::float8) / pow(2, 32) +$$ LANGUAGE SQL; +\set CHUNKS 2::int +\set CHUNK_ROWS 100000::int +\set GROUPING_CARDINALITY 10::int +create table agggroup(t int, s int, + cint2 int2, cint4 int4, cint8 int8); +select create_hypertable('agggroup', 's', chunk_time_interval => :GROUPING_CARDINALITY / :CHUNKS); +NOTICE: adding not-null constraint to column "s" + create_hypertable +----------------------- + (1,public,agggroup,t) +(1 row) + +create view source as +select s * 10000 + t as t, + s, + case when t % 1051 = 0 then null + else (mix(s + t * 1019) * 32767)::int2 end as cint2, + (mix(s + t * 1021) * 32767)::int4 as cint4, + (mix(s + t * 1031) * 32767)::int8 as cint8 +from + generate_series(1::int, :CHUNK_ROWS * :CHUNKS / :GROUPING_CARDINALITY) t, + generate_series(0::int, :GROUPING_CARDINALITY - 1::int) s(s) +; +insert into agggroup select * from source where s = 1; +alter table agggroup set (timescaledb.compress, timescaledb.compress_orderby = 't', + timescaledb.compress_segmentby = 's'); +select count(compress_chunk(x)) from show_chunks('agggroup') x; + count +------- + 1 +(1 row) + +alter table agggroup add column ss int default 11; +alter table agggroup add column x text default '11'; +insert into agggroup +select *, ss::text as x from ( + select *, + case + -- null in entire batch + when s = 2 then null + -- null for some rows + when s = 3 and t % 1051 = 0 then null + -- for some rows same as default + when s = 4 and t % 1057 = 0 then 11 + -- not null for entire batch + else s + end as ss + from source where s != 1 +) t +; +select count(compress_chunk(x)) from show_chunks('agggroup') x; + count +------- + 2 +(1 row) + +vacuum freeze analyze agggroup; +set timescaledb.debug_require_vector_agg = 'require'; +---- Uncomment to generate reference. Note that there are minor discrepancies +---- on float4 due to different numeric stability in our and PG implementations. +--set timescaledb.enable_vectorized_aggregation to off; set timescaledb.debug_require_vector_agg = 'allow'; +select + format('%sselect %s%s(%s) from agggroup%s%s%s;', + explain, + grouping || ', ', + function, variable, + ' where ' || condition, + ' group by ' || grouping, + format(' order by %s(%s), ', function, variable) || grouping || ' limit 10', + function, variable) +from + unnest(array[ + 'explain (costs off) ', + null]) explain, + unnest(array[ + 'cint2', + '*']) variable, + unnest(array[ + 'min', + 'count']) function, + unnest(array[ + null, + 'cint2 > 0', + 'cint2 is null', + 'cint2 is null and x is null']) with ordinality as condition(condition, n), + unnest(array[ + null, + 'cint2', + 'cint4', + 'cint4, cint8', + 'cint8', + 's, cint2', + 's, ss', + 's, x', + 'ss, cint2, x', + 'ss, s', + 'ss, x, cint2', + 't, s, ss, x, cint4, cint8, cint2', + 'x']) with ordinality as grouping(grouping, n) +where + true + and (explain is null /* or condition is null and grouping = 's' */) + and (variable != '*' or function = 'count') +order by explain, condition.n, variable, function, grouping.n +\gexec +select count(*) from agggroup; + count +-------- + 200000 +(1 row) + +select cint2, count(*) from agggroup group by cint2 order by count(*), cint2 limit 10; + cint2 | count +--------+------- + -16216 | 1 + -16071 | 1 + -15916 | 1 + -15892 | 1 + -15891 | 1 + -15732 | 1 + -15693 | 1 + -15637 | 1 + -15620 | 1 + -15615 | 1 +(10 rows) + +select cint4, count(*) from agggroup group by cint4 order by count(*), cint4 limit 10; + cint4 | count +--------+------- + -16350 | 1 + -16237 | 1 + -16144 | 1 + -15987 | 1 + -15925 | 1 + -15862 | 1 + -15849 | 1 + -15825 | 1 + -15804 | 1 + -15760 | 1 +(10 rows) + +select cint4, cint8, count(*) from agggroup group by cint4, cint8 order by count(*), cint4, cint8 limit 10; + cint4 | cint8 | count +--------+-------+------- + -16383 | 4889 | 1 + -16383 | 7417 | 1 + -16383 | 8953 | 1 + -16382 | -8851 | 1 + -16382 | -8612 | 1 + -16382 | -5254 | 1 + -16382 | -4489 | 1 + -16382 | -470 | 1 + -16382 | 411 | 1 + -16382 | 899 | 1 +(10 rows) + +select cint8, count(*) from agggroup group by cint8 order by count(*), cint8 limit 10; + cint8 | count +--------+------- + -16342 | 1 + -16246 | 1 + -16197 | 1 + -16152 | 1 + -16064 | 1 + -15932 | 1 + -15908 | 1 + -15869 | 1 + -15819 | 1 + -15753 | 1 +(10 rows) + +select s, cint2, count(*) from agggroup group by s, cint2 order by count(*), s, cint2 limit 10; + s | cint2 | count +---+--------+------- + 0 | -16377 | 1 + 0 | -16376 | 1 + 0 | -16375 | 1 + 0 | -16373 | 1 + 0 | -16372 | 1 + 0 | -16371 | 1 + 0 | -16370 | 1 + 0 | -16369 | 1 + 0 | -16368 | 1 + 0 | -16367 | 1 +(10 rows) + +select s, ss, count(*) from agggroup group by s, ss order by count(*), s, ss limit 10; + s | ss | count +---+----+------- + 3 | | 19 + 4 | 11 | 19 + 3 | 3 | 19981 + 4 | 4 | 19981 + 0 | 0 | 20000 + 1 | 11 | 20000 + 2 | 11 | 20000 + 5 | 5 | 20000 + 6 | 6 | 20000 + 7 | 7 | 20000 +(10 rows) + +select s, x, count(*) from agggroup group by s, x order by count(*), s, x limit 10; + s | x | count +---+----+------- + 3 | | 19 + 4 | 11 | 19 + 3 | 3 | 19981 + 4 | 4 | 19981 + 0 | 0 | 20000 + 1 | 11 | 20000 + 2 | 11 | 20000 + 5 | 5 | 20000 + 6 | 6 | 20000 + 7 | 7 | 20000 +(10 rows) + +select ss, cint2, x, count(*) from agggroup group by ss, cint2, x order by count(*), ss, cint2, x limit 10; + ss | cint2 | x | count +----+--------+---+------- + 0 | -16377 | 0 | 1 + 0 | -16376 | 0 | 1 + 0 | -16375 | 0 | 1 + 0 | -16373 | 0 | 1 + 0 | -16372 | 0 | 1 + 0 | -16371 | 0 | 1 + 0 | -16370 | 0 | 1 + 0 | -16369 | 0 | 1 + 0 | -16368 | 0 | 1 + 0 | -16367 | 0 | 1 +(10 rows) + +select ss, s, count(*) from agggroup group by ss, s order by count(*), ss, s limit 10; + ss | s | count +----+---+------- + 11 | 4 | 19 + | 3 | 19 + 3 | 3 | 19981 + 4 | 4 | 19981 + 0 | 0 | 20000 + 5 | 5 | 20000 + 6 | 6 | 20000 + 7 | 7 | 20000 + 8 | 8 | 20000 + 9 | 9 | 20000 +(10 rows) + +select ss, x, cint2, count(*) from agggroup group by ss, x, cint2 order by count(*), ss, x, cint2 limit 10; + ss | x | cint2 | count +----+---+--------+------- + 0 | 0 | -16377 | 1 + 0 | 0 | -16376 | 1 + 0 | 0 | -16375 | 1 + 0 | 0 | -16373 | 1 + 0 | 0 | -16372 | 1 + 0 | 0 | -16371 | 1 + 0 | 0 | -16370 | 1 + 0 | 0 | -16369 | 1 + 0 | 0 | -16368 | 1 + 0 | 0 | -16367 | 1 +(10 rows) + +select t, s, ss, x, cint4, cint8, cint2, count(*) from agggroup group by t, s, ss, x, cint4, cint8, cint2 order by count(*), t, s, ss, x, cint4, cint8, cint2 limit 10; + t | s | ss | x | cint4 | cint8 | cint2 | count +----+---+----+---+--------+-------+--------+------- + 1 | 0 | 0 | 0 | -15736 | 12910 | 3398 | 1 + 2 | 0 | 0 | 0 | 1096 | -6638 | -5373 | 1 + 3 | 0 | 0 | 0 | -15920 | 13672 | -7109 | 1 + 4 | 0 | 0 | 0 | 14299 | -8187 | -4927 | 1 + 5 | 0 | 0 | 0 | 9267 | 6436 | 4859 | 1 + 6 | 0 | 0 | 0 | -5203 | 9870 | 12177 | 1 + 7 | 0 | 0 | 0 | 6620 | -781 | 5174 | 1 + 8 | 0 | 0 | 0 | -10427 | 876 | -12705 | 1 + 9 | 0 | 0 | 0 | -14954 | -1593 | 2257 | 1 + 10 | 0 | 0 | 0 | 10047 | -7626 | 3923 | 1 +(10 rows) + +select x, count(*) from agggroup group by x order by count(*), x limit 10; + x | count +----+------- + | 19 + 3 | 19981 + 4 | 19981 + 0 | 20000 + 5 | 20000 + 6 | 20000 + 7 | 20000 + 8 | 20000 + 9 | 20000 + 11 | 40019 +(10 rows) + +select count(cint2) from agggroup; + count +-------- + 199810 +(1 row) + +select cint2, count(cint2) from agggroup group by cint2 order by count(cint2), cint2 limit 10; + cint2 | count +--------+------- + | 0 + -16216 | 1 + -16071 | 1 + -15916 | 1 + -15892 | 1 + -15891 | 1 + -15732 | 1 + -15693 | 1 + -15637 | 1 + -15620 | 1 +(10 rows) + +select cint4, count(cint2) from agggroup group by cint4 order by count(cint2), cint4 limit 10; + cint4 | count +--------+------- + 8426 | 0 + -16350 | 1 + -16237 | 1 + -16144 | 1 + -15987 | 1 + -15925 | 1 + -15862 | 1 + -15849 | 1 + -15825 | 1 + -15804 | 1 +(10 rows) + +select cint4, cint8, count(cint2) from agggroup group by cint4, cint8 order by count(cint2), cint4, cint8 limit 10; + cint4 | cint8 | count +--------+--------+------- + -16291 | 113 | 0 + -16091 | -4084 | 0 + -15799 | 12603 | 0 + -15724 | 15426 | 0 + -15328 | -6092 | 0 + -15279 | -3475 | 0 + -15063 | 3990 | 0 + -14998 | 14464 | 0 + -14949 | -10395 | 0 + -14848 | 3110 | 0 +(10 rows) + +select cint8, count(cint2) from agggroup group by cint8 order by count(cint2), cint8 limit 10; + cint8 | count +--------+------- + -16342 | 1 + -16246 | 1 + -16197 | 1 + -16152 | 1 + -16064 | 1 + -15932 | 1 + -15908 | 1 + -15869 | 1 + -15819 | 1 + -15753 | 1 +(10 rows) + +select s, cint2, count(cint2) from agggroup group by s, cint2 order by count(cint2), s, cint2 limit 10; + s | cint2 | count +---+-------+------- + 0 | | 0 + 1 | | 0 + 2 | | 0 + 3 | | 0 + 4 | | 0 + 5 | | 0 + 6 | | 0 + 7 | | 0 + 8 | | 0 + 9 | | 0 +(10 rows) + +select s, ss, count(cint2) from agggroup group by s, ss order by count(cint2), s, ss limit 10; + s | ss | count +---+----+------- + 3 | | 19 + 4 | 11 | 19 + 3 | 3 | 19962 + 4 | 4 | 19962 + 0 | 0 | 19981 + 1 | 11 | 19981 + 2 | 11 | 19981 + 5 | 5 | 19981 + 6 | 6 | 19981 + 7 | 7 | 19981 +(10 rows) + +select s, x, count(cint2) from agggroup group by s, x order by count(cint2), s, x limit 10; + s | x | count +---+----+------- + 3 | | 19 + 4 | 11 | 19 + 3 | 3 | 19962 + 4 | 4 | 19962 + 0 | 0 | 19981 + 1 | 11 | 19981 + 2 | 11 | 19981 + 5 | 5 | 19981 + 6 | 6 | 19981 + 7 | 7 | 19981 +(10 rows) + +select ss, cint2, x, count(cint2) from agggroup group by ss, cint2, x order by count(cint2), ss, cint2, x limit 10; + ss | cint2 | x | count +----+--------+----+------- + 0 | | 0 | 0 + 3 | | 3 | 0 + 4 | | 4 | 0 + 5 | | 5 | 0 + 6 | | 6 | 0 + 7 | | 7 | 0 + 8 | | 8 | 0 + 9 | | 9 | 0 + 11 | | 11 | 0 + 0 | -16377 | 0 | 1 +(10 rows) + +select ss, s, count(cint2) from agggroup group by ss, s order by count(cint2), ss, s limit 10; + ss | s | count +----+---+------- + 11 | 4 | 19 + | 3 | 19 + 3 | 3 | 19962 + 4 | 4 | 19962 + 0 | 0 | 19981 + 5 | 5 | 19981 + 6 | 6 | 19981 + 7 | 7 | 19981 + 8 | 8 | 19981 + 9 | 9 | 19981 +(10 rows) + +select ss, x, cint2, count(cint2) from agggroup group by ss, x, cint2 order by count(cint2), ss, x, cint2 limit 10; + ss | x | cint2 | count +----+----+--------+------- + 0 | 0 | | 0 + 3 | 3 | | 0 + 4 | 4 | | 0 + 5 | 5 | | 0 + 6 | 6 | | 0 + 7 | 7 | | 0 + 8 | 8 | | 0 + 9 | 9 | | 0 + 11 | 11 | | 0 + 0 | 0 | -16377 | 1 +(10 rows) + +select t, s, ss, x, cint4, cint8, cint2, count(cint2) from agggroup group by t, s, ss, x, cint4, cint8, cint2 order by count(cint2), t, s, ss, x, cint4, cint8, cint2 limit 10; + t | s | ss | x | cint4 | cint8 | cint2 | count +-------+---+----+---+--------+--------+-------+------- + 1051 | 0 | 0 | 0 | -8612 | 14327 | | 0 + 2102 | 0 | 0 | 0 | 11069 | 16047 | | 0 + 3153 | 0 | 0 | 0 | 6192 | 12700 | | 0 + 4204 | 0 | 0 | 0 | 4165 | -10102 | | 0 + 5255 | 0 | 0 | 0 | 16314 | 13418 | | 0 + 6306 | 0 | 0 | 0 | 701 | -3029 | | 0 + 7357 | 0 | 0 | 0 | 1115 | 4913 | | 0 + 8408 | 0 | 0 | 0 | 15553 | 1743 | | 0 + 9459 | 0 | 0 | 0 | -14640 | 11933 | | 0 + 10510 | 0 | 0 | 0 | -14725 | 6531 | | 0 +(10 rows) + +select x, count(cint2) from agggroup group by x order by count(cint2), x limit 10; + x | count +----+------- + | 19 + 3 | 19962 + 4 | 19962 + 0 | 19981 + 5 | 19981 + 6 | 19981 + 7 | 19981 + 8 | 19981 + 9 | 19981 + 11 | 39981 +(10 rows) + +select min(cint2) from agggroup; + min +-------- + -16383 +(1 row) + +select cint2, min(cint2) from agggroup group by cint2 order by min(cint2), cint2 limit 10; + cint2 | min +--------+-------- + -16383 | -16383 + -16382 | -16382 + -16381 | -16381 + -16380 | -16380 + -16379 | -16379 + -16378 | -16378 + -16377 | -16377 + -16376 | -16376 + -16375 | -16375 + -16374 | -16374 +(10 rows) + +select cint4, min(cint2) from agggroup group by cint4 order by min(cint2), cint4 limit 10; + cint4 | min +--------+-------- + -16190 | -16383 + -13372 | -16383 + -10318 | -16383 + -9008 | -16383 + -3043 | -16383 + 6729 | -16383 + -14012 | -16382 + -8606 | -16382 + -3080 | -16382 + 2223 | -16382 +(10 rows) + +select cint4, cint8, min(cint2) from agggroup group by cint4, cint8 order by min(cint2), cint4, cint8 limit 10; + cint4 | cint8 | min +--------+--------+-------- + -16190 | 13646 | -16383 + -13372 | 11094 | -16383 + -10318 | 6326 | -16383 + -9008 | 4390 | -16383 + -3043 | -1794 | -16383 + 6729 | 6717 | -16383 + -14012 | -9888 | -16382 + -8606 | -10357 | -16382 + -3080 | -15609 | -16382 + 2223 | 9035 | -16382 +(10 rows) + +select cint8, min(cint2) from agggroup group by cint8 order by min(cint2), cint8 limit 10; + cint8 | min +--------+-------- + -1794 | -16383 + 4390 | -16383 + 6326 | -16383 + 6717 | -16383 + 11094 | -16383 + 13646 | -16383 + -15609 | -16382 + -10357 | -16382 + -9888 | -16382 + 206 | -16382 +(10 rows) + +select s, cint2, min(cint2) from agggroup group by s, cint2 order by min(cint2), s, cint2 limit 10; + s | cint2 | min +---+--------+-------- + 0 | -16383 | -16383 + 4 | -16383 | -16383 + 5 | -16383 | -16383 + 6 | -16383 | -16383 + 2 | -16382 | -16382 + 7 | -16382 | -16382 + 8 | -16382 | -16382 + 2 | -16381 | -16381 + 3 | -16381 | -16381 + 4 | -16381 | -16381 +(10 rows) + +select s, ss, min(cint2) from agggroup group by s, ss order by min(cint2), s, ss limit 10; + s | ss | min +---+----+-------- + 0 | 0 | -16383 + 4 | 4 | -16383 + 5 | 5 | -16383 + 6 | 6 | -16383 + 2 | 11 | -16382 + 7 | 7 | -16382 + 8 | 8 | -16382 + 3 | 3 | -16381 + 1 | 11 | -16378 + 9 | 9 | -16375 +(10 rows) + +select s, x, min(cint2) from agggroup group by s, x order by min(cint2), s, x limit 10; + s | x | min +---+----+-------- + 0 | 0 | -16383 + 4 | 4 | -16383 + 5 | 5 | -16383 + 6 | 6 | -16383 + 2 | 11 | -16382 + 7 | 7 | -16382 + 8 | 8 | -16382 + 3 | 3 | -16381 + 1 | 11 | -16378 + 9 | 9 | -16375 +(10 rows) + +select ss, cint2, x, min(cint2) from agggroup group by ss, cint2, x order by min(cint2), ss, cint2, x limit 10; + ss | cint2 | x | min +----+--------+----+-------- + 0 | -16383 | 0 | -16383 + 4 | -16383 | 4 | -16383 + 5 | -16383 | 5 | -16383 + 6 | -16383 | 6 | -16383 + 7 | -16382 | 7 | -16382 + 8 | -16382 | 8 | -16382 + 11 | -16382 | 11 | -16382 + 3 | -16381 | 3 | -16381 + 4 | -16381 | 4 | -16381 + 5 | -16381 | 5 | -16381 +(10 rows) + +select ss, s, min(cint2) from agggroup group by ss, s order by min(cint2), ss, s limit 10; + ss | s | min +----+---+-------- + 0 | 0 | -16383 + 4 | 4 | -16383 + 5 | 5 | -16383 + 6 | 6 | -16383 + 7 | 7 | -16382 + 8 | 8 | -16382 + 11 | 2 | -16382 + 3 | 3 | -16381 + 11 | 1 | -16378 + 9 | 9 | -16375 +(10 rows) + +select ss, x, cint2, min(cint2) from agggroup group by ss, x, cint2 order by min(cint2), ss, x, cint2 limit 10; + ss | x | cint2 | min +----+----+--------+-------- + 0 | 0 | -16383 | -16383 + 4 | 4 | -16383 | -16383 + 5 | 5 | -16383 | -16383 + 6 | 6 | -16383 | -16383 + 7 | 7 | -16382 | -16382 + 8 | 8 | -16382 | -16382 + 11 | 11 | -16382 | -16382 + 3 | 3 | -16381 | -16381 + 4 | 4 | -16381 | -16381 + 5 | 5 | -16381 | -16381 +(10 rows) + +select t, s, ss, x, cint4, cint8, cint2, min(cint2) from agggroup group by t, s, ss, x, cint4, cint8, cint2 order by min(cint2), t, s, ss, x, cint4, cint8, cint2 limit 10; + t | s | ss | x | cint4 | cint8 | cint2 | min +-------+---+----+----+--------+--------+--------+-------- + 6194 | 0 | 0 | 0 | -13372 | 11094 | -16383 | -16383 + 17044 | 0 | 0 | 0 | -10318 | 6326 | -16383 | -16383 + 53843 | 4 | 4 | 4 | -9008 | 4390 | -16383 | -16383 + 60530 | 5 | 5 | 5 | 6729 | 6717 | -16383 | -16383 + 73208 | 6 | 6 | 6 | -3043 | -1794 | -16383 | -16383 + 74870 | 6 | 6 | 6 | -16190 | 13646 | -16383 | -16383 + 22836 | 2 | 11 | 11 | -3080 | -15609 | -16382 | -16382 + 29858 | 2 | 11 | 11 | -14012 | -9888 | -16382 | -16382 + 31516 | 2 | 11 | 11 | 6193 | 206 | -16382 | -16382 + 76781 | 7 | 7 | 7 | 9938 | 6519 | -16382 | -16382 +(10 rows) + +select x, min(cint2) from agggroup group by x order by min(cint2), x limit 10; + x | min +----+-------- + 0 | -16383 + 4 | -16383 + 5 | -16383 + 6 | -16383 + 11 | -16382 + 7 | -16382 + 8 | -16382 + 3 | -16381 + 9 | -16375 + | -16295 +(10 rows) + +select count(*) from agggroup where cint2 > 0; + count +------- + 99664 +(1 row) + +select cint2, count(*) from agggroup where cint2 > 0 group by cint2 order by count(*), cint2 limit 10; + cint2 | count +-------+------- + 153 | 1 + 290 | 1 + 490 | 1 + 605 | 1 + 666 | 1 + 700 | 1 + 780 | 1 + 851 | 1 + 936 | 1 + 1001 | 1 +(10 rows) + +select cint4, count(*) from agggroup where cint2 > 0 group by cint4 order by count(*), cint4 limit 10; + cint4 | count +--------+------- + -16383 | 1 + -16380 | 1 + -16371 | 1 + -16368 | 1 + -16366 | 1 + -16365 | 1 + -16363 | 1 + -16360 | 1 + -16356 | 1 + -16350 | 1 +(10 rows) + +select cint4, cint8, count(*) from agggroup where cint2 > 0 group by cint4, cint8 order by count(*), cint4, cint8 limit 10; + cint4 | cint8 | count +--------+-------+------- + -16383 | 4889 | 1 + -16382 | -8851 | 1 + -16382 | -4489 | 1 + -16382 | -470 | 1 + -16382 | 411 | 1 + -16382 | 8377 | 1 + -16382 | 8832 | 1 + -16382 | 15709 | 1 + -16380 | 1449 | 1 + -16379 | 1234 | 1 +(10 rows) + +select cint8, count(*) from agggroup where cint2 > 0 group by cint8 order by count(*), cint8 limit 10; + cint8 | count +--------+------- + -16382 | 1 + -16378 | 1 + -16372 | 1 + -16353 | 1 + -16342 | 1 + -16338 | 1 + -16337 | 1 + -16336 | 1 + -16330 | 1 + -16328 | 1 +(10 rows) + +select s, cint2, count(*) from agggroup where cint2 > 0 group by s, cint2 order by count(*), s, cint2 limit 10; + s | cint2 | count +---+-------+------- + 0 | 4 | 1 + 0 | 7 | 1 + 0 | 8 | 1 + 0 | 9 | 1 + 0 | 10 | 1 + 0 | 11 | 1 + 0 | 18 | 1 + 0 | 24 | 1 + 0 | 28 | 1 + 0 | 31 | 1 +(10 rows) + +select s, ss, count(*) from agggroup where cint2 > 0 group by s, ss order by count(*), s, ss limit 10; + s | ss | count +---+----+------- + 3 | | 9 + 4 | 11 | 9 + 2 | 11 | 9868 + 3 | 3 | 9884 + 6 | 6 | 9890 + 4 | 4 | 9897 + 8 | 8 | 9898 + 7 | 7 | 9973 + 0 | 0 | 10012 + 9 | 9 | 10018 +(10 rows) + +select s, x, count(*) from agggroup where cint2 > 0 group by s, x order by count(*), s, x limit 10; + s | x | count +---+----+------- + 3 | | 9 + 4 | 11 | 9 + 2 | 11 | 9868 + 3 | 3 | 9884 + 6 | 6 | 9890 + 4 | 4 | 9897 + 8 | 8 | 9898 + 7 | 7 | 9973 + 0 | 0 | 10012 + 9 | 9 | 10018 +(10 rows) + +select ss, cint2, x, count(*) from agggroup where cint2 > 0 group by ss, cint2, x order by count(*), ss, cint2, x limit 10; + ss | cint2 | x | count +----+-------+---+------- + 0 | 4 | 0 | 1 + 0 | 7 | 0 | 1 + 0 | 8 | 0 | 1 + 0 | 9 | 0 | 1 + 0 | 10 | 0 | 1 + 0 | 11 | 0 | 1 + 0 | 18 | 0 | 1 + 0 | 24 | 0 | 1 + 0 | 28 | 0 | 1 + 0 | 31 | 0 | 1 +(10 rows) + +select ss, s, count(*) from agggroup where cint2 > 0 group by ss, s order by count(*), ss, s limit 10; + ss | s | count +----+---+------- + 11 | 4 | 9 + | 3 | 9 + 11 | 2 | 9868 + 3 | 3 | 9884 + 6 | 6 | 9890 + 4 | 4 | 9897 + 8 | 8 | 9898 + 7 | 7 | 9973 + 0 | 0 | 10012 + 9 | 9 | 10018 +(10 rows) + +select ss, x, cint2, count(*) from agggroup where cint2 > 0 group by ss, x, cint2 order by count(*), ss, x, cint2 limit 10; + ss | x | cint2 | count +----+---+-------+------- + 0 | 0 | 4 | 1 + 0 | 0 | 7 | 1 + 0 | 0 | 8 | 1 + 0 | 0 | 9 | 1 + 0 | 0 | 10 | 1 + 0 | 0 | 11 | 1 + 0 | 0 | 18 | 1 + 0 | 0 | 24 | 1 + 0 | 0 | 28 | 1 + 0 | 0 | 31 | 1 +(10 rows) + +select t, s, ss, x, cint4, cint8, cint2, count(*) from agggroup where cint2 > 0 group by t, s, ss, x, cint4, cint8, cint2 order by count(*), t, s, ss, x, cint4, cint8, cint2 limit 10; + t | s | ss | x | cint4 | cint8 | cint2 | count +----+---+----+---+--------+-------+-------+------- + 1 | 0 | 0 | 0 | -15736 | 12910 | 3398 | 1 + 5 | 0 | 0 | 0 | 9267 | 6436 | 4859 | 1 + 6 | 0 | 0 | 0 | -5203 | 9870 | 12177 | 1 + 7 | 0 | 0 | 0 | 6620 | -781 | 5174 | 1 + 9 | 0 | 0 | 0 | -14954 | -1593 | 2257 | 1 + 10 | 0 | 0 | 0 | 10047 | -7626 | 3923 | 1 + 14 | 0 | 0 | 0 | -13766 | -398 | 4669 | 1 + 15 | 0 | 0 | 0 | -13009 | 14045 | 15101 | 1 + 19 | 0 | 0 | 0 | -16257 | 4566 | 7684 | 1 + 22 | 0 | 0 | 0 | -6345 | -8658 | 11755 | 1 +(10 rows) + +select x, count(*) from agggroup where cint2 > 0 group by x order by count(*), x limit 10; + x | count +----+------- + | 9 + 3 | 9884 + 6 | 9890 + 4 | 9897 + 8 | 9898 + 7 | 9973 + 0 | 10012 + 9 | 10018 + 5 | 10110 + 11 | 19973 +(10 rows) + +select count(cint2) from agggroup where cint2 > 0; + count +------- + 99664 +(1 row) + +select cint2, count(cint2) from agggroup where cint2 > 0 group by cint2 order by count(cint2), cint2 limit 10; + cint2 | count +-------+------- + 153 | 1 + 290 | 1 + 490 | 1 + 605 | 1 + 666 | 1 + 700 | 1 + 780 | 1 + 851 | 1 + 936 | 1 + 1001 | 1 +(10 rows) + +select cint4, count(cint2) from agggroup where cint2 > 0 group by cint4 order by count(cint2), cint4 limit 10; + cint4 | count +--------+------- + -16383 | 1 + -16380 | 1 + -16371 | 1 + -16368 | 1 + -16366 | 1 + -16365 | 1 + -16363 | 1 + -16360 | 1 + -16356 | 1 + -16350 | 1 +(10 rows) + +select cint4, cint8, count(cint2) from agggroup where cint2 > 0 group by cint4, cint8 order by count(cint2), cint4, cint8 limit 10; + cint4 | cint8 | count +--------+-------+------- + -16383 | 4889 | 1 + -16382 | -8851 | 1 + -16382 | -4489 | 1 + -16382 | -470 | 1 + -16382 | 411 | 1 + -16382 | 8377 | 1 + -16382 | 8832 | 1 + -16382 | 15709 | 1 + -16380 | 1449 | 1 + -16379 | 1234 | 1 +(10 rows) + +select cint8, count(cint2) from agggroup where cint2 > 0 group by cint8 order by count(cint2), cint8 limit 10; + cint8 | count +--------+------- + -16382 | 1 + -16378 | 1 + -16372 | 1 + -16353 | 1 + -16342 | 1 + -16338 | 1 + -16337 | 1 + -16336 | 1 + -16330 | 1 + -16328 | 1 +(10 rows) + +select s, cint2, count(cint2) from agggroup where cint2 > 0 group by s, cint2 order by count(cint2), s, cint2 limit 10; + s | cint2 | count +---+-------+------- + 0 | 4 | 1 + 0 | 7 | 1 + 0 | 8 | 1 + 0 | 9 | 1 + 0 | 10 | 1 + 0 | 11 | 1 + 0 | 18 | 1 + 0 | 24 | 1 + 0 | 28 | 1 + 0 | 31 | 1 +(10 rows) + +select s, ss, count(cint2) from agggroup where cint2 > 0 group by s, ss order by count(cint2), s, ss limit 10; + s | ss | count +---+----+------- + 3 | | 9 + 4 | 11 | 9 + 2 | 11 | 9868 + 3 | 3 | 9884 + 6 | 6 | 9890 + 4 | 4 | 9897 + 8 | 8 | 9898 + 7 | 7 | 9973 + 0 | 0 | 10012 + 9 | 9 | 10018 +(10 rows) + +select s, x, count(cint2) from agggroup where cint2 > 0 group by s, x order by count(cint2), s, x limit 10; + s | x | count +---+----+------- + 3 | | 9 + 4 | 11 | 9 + 2 | 11 | 9868 + 3 | 3 | 9884 + 6 | 6 | 9890 + 4 | 4 | 9897 + 8 | 8 | 9898 + 7 | 7 | 9973 + 0 | 0 | 10012 + 9 | 9 | 10018 +(10 rows) + +select ss, cint2, x, count(cint2) from agggroup where cint2 > 0 group by ss, cint2, x order by count(cint2), ss, cint2, x limit 10; + ss | cint2 | x | count +----+-------+---+------- + 0 | 4 | 0 | 1 + 0 | 7 | 0 | 1 + 0 | 8 | 0 | 1 + 0 | 9 | 0 | 1 + 0 | 10 | 0 | 1 + 0 | 11 | 0 | 1 + 0 | 18 | 0 | 1 + 0 | 24 | 0 | 1 + 0 | 28 | 0 | 1 + 0 | 31 | 0 | 1 +(10 rows) + +select ss, s, count(cint2) from agggroup where cint2 > 0 group by ss, s order by count(cint2), ss, s limit 10; + ss | s | count +----+---+------- + 11 | 4 | 9 + | 3 | 9 + 11 | 2 | 9868 + 3 | 3 | 9884 + 6 | 6 | 9890 + 4 | 4 | 9897 + 8 | 8 | 9898 + 7 | 7 | 9973 + 0 | 0 | 10012 + 9 | 9 | 10018 +(10 rows) + +select ss, x, cint2, count(cint2) from agggroup where cint2 > 0 group by ss, x, cint2 order by count(cint2), ss, x, cint2 limit 10; + ss | x | cint2 | count +----+---+-------+------- + 0 | 0 | 4 | 1 + 0 | 0 | 7 | 1 + 0 | 0 | 8 | 1 + 0 | 0 | 9 | 1 + 0 | 0 | 10 | 1 + 0 | 0 | 11 | 1 + 0 | 0 | 18 | 1 + 0 | 0 | 24 | 1 + 0 | 0 | 28 | 1 + 0 | 0 | 31 | 1 +(10 rows) + +select t, s, ss, x, cint4, cint8, cint2, count(cint2) from agggroup where cint2 > 0 group by t, s, ss, x, cint4, cint8, cint2 order by count(cint2), t, s, ss, x, cint4, cint8, cint2 limit 10; + t | s | ss | x | cint4 | cint8 | cint2 | count +----+---+----+---+--------+-------+-------+------- + 1 | 0 | 0 | 0 | -15736 | 12910 | 3398 | 1 + 5 | 0 | 0 | 0 | 9267 | 6436 | 4859 | 1 + 6 | 0 | 0 | 0 | -5203 | 9870 | 12177 | 1 + 7 | 0 | 0 | 0 | 6620 | -781 | 5174 | 1 + 9 | 0 | 0 | 0 | -14954 | -1593 | 2257 | 1 + 10 | 0 | 0 | 0 | 10047 | -7626 | 3923 | 1 + 14 | 0 | 0 | 0 | -13766 | -398 | 4669 | 1 + 15 | 0 | 0 | 0 | -13009 | 14045 | 15101 | 1 + 19 | 0 | 0 | 0 | -16257 | 4566 | 7684 | 1 + 22 | 0 | 0 | 0 | -6345 | -8658 | 11755 | 1 +(10 rows) + +select x, count(cint2) from agggroup where cint2 > 0 group by x order by count(cint2), x limit 10; + x | count +----+------- + | 9 + 3 | 9884 + 6 | 9890 + 4 | 9897 + 8 | 9898 + 7 | 9973 + 0 | 10012 + 9 | 10018 + 5 | 10110 + 11 | 19973 +(10 rows) + +select min(cint2) from agggroup where cint2 > 0; + min +----- + 1 +(1 row) + +select cint2, min(cint2) from agggroup where cint2 > 0 group by cint2 order by min(cint2), cint2 limit 10; + cint2 | min +-------+----- + 1 | 1 + 2 | 2 + 3 | 3 + 4 | 4 + 5 | 5 + 6 | 6 + 7 | 7 + 8 | 8 + 9 | 9 + 10 | 10 +(10 rows) + +select cint4, min(cint2) from agggroup where cint2 > 0 group by cint4 order by min(cint2), cint4 limit 10; + cint4 | min +--------+----- + -12025 | 1 + -10344 | 1 + -4190 | 1 + -1493 | 1 + 1863 | 1 + 9242 | 1 + 11189 | 1 + 14078 | 1 + 15656 | 1 + -11410 | 2 +(10 rows) + +select cint4, cint8, min(cint2) from agggroup where cint2 > 0 group by cint4, cint8 order by min(cint2), cint4, cint8 limit 10; + cint4 | cint8 | min +--------+--------+----- + -12025 | -2210 | 1 + -10344 | -13684 | 1 + -4190 | -2827 | 1 + -1493 | -1043 | 1 + 1863 | 7650 | 1 + 9242 | -9798 | 1 + 11189 | -5168 | 1 + 14078 | 9929 | 1 + 15656 | 12597 | 1 + -11410 | 6033 | 2 +(10 rows) + +select cint8, min(cint2) from agggroup where cint2 > 0 group by cint8 order by min(cint2), cint8 limit 10; + cint8 | min +--------+----- + -13684 | 1 + -9798 | 1 + -5168 | 1 + -2827 | 1 + -2210 | 1 + -1043 | 1 + 7650 | 1 + 9929 | 1 + 12597 | 1 + -13639 | 2 +(10 rows) + +select s, cint2, min(cint2) from agggroup where cint2 > 0 group by s, cint2 order by min(cint2), s, cint2 limit 10; + s | cint2 | min +---+-------+----- + 1 | 1 | 1 + 2 | 1 | 1 + 3 | 1 | 1 + 5 | 1 | 1 + 7 | 1 | 1 + 8 | 1 | 1 + 1 | 2 | 2 + 3 | 2 | 2 + 7 | 2 | 2 + 9 | 2 | 2 +(10 rows) + +select s, ss, min(cint2) from agggroup where cint2 > 0 group by s, ss order by min(cint2), s, ss limit 10; + s | ss | min +---+----+----- + 1 | 11 | 1 + 2 | 11 | 1 + 3 | 3 | 1 + 5 | 5 | 1 + 7 | 7 | 1 + 8 | 8 | 1 + 9 | 9 | 2 + 6 | 6 | 3 + 0 | 0 | 4 + 4 | 4 | 4 +(10 rows) + +select s, x, min(cint2) from agggroup where cint2 > 0 group by s, x order by min(cint2), s, x limit 10; + s | x | min +---+----+----- + 1 | 11 | 1 + 2 | 11 | 1 + 3 | 3 | 1 + 5 | 5 | 1 + 7 | 7 | 1 + 8 | 8 | 1 + 9 | 9 | 2 + 6 | 6 | 3 + 0 | 0 | 4 + 4 | 4 | 4 +(10 rows) + +select ss, cint2, x, min(cint2) from agggroup where cint2 > 0 group by ss, cint2, x order by min(cint2), ss, cint2, x limit 10; + ss | cint2 | x | min +----+-------+----+----- + 3 | 1 | 3 | 1 + 5 | 1 | 5 | 1 + 7 | 1 | 7 | 1 + 8 | 1 | 8 | 1 + 11 | 1 | 11 | 1 + 3 | 2 | 3 | 2 + 7 | 2 | 7 | 2 + 9 | 2 | 9 | 2 + 11 | 2 | 11 | 2 + 3 | 3 | 3 | 3 +(10 rows) + +select ss, s, min(cint2) from agggroup where cint2 > 0 group by ss, s order by min(cint2), ss, s limit 10; + ss | s | min +----+---+----- + 3 | 3 | 1 + 5 | 5 | 1 + 7 | 7 | 1 + 8 | 8 | 1 + 11 | 1 | 1 + 11 | 2 | 1 + 9 | 9 | 2 + 6 | 6 | 3 + 0 | 0 | 4 + 4 | 4 | 4 +(10 rows) + +select ss, x, cint2, min(cint2) from agggroup where cint2 > 0 group by ss, x, cint2 order by min(cint2), ss, x, cint2 limit 10; + ss | x | cint2 | min +----+----+-------+----- + 3 | 3 | 1 | 1 + 5 | 5 | 1 | 1 + 7 | 7 | 1 | 1 + 8 | 8 | 1 | 1 + 11 | 11 | 1 | 1 + 3 | 3 | 2 | 2 + 7 | 7 | 2 | 2 + 9 | 9 | 2 | 2 + 11 | 11 | 2 | 2 + 3 | 3 | 3 | 3 +(10 rows) + +select t, s, ss, x, cint4, cint8, cint2, min(cint2) from agggroup where cint2 > 0 group by t, s, ss, x, cint4, cint8, cint2 order by min(cint2), t, s, ss, x, cint4, cint8, cint2 limit 10; + t | s | ss | x | cint4 | cint8 | cint2 | min +-------+---+----+----+--------+--------+-------+----- + 11611 | 1 | 11 | 11 | -12025 | -2210 | 1 | 1 + 28649 | 2 | 11 | 11 | -1493 | -1043 | 1 | 1 + 28786 | 1 | 11 | 11 | -4190 | -2827 | 1 | 1 + 41774 | 3 | 3 | 3 | 1863 | 7650 | 1 | 1 + 41779 | 3 | 3 | 3 | 14078 | 9929 | 1 | 1 + 51152 | 5 | 5 | 5 | 9242 | -9798 | 1 | 1 + 70932 | 7 | 7 | 7 | -10344 | -13684 | 1 | 1 + 86957 | 7 | 7 | 7 | 15656 | 12597 | 1 | 1 + 89689 | 8 | 8 | 8 | 11189 | -5168 | 1 | 1 + 22147 | 1 | 11 | 11 | -9569 | 9760 | 2 | 2 +(10 rows) + +select x, min(cint2) from agggroup where cint2 > 0 group by x order by min(cint2), x limit 10; + x | min +----+------ + 11 | 1 + 3 | 1 + 5 | 1 + 7 | 1 + 8 | 1 + 9 | 2 + 6 | 3 + 0 | 4 + 4 | 4 + | 4895 +(10 rows) + +select count(*) from agggroup where cint2 is null; + count +------- + 190 +(1 row) + +select cint2, count(*) from agggroup where cint2 is null group by cint2 order by count(*), cint2 limit 10; + cint2 | count +-------+------- + | 190 +(1 row) + +select cint4, count(*) from agggroup where cint2 is null group by cint4 order by count(*), cint4 limit 10; + cint4 | count +--------+------- + -16291 | 1 + -16091 | 1 + -15799 | 1 + -15724 | 1 + -15328 | 1 + -15279 | 1 + -15063 | 1 + -14998 | 1 + -14949 | 1 + -14848 | 1 +(10 rows) + +select cint4, cint8, count(*) from agggroup where cint2 is null group by cint4, cint8 order by count(*), cint4, cint8 limit 10; + cint4 | cint8 | count +--------+--------+------- + -16291 | 113 | 1 + -16091 | -4084 | 1 + -15799 | 12603 | 1 + -15724 | 15426 | 1 + -15328 | -6092 | 1 + -15279 | -3475 | 1 + -15063 | 3990 | 1 + -14998 | 14464 | 1 + -14949 | -10395 | 1 + -14848 | 3110 | 1 +(10 rows) + +select cint8, count(*) from agggroup where cint2 is null group by cint8 order by count(*), cint8 limit 10; + cint8 | count +--------+------- + -16026 | 1 + -15987 | 1 + -15904 | 1 + -15897 | 1 + -15761 | 1 + -15506 | 1 + -15346 | 1 + -14986 | 1 + -14811 | 1 + -14674 | 1 +(10 rows) + +select s, cint2, count(*) from agggroup where cint2 is null group by s, cint2 order by count(*), s, cint2 limit 10; + s | cint2 | count +---+-------+------- + 0 | | 19 + 1 | | 19 + 2 | | 19 + 3 | | 19 + 4 | | 19 + 5 | | 19 + 6 | | 19 + 7 | | 19 + 8 | | 19 + 9 | | 19 +(10 rows) + +select s, ss, count(*) from agggroup where cint2 is null group by s, ss order by count(*), s, ss limit 10; + s | ss | count +---+----+------- + 0 | 0 | 19 + 1 | 11 | 19 + 2 | 11 | 19 + 3 | 3 | 19 + 4 | 4 | 19 + 5 | 5 | 19 + 6 | 6 | 19 + 7 | 7 | 19 + 8 | 8 | 19 + 9 | 9 | 19 +(10 rows) + +select s, x, count(*) from agggroup where cint2 is null group by s, x order by count(*), s, x limit 10; + s | x | count +---+----+------- + 0 | 0 | 19 + 1 | 11 | 19 + 2 | 11 | 19 + 3 | 3 | 19 + 4 | 4 | 19 + 5 | 5 | 19 + 6 | 6 | 19 + 7 | 7 | 19 + 8 | 8 | 19 + 9 | 9 | 19 +(10 rows) + +select ss, cint2, x, count(*) from agggroup where cint2 is null group by ss, cint2, x order by count(*), ss, cint2, x limit 10; + ss | cint2 | x | count +----+-------+----+------- + 0 | | 0 | 19 + 3 | | 3 | 19 + 4 | | 4 | 19 + 5 | | 5 | 19 + 6 | | 6 | 19 + 7 | | 7 | 19 + 8 | | 8 | 19 + 9 | | 9 | 19 + 11 | | 11 | 38 +(9 rows) + +select ss, s, count(*) from agggroup where cint2 is null group by ss, s order by count(*), ss, s limit 10; + ss | s | count +----+---+------- + 0 | 0 | 19 + 3 | 3 | 19 + 4 | 4 | 19 + 5 | 5 | 19 + 6 | 6 | 19 + 7 | 7 | 19 + 8 | 8 | 19 + 9 | 9 | 19 + 11 | 1 | 19 + 11 | 2 | 19 +(10 rows) + +select ss, x, cint2, count(*) from agggroup where cint2 is null group by ss, x, cint2 order by count(*), ss, x, cint2 limit 10; + ss | x | cint2 | count +----+----+-------+------- + 0 | 0 | | 19 + 3 | 3 | | 19 + 4 | 4 | | 19 + 5 | 5 | | 19 + 6 | 6 | | 19 + 7 | 7 | | 19 + 8 | 8 | | 19 + 9 | 9 | | 19 + 11 | 11 | | 38 +(9 rows) + +select t, s, ss, x, cint4, cint8, cint2, count(*) from agggroup where cint2 is null group by t, s, ss, x, cint4, cint8, cint2 order by count(*), t, s, ss, x, cint4, cint8, cint2 limit 10; + t | s | ss | x | cint4 | cint8 | cint2 | count +-------+---+----+---+--------+--------+-------+------- + 1051 | 0 | 0 | 0 | -8612 | 14327 | | 1 + 2102 | 0 | 0 | 0 | 11069 | 16047 | | 1 + 3153 | 0 | 0 | 0 | 6192 | 12700 | | 1 + 4204 | 0 | 0 | 0 | 4165 | -10102 | | 1 + 5255 | 0 | 0 | 0 | 16314 | 13418 | | 1 + 6306 | 0 | 0 | 0 | 701 | -3029 | | 1 + 7357 | 0 | 0 | 0 | 1115 | 4913 | | 1 + 8408 | 0 | 0 | 0 | 15553 | 1743 | | 1 + 9459 | 0 | 0 | 0 | -14640 | 11933 | | 1 + 10510 | 0 | 0 | 0 | -14725 | 6531 | | 1 +(10 rows) + +select x, count(*) from agggroup where cint2 is null group by x order by count(*), x limit 10; + x | count +----+------- + 0 | 19 + 3 | 19 + 4 | 19 + 5 | 19 + 6 | 19 + 7 | 19 + 8 | 19 + 9 | 19 + 11 | 38 +(9 rows) + +select count(cint2) from agggroup where cint2 is null; + count +------- + 0 +(1 row) + +select cint2, count(cint2) from agggroup where cint2 is null group by cint2 order by count(cint2), cint2 limit 10; + cint2 | count +-------+------- + | 0 +(1 row) + +select cint4, count(cint2) from agggroup where cint2 is null group by cint4 order by count(cint2), cint4 limit 10; + cint4 | count +--------+------- + -16291 | 0 + -16091 | 0 + -15799 | 0 + -15724 | 0 + -15328 | 0 + -15279 | 0 + -15063 | 0 + -14998 | 0 + -14949 | 0 + -14848 | 0 +(10 rows) + +select cint4, cint8, count(cint2) from agggroup where cint2 is null group by cint4, cint8 order by count(cint2), cint4, cint8 limit 10; + cint4 | cint8 | count +--------+--------+------- + -16291 | 113 | 0 + -16091 | -4084 | 0 + -15799 | 12603 | 0 + -15724 | 15426 | 0 + -15328 | -6092 | 0 + -15279 | -3475 | 0 + -15063 | 3990 | 0 + -14998 | 14464 | 0 + -14949 | -10395 | 0 + -14848 | 3110 | 0 +(10 rows) + +select cint8, count(cint2) from agggroup where cint2 is null group by cint8 order by count(cint2), cint8 limit 10; + cint8 | count +--------+------- + -16026 | 0 + -15987 | 0 + -15904 | 0 + -15897 | 0 + -15761 | 0 + -15506 | 0 + -15346 | 0 + -14986 | 0 + -14811 | 0 + -14674 | 0 +(10 rows) + +select s, cint2, count(cint2) from agggroup where cint2 is null group by s, cint2 order by count(cint2), s, cint2 limit 10; + s | cint2 | count +---+-------+------- + 0 | | 0 + 1 | | 0 + 2 | | 0 + 3 | | 0 + 4 | | 0 + 5 | | 0 + 6 | | 0 + 7 | | 0 + 8 | | 0 + 9 | | 0 +(10 rows) + +select s, ss, count(cint2) from agggroup where cint2 is null group by s, ss order by count(cint2), s, ss limit 10; + s | ss | count +---+----+------- + 0 | 0 | 0 + 1 | 11 | 0 + 2 | 11 | 0 + 3 | 3 | 0 + 4 | 4 | 0 + 5 | 5 | 0 + 6 | 6 | 0 + 7 | 7 | 0 + 8 | 8 | 0 + 9 | 9 | 0 +(10 rows) + +select s, x, count(cint2) from agggroup where cint2 is null group by s, x order by count(cint2), s, x limit 10; + s | x | count +---+----+------- + 0 | 0 | 0 + 1 | 11 | 0 + 2 | 11 | 0 + 3 | 3 | 0 + 4 | 4 | 0 + 5 | 5 | 0 + 6 | 6 | 0 + 7 | 7 | 0 + 8 | 8 | 0 + 9 | 9 | 0 +(10 rows) + +select ss, cint2, x, count(cint2) from agggroup where cint2 is null group by ss, cint2, x order by count(cint2), ss, cint2, x limit 10; + ss | cint2 | x | count +----+-------+----+------- + 0 | | 0 | 0 + 3 | | 3 | 0 + 4 | | 4 | 0 + 5 | | 5 | 0 + 6 | | 6 | 0 + 7 | | 7 | 0 + 8 | | 8 | 0 + 9 | | 9 | 0 + 11 | | 11 | 0 +(9 rows) + +select ss, s, count(cint2) from agggroup where cint2 is null group by ss, s order by count(cint2), ss, s limit 10; + ss | s | count +----+---+------- + 0 | 0 | 0 + 3 | 3 | 0 + 4 | 4 | 0 + 5 | 5 | 0 + 6 | 6 | 0 + 7 | 7 | 0 + 8 | 8 | 0 + 9 | 9 | 0 + 11 | 1 | 0 + 11 | 2 | 0 +(10 rows) + +select ss, x, cint2, count(cint2) from agggroup where cint2 is null group by ss, x, cint2 order by count(cint2), ss, x, cint2 limit 10; + ss | x | cint2 | count +----+----+-------+------- + 0 | 0 | | 0 + 3 | 3 | | 0 + 4 | 4 | | 0 + 5 | 5 | | 0 + 6 | 6 | | 0 + 7 | 7 | | 0 + 8 | 8 | | 0 + 9 | 9 | | 0 + 11 | 11 | | 0 +(9 rows) + +select t, s, ss, x, cint4, cint8, cint2, count(cint2) from agggroup where cint2 is null group by t, s, ss, x, cint4, cint8, cint2 order by count(cint2), t, s, ss, x, cint4, cint8, cint2 limit 10; + t | s | ss | x | cint4 | cint8 | cint2 | count +-------+---+----+---+--------+--------+-------+------- + 1051 | 0 | 0 | 0 | -8612 | 14327 | | 0 + 2102 | 0 | 0 | 0 | 11069 | 16047 | | 0 + 3153 | 0 | 0 | 0 | 6192 | 12700 | | 0 + 4204 | 0 | 0 | 0 | 4165 | -10102 | | 0 + 5255 | 0 | 0 | 0 | 16314 | 13418 | | 0 + 6306 | 0 | 0 | 0 | 701 | -3029 | | 0 + 7357 | 0 | 0 | 0 | 1115 | 4913 | | 0 + 8408 | 0 | 0 | 0 | 15553 | 1743 | | 0 + 9459 | 0 | 0 | 0 | -14640 | 11933 | | 0 + 10510 | 0 | 0 | 0 | -14725 | 6531 | | 0 +(10 rows) + +select x, count(cint2) from agggroup where cint2 is null group by x order by count(cint2), x limit 10; + x | count +----+------- + 0 | 0 + 11 | 0 + 3 | 0 + 4 | 0 + 5 | 0 + 6 | 0 + 7 | 0 + 8 | 0 + 9 | 0 +(9 rows) + +select min(cint2) from agggroup where cint2 is null; + min +----- + +(1 row) + +select cint2, min(cint2) from agggroup where cint2 is null group by cint2 order by min(cint2), cint2 limit 10; + cint2 | min +-------+----- + | +(1 row) + +select cint4, min(cint2) from agggroup where cint2 is null group by cint4 order by min(cint2), cint4 limit 10; + cint4 | min +--------+----- + -16291 | + -16091 | + -15799 | + -15724 | + -15328 | + -15279 | + -15063 | + -14998 | + -14949 | + -14848 | +(10 rows) + +select cint4, cint8, min(cint2) from agggroup where cint2 is null group by cint4, cint8 order by min(cint2), cint4, cint8 limit 10; + cint4 | cint8 | min +--------+--------+----- + -16291 | 113 | + -16091 | -4084 | + -15799 | 12603 | + -15724 | 15426 | + -15328 | -6092 | + -15279 | -3475 | + -15063 | 3990 | + -14998 | 14464 | + -14949 | -10395 | + -14848 | 3110 | +(10 rows) + +select cint8, min(cint2) from agggroup where cint2 is null group by cint8 order by min(cint2), cint8 limit 10; + cint8 | min +--------+----- + -16026 | + -15987 | + -15904 | + -15897 | + -15761 | + -15506 | + -15346 | + -14986 | + -14811 | + -14674 | +(10 rows) + +select s, cint2, min(cint2) from agggroup where cint2 is null group by s, cint2 order by min(cint2), s, cint2 limit 10; + s | cint2 | min +---+-------+----- + 0 | | + 1 | | + 2 | | + 3 | | + 4 | | + 5 | | + 6 | | + 7 | | + 8 | | + 9 | | +(10 rows) + +select s, ss, min(cint2) from agggroup where cint2 is null group by s, ss order by min(cint2), s, ss limit 10; + s | ss | min +---+----+----- + 0 | 0 | + 1 | 11 | + 2 | 11 | + 3 | 3 | + 4 | 4 | + 5 | 5 | + 6 | 6 | + 7 | 7 | + 8 | 8 | + 9 | 9 | +(10 rows) + +select s, x, min(cint2) from agggroup where cint2 is null group by s, x order by min(cint2), s, x limit 10; + s | x | min +---+----+----- + 0 | 0 | + 1 | 11 | + 2 | 11 | + 3 | 3 | + 4 | 4 | + 5 | 5 | + 6 | 6 | + 7 | 7 | + 8 | 8 | + 9 | 9 | +(10 rows) + +select ss, cint2, x, min(cint2) from agggroup where cint2 is null group by ss, cint2, x order by min(cint2), ss, cint2, x limit 10; + ss | cint2 | x | min +----+-------+----+----- + 0 | | 0 | + 3 | | 3 | + 4 | | 4 | + 5 | | 5 | + 6 | | 6 | + 7 | | 7 | + 8 | | 8 | + 9 | | 9 | + 11 | | 11 | +(9 rows) + +select ss, s, min(cint2) from agggroup where cint2 is null group by ss, s order by min(cint2), ss, s limit 10; + ss | s | min +----+---+----- + 0 | 0 | + 3 | 3 | + 4 | 4 | + 5 | 5 | + 6 | 6 | + 7 | 7 | + 8 | 8 | + 9 | 9 | + 11 | 1 | + 11 | 2 | +(10 rows) + +select ss, x, cint2, min(cint2) from agggroup where cint2 is null group by ss, x, cint2 order by min(cint2), ss, x, cint2 limit 10; + ss | x | cint2 | min +----+----+-------+----- + 0 | 0 | | + 3 | 3 | | + 4 | 4 | | + 5 | 5 | | + 6 | 6 | | + 7 | 7 | | + 8 | 8 | | + 9 | 9 | | + 11 | 11 | | +(9 rows) + +select t, s, ss, x, cint4, cint8, cint2, min(cint2) from agggroup where cint2 is null group by t, s, ss, x, cint4, cint8, cint2 order by min(cint2), t, s, ss, x, cint4, cint8, cint2 limit 10; + t | s | ss | x | cint4 | cint8 | cint2 | min +-------+---+----+---+--------+--------+-------+----- + 1051 | 0 | 0 | 0 | -8612 | 14327 | | + 2102 | 0 | 0 | 0 | 11069 | 16047 | | + 3153 | 0 | 0 | 0 | 6192 | 12700 | | + 4204 | 0 | 0 | 0 | 4165 | -10102 | | + 5255 | 0 | 0 | 0 | 16314 | 13418 | | + 6306 | 0 | 0 | 0 | 701 | -3029 | | + 7357 | 0 | 0 | 0 | 1115 | 4913 | | + 8408 | 0 | 0 | 0 | 15553 | 1743 | | + 9459 | 0 | 0 | 0 | -14640 | 11933 | | + 10510 | 0 | 0 | 0 | -14725 | 6531 | | +(10 rows) + +select x, min(cint2) from agggroup where cint2 is null group by x order by min(cint2), x limit 10; + x | min +----+----- + 0 | + 11 | + 3 | + 4 | + 5 | + 6 | + 7 | + 8 | + 9 | +(9 rows) + +select count(*) from agggroup where cint2 is null and x is null; + count +------- + 0 +(1 row) + +select cint2, count(*) from agggroup where cint2 is null and x is null group by cint2 order by count(*), cint2 limit 10; + cint2 | count +-------+------- +(0 rows) + +select cint4, count(*) from agggroup where cint2 is null and x is null group by cint4 order by count(*), cint4 limit 10; + cint4 | count +-------+------- +(0 rows) + +select cint4, cint8, count(*) from agggroup where cint2 is null and x is null group by cint4, cint8 order by count(*), cint4, cint8 limit 10; + cint4 | cint8 | count +-------+-------+------- +(0 rows) + +select cint8, count(*) from agggroup where cint2 is null and x is null group by cint8 order by count(*), cint8 limit 10; + cint8 | count +-------+------- +(0 rows) + +select s, cint2, count(*) from agggroup where cint2 is null and x is null group by s, cint2 order by count(*), s, cint2 limit 10; + s | cint2 | count +---+-------+------- +(0 rows) + +select s, ss, count(*) from agggroup where cint2 is null and x is null group by s, ss order by count(*), s, ss limit 10; + s | ss | count +---+----+------- +(0 rows) + +select s, x, count(*) from agggroup where cint2 is null and x is null group by s, x order by count(*), s, x limit 10; + s | x | count +---+---+------- +(0 rows) + +select ss, cint2, x, count(*) from agggroup where cint2 is null and x is null group by ss, cint2, x order by count(*), ss, cint2, x limit 10; + ss | cint2 | x | count +----+-------+---+------- +(0 rows) + +select ss, s, count(*) from agggroup where cint2 is null and x is null group by ss, s order by count(*), ss, s limit 10; + ss | s | count +----+---+------- +(0 rows) + +select ss, x, cint2, count(*) from agggroup where cint2 is null and x is null group by ss, x, cint2 order by count(*), ss, x, cint2 limit 10; + ss | x | cint2 | count +----+---+-------+------- +(0 rows) + +select t, s, ss, x, cint4, cint8, cint2, count(*) from agggroup where cint2 is null and x is null group by t, s, ss, x, cint4, cint8, cint2 order by count(*), t, s, ss, x, cint4, cint8, cint2 limit 10; + t | s | ss | x | cint4 | cint8 | cint2 | count +---+---+----+---+-------+-------+-------+------- +(0 rows) + +select x, count(*) from agggroup where cint2 is null and x is null group by x order by count(*), x limit 10; + x | count +---+------- +(0 rows) + +select count(cint2) from agggroup where cint2 is null and x is null; + count +------- + 0 +(1 row) + +select cint2, count(cint2) from agggroup where cint2 is null and x is null group by cint2 order by count(cint2), cint2 limit 10; + cint2 | count +-------+------- +(0 rows) + +select cint4, count(cint2) from agggroup where cint2 is null and x is null group by cint4 order by count(cint2), cint4 limit 10; + cint4 | count +-------+------- +(0 rows) + +select cint4, cint8, count(cint2) from agggroup where cint2 is null and x is null group by cint4, cint8 order by count(cint2), cint4, cint8 limit 10; + cint4 | cint8 | count +-------+-------+------- +(0 rows) + +select cint8, count(cint2) from agggroup where cint2 is null and x is null group by cint8 order by count(cint2), cint8 limit 10; + cint8 | count +-------+------- +(0 rows) + +select s, cint2, count(cint2) from agggroup where cint2 is null and x is null group by s, cint2 order by count(cint2), s, cint2 limit 10; + s | cint2 | count +---+-------+------- +(0 rows) + +select s, ss, count(cint2) from agggroup where cint2 is null and x is null group by s, ss order by count(cint2), s, ss limit 10; + s | ss | count +---+----+------- +(0 rows) + +select s, x, count(cint2) from agggroup where cint2 is null and x is null group by s, x order by count(cint2), s, x limit 10; + s | x | count +---+---+------- +(0 rows) + +select ss, cint2, x, count(cint2) from agggroup where cint2 is null and x is null group by ss, cint2, x order by count(cint2), ss, cint2, x limit 10; + ss | cint2 | x | count +----+-------+---+------- +(0 rows) + +select ss, s, count(cint2) from agggroup where cint2 is null and x is null group by ss, s order by count(cint2), ss, s limit 10; + ss | s | count +----+---+------- +(0 rows) + +select ss, x, cint2, count(cint2) from agggroup where cint2 is null and x is null group by ss, x, cint2 order by count(cint2), ss, x, cint2 limit 10; + ss | x | cint2 | count +----+---+-------+------- +(0 rows) + +select t, s, ss, x, cint4, cint8, cint2, count(cint2) from agggroup where cint2 is null and x is null group by t, s, ss, x, cint4, cint8, cint2 order by count(cint2), t, s, ss, x, cint4, cint8, cint2 limit 10; + t | s | ss | x | cint4 | cint8 | cint2 | count +---+---+----+---+-------+-------+-------+------- +(0 rows) + +select x, count(cint2) from agggroup where cint2 is null and x is null group by x order by count(cint2), x limit 10; + x | count +---+------- +(0 rows) + +select min(cint2) from agggroup where cint2 is null and x is null; + min +----- + +(1 row) + +select cint2, min(cint2) from agggroup where cint2 is null and x is null group by cint2 order by min(cint2), cint2 limit 10; + cint2 | min +-------+----- +(0 rows) + +select cint4, min(cint2) from agggroup where cint2 is null and x is null group by cint4 order by min(cint2), cint4 limit 10; + cint4 | min +-------+----- +(0 rows) + +select cint4, cint8, min(cint2) from agggroup where cint2 is null and x is null group by cint4, cint8 order by min(cint2), cint4, cint8 limit 10; + cint4 | cint8 | min +-------+-------+----- +(0 rows) + +select cint8, min(cint2) from agggroup where cint2 is null and x is null group by cint8 order by min(cint2), cint8 limit 10; + cint8 | min +-------+----- +(0 rows) + +select s, cint2, min(cint2) from agggroup where cint2 is null and x is null group by s, cint2 order by min(cint2), s, cint2 limit 10; + s | cint2 | min +---+-------+----- +(0 rows) + +select s, ss, min(cint2) from agggroup where cint2 is null and x is null group by s, ss order by min(cint2), s, ss limit 10; + s | ss | min +---+----+----- +(0 rows) + +select s, x, min(cint2) from agggroup where cint2 is null and x is null group by s, x order by min(cint2), s, x limit 10; + s | x | min +---+---+----- +(0 rows) + +select ss, cint2, x, min(cint2) from agggroup where cint2 is null and x is null group by ss, cint2, x order by min(cint2), ss, cint2, x limit 10; + ss | cint2 | x | min +----+-------+---+----- +(0 rows) + +select ss, s, min(cint2) from agggroup where cint2 is null and x is null group by ss, s order by min(cint2), ss, s limit 10; + ss | s | min +----+---+----- +(0 rows) + +select ss, x, cint2, min(cint2) from agggroup where cint2 is null and x is null group by ss, x, cint2 order by min(cint2), ss, x, cint2 limit 10; + ss | x | cint2 | min +----+---+-------+----- +(0 rows) + +select t, s, ss, x, cint4, cint8, cint2, min(cint2) from agggroup where cint2 is null and x is null group by t, s, ss, x, cint4, cint8, cint2 order by min(cint2), t, s, ss, x, cint4, cint8, cint2 limit 10; + t | s | ss | x | cint4 | cint8 | cint2 | min +---+---+----+---+-------+-------+-------+----- +(0 rows) + +select x, min(cint2) from agggroup where cint2 is null and x is null group by x order by min(cint2), x limit 10; + x | min +---+----- +(0 rows) + +reset timescaledb.debug_require_vector_agg; +-- Test long text columns. Also make one of them a segmentby, so that we can +-- test the long scalar values. +create table long(t int, a text, b text, c text, d text); +select create_hypertable('long', 't'); +NOTICE: adding not-null constraint to column "t" + create_hypertable +------------------- + (3,public,long,t) +(1 row) + +insert into long select n, a, x, x, x from ( + select n, 'short' || m a, repeat('1', 100 * 4 + n) x + from generate_series(1, 4) n, + generate_series(1, 4) m) t +; +insert into long values (-1, 'a', 'b', 'c', 'd'); +insert into long values (-2, repeat('long', 1000), 'b', 'c', 'd'); +alter table long set (timescaledb.compress, timescaledb.compress_segmentby = 'a', + timescaledb.compress_orderby = 't desc'); +select count(compress_chunk(x)) from show_chunks('long') x; + count +------- + 2 +(1 row) + +set timescaledb.debug_require_vector_agg = 'require'; +---- Uncomment to generate reference. +--set timescaledb.enable_vectorized_aggregation to off; set timescaledb.debug_require_vector_agg = 'allow'; +-- Various placements of long scalar column +select sum(t) from long group by a, b, c, d order by 1 limit 10; + sum +----- + -2 + -1 + 1 + 1 + 1 + 1 + 2 + 2 + 2 + 2 +(10 rows) + +select sum(t) from long group by d, b, c, a order by 1 limit 10; + sum +----- + -2 + -1 + 1 + 1 + 1 + 1 + 2 + 2 + 2 + 2 +(10 rows) + +select sum(t) from long group by d, b, a, c order by 1 limit 10; + sum +----- + -2 + -1 + 1 + 1 + 1 + 1 + 2 + 2 + 2 + 2 +(10 rows) + +-- Just the scalar column +select sum(t) from long group by a order by 1; + sum +----- + -2 + -1 + 10 + 10 + 10 + 10 +(6 rows) + +-- No scalar columns +select sum(t) from long group by b, c, d order by 1 limit 10; + sum +----- + -3 + 4 + 8 + 12 + 16 +(5 rows) + +reset timescaledb.debug_require_vector_agg; +-- Test various serialized key lengths. We want to touch the transition from short +-- to long varlena header for the serialized key. +create table keylength(t int, a text, b text); +select create_hypertable('keylength', 't'); +NOTICE: adding not-null constraint to column "t" + create_hypertable +------------------------ + (5,public,keylength,t) +(1 row) + +insert into keylength select t, 'a', repeat('b', t) from generate_series(1, 1000) t; +insert into keylength values (-1, '', ''); -- second chunk +alter table keylength set (timescaledb.compress, timescaledb.compress_segmentby = '', + timescaledb.compress_orderby = 't desc'); +select count(compress_chunk(x)) from show_chunks('keylength') x; + count +------- + 2 +(1 row) + +set timescaledb.debug_require_vector_agg = 'require'; +---- Uncomment to generate reference. +--set timescaledb.enable_vectorized_aggregation to off; set timescaledb.debug_require_vector_agg = 'allow'; +select sum(t) from keylength group by a, b order by 1 desc limit 10; + sum +------ + 1000 + 999 + 998 + 997 + 996 + 995 + 994 + 993 + 992 + 991 +(10 rows) + +select sum(t) from keylength group by b, a order by 1 desc limit 10; + sum +------ + 1000 + 999 + 998 + 997 + 996 + 995 + 994 + 993 + 992 + 991 +(10 rows) + +reset timescaledb.debug_require_vector_agg; diff --git a/tsl/test/expected/vectorized_aggregation.out b/tsl/test/expected/vectorized_aggregation.out index a55ef2b133e..db53ab631dc 100644 --- a/tsl/test/expected/vectorized_aggregation.out +++ b/tsl/test/expected/vectorized_aggregation.out @@ -541,7 +541,7 @@ SELECT sum(segment_by_value) FROM testtable GROUP BY int_value; Output: _hyper_1_10_chunk.int_value, _hyper_1_10_chunk.segment_by_value (63 rows) --- Vectorization not possible with grouping by multiple columns +-- Vectorization possible with grouping by multiple columns :EXPLAIN SELECT sum(segment_by_value) FROM testtable GROUP BY int_value, float_value; QUERY PLAN @@ -553,23 +553,23 @@ SELECT sum(segment_by_value) FROM testtable GROUP BY int_value, float_value; Output: _hyper_1_1_chunk.int_value, _hyper_1_1_chunk.float_value, (PARTIAL sum(_hyper_1_1_chunk.segment_by_value)) Workers Planned: 2 -> Parallel Append - -> Partial HashAggregate - Output: _hyper_1_1_chunk.int_value, _hyper_1_1_chunk.float_value, PARTIAL sum(_hyper_1_1_chunk.segment_by_value) - Group Key: _hyper_1_1_chunk.int_value, _hyper_1_1_chunk.float_value + -> Custom Scan (VectorAgg) + Output: _hyper_1_1_chunk.int_value, _hyper_1_1_chunk.float_value, (PARTIAL sum(_hyper_1_1_chunk.segment_by_value)) + Grouping Policy: hashed with serialized key -> Custom Scan (DecompressChunk) on _timescaledb_internal._hyper_1_1_chunk Output: _hyper_1_1_chunk.int_value, _hyper_1_1_chunk.float_value, _hyper_1_1_chunk.segment_by_value -> Parallel Seq Scan on _timescaledb_internal.compress_hyper_2_11_chunk Output: compress_hyper_2_11_chunk._ts_meta_count, compress_hyper_2_11_chunk.segment_by_value, compress_hyper_2_11_chunk._ts_meta_min_1, compress_hyper_2_11_chunk._ts_meta_max_1, compress_hyper_2_11_chunk."time", compress_hyper_2_11_chunk.int_value, compress_hyper_2_11_chunk.float_value - -> Partial HashAggregate - Output: _hyper_1_2_chunk.int_value, _hyper_1_2_chunk.float_value, PARTIAL sum(_hyper_1_2_chunk.segment_by_value) - Group Key: _hyper_1_2_chunk.int_value, _hyper_1_2_chunk.float_value + -> Custom Scan (VectorAgg) + Output: _hyper_1_2_chunk.int_value, _hyper_1_2_chunk.float_value, (PARTIAL sum(_hyper_1_2_chunk.segment_by_value)) + Grouping Policy: hashed with serialized key -> Custom Scan (DecompressChunk) on _timescaledb_internal._hyper_1_2_chunk Output: _hyper_1_2_chunk.int_value, _hyper_1_2_chunk.float_value, _hyper_1_2_chunk.segment_by_value -> Parallel Seq Scan on _timescaledb_internal.compress_hyper_2_12_chunk Output: compress_hyper_2_12_chunk._ts_meta_count, compress_hyper_2_12_chunk.segment_by_value, compress_hyper_2_12_chunk._ts_meta_min_1, compress_hyper_2_12_chunk._ts_meta_max_1, compress_hyper_2_12_chunk."time", compress_hyper_2_12_chunk.int_value, compress_hyper_2_12_chunk.float_value - -> Partial HashAggregate - Output: _hyper_1_3_chunk.int_value, _hyper_1_3_chunk.float_value, PARTIAL sum(_hyper_1_3_chunk.segment_by_value) - Group Key: _hyper_1_3_chunk.int_value, _hyper_1_3_chunk.float_value + -> Custom Scan (VectorAgg) + Output: _hyper_1_3_chunk.int_value, _hyper_1_3_chunk.float_value, (PARTIAL sum(_hyper_1_3_chunk.segment_by_value)) + Grouping Policy: hashed with serialized key -> Custom Scan (DecompressChunk) on _timescaledb_internal._hyper_1_3_chunk Output: _hyper_1_3_chunk.int_value, _hyper_1_3_chunk.float_value, _hyper_1_3_chunk.segment_by_value -> Parallel Seq Scan on _timescaledb_internal.compress_hyper_2_13_chunk diff --git a/tsl/test/sql/CMakeLists.txt b/tsl/test/sql/CMakeLists.txt index 06b1c6ac9c5..b8171223e03 100644 --- a/tsl/test/sql/CMakeLists.txt +++ b/tsl/test/sql/CMakeLists.txt @@ -121,6 +121,7 @@ if(CMAKE_BUILD_TYPE MATCHES Debug) feature_flags.sql vector_agg_default.sql vector_agg_filter.sql + vector_agg_grouping.sql vector_agg_text.sql vector_agg_memory.sql vector_agg_segmentby.sql) diff --git a/tsl/test/sql/hypercore_vectoragg.sql b/tsl/test/sql/hypercore_vectoragg.sql index 6bf52a6e9cc..c75fe32a2af 100644 --- a/tsl/test/sql/hypercore_vectoragg.sql +++ b/tsl/test/sql/hypercore_vectoragg.sql @@ -110,16 +110,12 @@ select location, count(*) from aggdata where location=1 group by location; -- -- Test ordering/grouping on segmentby, orderby columns -- --- This grouping is currently NOT supported by VectorAgg --- set timescaledb.enable_vectorized_aggregation=true; explain (verbose, costs off) select time, device, sum(temp) from aggdata where device is not null group by time, device order by time, device limit 10; -set timescaledb.debug_require_vector_agg to 'forbid'; select time, device, sum(temp) from aggdata where device is not null group by time, device order by time, device limit 10; set timecaledb.enable_vectorized_aggregation=false; -reset timescaledb.debug_require_vector_agg; explain (verbose, costs off) select time, device, sum(temp) from aggdata where device is not null group by time, device order by time, device limit 10; select time, device, sum(temp) from aggdata where device is not null group by time, device order by time, device limit 10; @@ -127,11 +123,9 @@ select time, device, sum(temp) from aggdata where device is not null group by ti set timescaledb.enable_vectorized_aggregation=true; explain (verbose, costs off) select time, device, sum(temp) filter (where device is not null) from aggdata group by time, device order by time, device desc limit 10; -set timescaledb.debug_require_vector_agg to 'forbid'; select time, device, sum(temp) filter (where device is not null) from aggdata group by time, device order by time, device desc limit 10; set timescaledb.enable_vectorized_aggregation=false; -reset timescaledb.debug_require_vector_agg; explain (verbose, costs off) select time, device, sum(temp) filter (where device is not null) from aggdata group by time, device order by time, device desc limit 10; select time, device, sum(temp) filter (where device is not null) from aggdata group by time, device order by time, device desc limit 10; diff --git a/tsl/test/sql/vector_agg_grouping.sql b/tsl/test/sql/vector_agg_grouping.sql new file mode 100644 index 00000000000..5368e64d69a --- /dev/null +++ b/tsl/test/sql/vector_agg_grouping.sql @@ -0,0 +1,164 @@ +-- This file and its contents are licensed under the Timescale License. +-- Please see the included NOTICE for copyright information and +-- LICENSE-TIMESCALE for a copy of the license. + +\c :TEST_DBNAME :ROLE_SUPERUSER +-- helper function: float -> pseudorandom float [-0.5..0.5] +CREATE OR REPLACE FUNCTION mix(x anyelement) RETURNS float8 AS $$ + SELECT hashfloat8(x::float8) / pow(2, 32) +$$ LANGUAGE SQL; + +\set CHUNKS 2::int +\set CHUNK_ROWS 100000::int +\set GROUPING_CARDINALITY 10::int + +create table agggroup(t int, s int, + cint2 int2, cint4 int4, cint8 int8); +select create_hypertable('agggroup', 's', chunk_time_interval => :GROUPING_CARDINALITY / :CHUNKS); + +create view source as +select s * 10000 + t as t, + s, + case when t % 1051 = 0 then null + else (mix(s + t * 1019) * 32767)::int2 end as cint2, + (mix(s + t * 1021) * 32767)::int4 as cint4, + (mix(s + t * 1031) * 32767)::int8 as cint8 +from + generate_series(1::int, :CHUNK_ROWS * :CHUNKS / :GROUPING_CARDINALITY) t, + generate_series(0::int, :GROUPING_CARDINALITY - 1::int) s(s) +; + +insert into agggroup select * from source where s = 1; + +alter table agggroup set (timescaledb.compress, timescaledb.compress_orderby = 't', + timescaledb.compress_segmentby = 's'); + +select count(compress_chunk(x)) from show_chunks('agggroup') x; + +alter table agggroup add column ss int default 11; +alter table agggroup add column x text default '11'; + +insert into agggroup +select *, ss::text as x from ( + select *, + case + -- null in entire batch + when s = 2 then null + -- null for some rows + when s = 3 and t % 1051 = 0 then null + -- for some rows same as default + when s = 4 and t % 1057 = 0 then 11 + -- not null for entire batch + else s + end as ss + from source where s != 1 +) t +; +select count(compress_chunk(x)) from show_chunks('agggroup') x; +vacuum freeze analyze agggroup; + + +set timescaledb.debug_require_vector_agg = 'require'; +---- Uncomment to generate reference. Note that there are minor discrepancies +---- on float4 due to different numeric stability in our and PG implementations. +--set timescaledb.enable_vectorized_aggregation to off; set timescaledb.debug_require_vector_agg = 'allow'; + +select + format('%sselect %s%s(%s) from agggroup%s%s%s;', + explain, + grouping || ', ', + function, variable, + ' where ' || condition, + ' group by ' || grouping, + format(' order by %s(%s), ', function, variable) || grouping || ' limit 10', + function, variable) +from + unnest(array[ + 'explain (costs off) ', + null]) explain, + unnest(array[ + 'cint2', + '*']) variable, + unnest(array[ + 'min', + 'count']) function, + unnest(array[ + null, + 'cint2 > 0', + 'cint2 is null', + 'cint2 is null and x is null']) with ordinality as condition(condition, n), + unnest(array[ + null, + 'cint2', + 'cint4', + 'cint4, cint8', + 'cint8', + 's, cint2', + 's, ss', + 's, x', + 'ss, cint2, x', + 'ss, s', + 'ss, x, cint2', + 't, s, ss, x, cint4, cint8, cint2', + 'x']) with ordinality as grouping(grouping, n) +where + true + and (explain is null /* or condition is null and grouping = 's' */) + and (variable != '*' or function = 'count') +order by explain, condition.n, variable, function, grouping.n +\gexec + +reset timescaledb.debug_require_vector_agg; + + +-- Test long text columns. Also make one of them a segmentby, so that we can +-- test the long scalar values. +create table long(t int, a text, b text, c text, d text); +select create_hypertable('long', 't'); +insert into long select n, a, x, x, x from ( + select n, 'short' || m a, repeat('1', 100 * 4 + n) x + from generate_series(1, 4) n, + generate_series(1, 4) m) t +; +insert into long values (-1, 'a', 'b', 'c', 'd'); +insert into long values (-2, repeat('long', 1000), 'b', 'c', 'd'); +alter table long set (timescaledb.compress, timescaledb.compress_segmentby = 'a', + timescaledb.compress_orderby = 't desc'); +select count(compress_chunk(x)) from show_chunks('long') x; + +set timescaledb.debug_require_vector_agg = 'require'; +---- Uncomment to generate reference. +--set timescaledb.enable_vectorized_aggregation to off; set timescaledb.debug_require_vector_agg = 'allow'; + +-- Various placements of long scalar column +select sum(t) from long group by a, b, c, d order by 1 limit 10; +select sum(t) from long group by d, b, c, a order by 1 limit 10; +select sum(t) from long group by d, b, a, c order by 1 limit 10; + +-- Just the scalar column +select sum(t) from long group by a order by 1; + +-- No scalar columns +select sum(t) from long group by b, c, d order by 1 limit 10; + +reset timescaledb.debug_require_vector_agg; + + +-- Test various serialized key lengths. We want to touch the transition from short +-- to long varlena header for the serialized key. +create table keylength(t int, a text, b text); +select create_hypertable('keylength', 't'); +insert into keylength select t, 'a', repeat('b', t) from generate_series(1, 1000) t; +insert into keylength values (-1, '', ''); -- second chunk +alter table keylength set (timescaledb.compress, timescaledb.compress_segmentby = '', + timescaledb.compress_orderby = 't desc'); +select count(compress_chunk(x)) from show_chunks('keylength') x; + +set timescaledb.debug_require_vector_agg = 'require'; +---- Uncomment to generate reference. +--set timescaledb.enable_vectorized_aggregation to off; set timescaledb.debug_require_vector_agg = 'allow'; + +select sum(t) from keylength group by a, b order by 1 desc limit 10; +select sum(t) from keylength group by b, a order by 1 desc limit 10; + +reset timescaledb.debug_require_vector_agg; diff --git a/tsl/test/sql/vectorized_aggregation.sql b/tsl/test/sql/vectorized_aggregation.sql index e4388a60fe7..15336d16f95 100644 --- a/tsl/test/sql/vectorized_aggregation.sql +++ b/tsl/test/sql/vectorized_aggregation.sql @@ -65,7 +65,7 @@ SELECT sum(segment_by_value) FROM testtable GROUP BY float_value; :EXPLAIN SELECT sum(segment_by_value) FROM testtable GROUP BY int_value; --- Vectorization not possible with grouping by multiple columns +-- Vectorization possible with grouping by multiple columns :EXPLAIN SELECT sum(segment_by_value) FROM testtable GROUP BY int_value, float_value;