提交 1a2a02a6 编写于 作者: H Heikki Linnakangas

Remove CaQL.

The previous commits have removed all usage of CaQL. It's no longer
needed.
上级 5ae8d6d1
......@@ -26,7 +26,7 @@ OBJS = catalog.o dependency.o heap.o index.o indexing.o namespace.o aclchk.o \
aocatalog.o $(QUICKLZ_COMPRESSION)
SUBDIRS = caql core
SUBDIRS = core
BKIFILES = postgres.bki postgres.description postgres.shdescription
......
subdir = src/backend/catalog/caql
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
OBJS = caqlanalyze.o catquery.o gram.o
FLEXFLAGS = -CF
include $(top_srcdir)/src/backend/common.mk
ifneq ($(findstring -logquery, $(caql_logquery_FLAGS)),)
override CPPFLAGS := $(CPPFLAGS) -DCAQL_LOGQUERY
endif
gram.o: $(srcdir)/scan.c
$(srcdir)/gram.h: $(srcdir)/gram.c ;
$(srcdir)/gram.c: gram.y
ifdef BISON
$(BISON) -d $(BISONFLAGS) -o $@ $<
else
@$(missing) bison $< $@
endif
$(srcdir)/scan.c: scan.l
ifdef FLEX
$(FLEX) $(FLEXFLAGS) -o'$@' $<
# This is due to flex bug around unused variable yyg in <= 2.5.35
$(PERL) -pi'' -e 's/^.* depending upon options\. \*\/$$/$$& \n\n(void) yyg;/' $@
else
@$(missing) flex $< $@
endif
caql_locktest_FLAGS := -lockcheck -readlock \
-holdlock \
-logquery \
-lwl=pg_type,pg_proc,pg_namespace,pg_operator,pg_opclass,pg_amop,pg_am,pg_amproc
# NOTE:
# to turn on caql query logging, do "make caql_logquery_FLAGS=-logquery"
# to filter duplicate caql callers, do "make caql_logquery_FLAGS=-logquery_hash"
test:
echo $(caql_logquery_FLAGS)
echo $(caql_locktest_FLAGS)
clean:
rm -f SUBSYS.o $(OBJS) gram.c gram.h scan.c scan.c
/*-------------------------------------------------------------------------
*
* caqlanalyze.c
* parser analyzer for CaQL.
*
* The analysis should be quick. We try not to use palloc or heavy process
* in this file.
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/hash.h"
#include "access/heapam.h"
#include "catalog/catcore.h"
#include "catalog/catquery.h"
#include "catalog/caqlparse.h"
#include "cdb/cdbvars.h"
#include "cdb/cdbpersistentstore.h"
#include "utils/array.h"
#include "utils/inval.h"
#include "utils/memutils.h"
#include "utils/syscache.h"
/*
* Settings of parser cache. NUM is the total element length and
* PER_BUCKET is elements per bucket. Because we use division, make sure
* NUM / PER_BUCKET is divisible.
*/
#define CAQL_PARSER_CACHE_NUM 256
#define CAQL_PARSER_CACHE_PER_BUCKET 8
#define CAQL_PARSER_CACHE_BUCKET \
(CAQL_PARSER_CACHE_NUM / CAQL_PARSER_CACHE_PER_BUCKET)
/*
* The cache information. We store flat cookie in an entry
* so that we can avoid additional memory allocation.
*/
typedef struct caql_parser_cache
{
uint32 hval; /* hash value */
MemoryContext context; /* private memory context */
caql_hash_cookie cookie; /* body */
} caql_parser_cache;
/* The parser cache array. */
static caql_parser_cache parser_cache[CAQL_PARSER_CACHE_NUM];
/* Prototypes */
static SysScanDesc caql_basic_fn_all(caql_hash_cookie *pchn,
cqContext *pCtx, cq_list *pcql, bool bLockEntireTable);
static List *caql_query_predicate(Node *query);
static bool caql_process_predicates(caql_hash_cookie *pchn,
List *predicates);
static bool caql_lookup_predicate(caql_hash_cookie *pchn, CaQLExpr *expr,
AttrNumber *attnum, StrategyNumber *strategy, Oid *fnoid,
Oid *typid);
static const CatCoreIndex *caql_find_index(caql_hash_cookie *pchn,
Node *query);
static const CatCoreIndex *caql_find_index_by(caql_hash_cookie *pchn,
List *keylist, bool is_orderby);
static caql_hash_cookie *caql_get_parser_cache(const char *str, unsigned int len);
static void caql_put_parser_cache(const char *str, unsigned int len,
caql_hash_cookie *cookie);
/*
* Preprocess the query string and build up hash_cookie, which will be
* passed to caql_switch later.
*/
struct caql_hash_cookie *
cq_lookup(const char *str, unsigned int len, cq_list *pcql)
{
Node *query;
struct caql_hash_cookie *hash_cookie;
hash_cookie = caql_get_parser_cache(str, len);
if (hash_cookie != NULL)
return hash_cookie;
query = caql_raw_parser(str, (const char *) pcql->filename, pcql->lineno);
if (query == NULL)
return NULL;
hash_cookie = palloc0(sizeof(struct caql_hash_cookie));
switch(nodeTag(query))
{
case T_CaQLSelect:
{
CaQLSelect *node = (CaQLSelect *) query;
char *attname;
if (node->forupdate)
hash_cookie->bUpdate = true;
hash_cookie->relation = catcore_lookup_rel(node->from);
if (hash_cookie->relation == NULL)
elog(ERROR, "could not find relation \"%s\" in %s at %s:%d",
node->from, str, pcql->filename, pcql->lineno);
attname = strVal(linitial(node->targetlist));
/*
* Look up attribute number if target list has a column.
* '*' includes count('*'). The first character test is
* not wrong due to the syntax limitation, and this is quick.
*/
if (attname[0] != '*')
{
hash_cookie->attnum =
catcore_lookup_attnum(hash_cookie->relation, attname,
&hash_cookie->atttype);
if (hash_cookie->attnum == InvalidAttrNumber)
elog(ERROR, "could not find attribute \"%s\" in %s at %s:%d",
attname, str, pcql->filename, pcql->lineno);
}
hash_cookie->bAllEqual =
caql_process_predicates(hash_cookie, node->where);
}
break;
default:
return NULL;
}
hash_cookie->name = str;
hash_cookie->query = query;
hash_cookie->file = (char *) pcql->filename;
hash_cookie->lineno = pcql->lineno;
/* Find an available index based on predicates or ORDER BY */
hash_cookie->index = caql_find_index(hash_cookie, query);
if (hash_cookie->index != NULL)
hash_cookie->syscacheid = GetSysCacheId(hash_cookie->index->indexoid);
else
hash_cookie->syscacheid = -1;
caql_put_parser_cache(str, len, hash_cookie);
return hash_cookie;
}
/*
* This makes decisions wheather we should use syscache, index scan, or
* heap scan, based on the query and the settings. Then it opens and
* initializes relation and scan.
*
* Note: it frees pcql.
*/
cqContext *
caql_switch(struct caql_hash_cookie *pchn,
cqContext *pCtx,
cq_list *pcql)
{
int i;
Assert(pCtx);
/* set the snapshot and lockmodes */
if (!pCtx->cq_setsnapshot)
pCtx->cq_snapshot = SnapshotNow;
if (pchn->bUpdate)
pCtx->cq_lockmode = RowExclusiveLock;
else
pCtx->cq_lockmode = AccessShareLock;
/* get everything we need from cql */
for (i = 0; i < pcql->maxkeys; i++)
{
pCtx->cq_datumKeys[i] = pcql->cqlKeys[i];
}
pCtx->cq_NumKeys = pcql->maxkeys;
pCtx->cq_cacheKeys = &pCtx->cq_datumKeys[0];
pCtx->relation = pchn->relation;
pCtx->index = pchn->index;
#ifdef CAQL_LOGQUERY
caql_logquery("caql_basic_fn_000", pcql->filename, pcql->lineno,
pCtx->cq_uniqquery_code, DatumGetObjectId(pcql->cqlKeys[0]));
#endif /* CAQL_LOGQUERY */
pCtx->cq_sysScan = caql_basic_fn_all(pchn, pCtx, pcql, false);
/* NOTE: free up the cql before we return */
pcql->bGood = false;
pfree(pcql);
return pCtx;
}
/*
* Workhorse for caql_switch.
*/
static SysScanDesc
caql_basic_fn_all(caql_hash_cookie *pchn, cqContext *pCtx,
cq_list *pcql, bool bLockEntireTable)
{
const CatCoreIndex *index = pchn->index;
SysScanDesc desc;
Oid scanIndexId = InvalidOid; /* index id */
int i, numScanKeys;
List *predicates;
ListCell *l;
pCtx->cq_relationId = pchn->relation->relid;
predicates = caql_query_predicate(pchn->query);
numScanKeys = list_length(predicates);
Assert(numScanKeys <= MAX_SCAN_NUM);
/*
* Use the syscache if available and unless the caller states otherwise.
*/
if (index && pchn->syscacheid >= 0)
{
int nkeys = index->nkeys;
pCtx->cq_usesyscache = true;
/*
* Must match all columns of the index to use syscache.
*/
if (pCtx->cq_NumKeys != nkeys)
pCtx->cq_usesyscache = false;
/*
* If all predicates are not equals, syscache is not used.
*/
if (!pchn->bAllEqual)
pCtx->cq_usesyscache = false;
/* ARD-28, MPP-16119: can only use SnapshowNow with syscache */
if (pCtx->cq_snapshot != SnapshotNow)
{
/* Complain in debug, but behave in production */
Assert(!pCtx->cq_usesyscache);
pCtx->cq_usesyscache = true;
}
if (pCtx->cq_usesyscache)
{
pCtx->cq_cacheId = pchn->syscacheid;
/* number of keys must match */
Assert(pCtx->cq_NumKeys == nkeys);
}
}
/*
* Use index scan if available and unless the caller states otherwise.
*/
if (index)
{
scanIndexId = index->indexoid;
pCtx->cq_useidxOK = true;
}
/*
* If we don't have index id, don't use index scan.
*/
if (!OidIsValid(scanIndexId))
{
/*
* Complain in debug, but behave in production.
* (possibly the caller made wrong decision)
*/
Assert(!pCtx->cq_useidxOK);
pCtx->cq_useidxOK = false;
}
/*
* If relation is not supplied by the caller, open it here.
*/
if (!pCtx->cq_externrel)
{
if (pCtx->cq_usesyscache &&
(AccessShareLock == pCtx->cq_lockmode))
{
pCtx->cq_externrel = true; /* pretend we have external relation */
pCtx->cq_heap_rel = InvalidRelation;
return NULL;
}
else
{
pCtx->cq_heap_rel = heap_open(pCtx->cq_relationId,
pCtx->cq_lockmode);
pCtx->cq_tupdesc = RelationGetDescr(pCtx->cq_heap_rel);
}
}
else
{
/* make sure the supplied relation matches the caql */
if (RelationIsValid(pCtx->cq_heap_rel))
{
Assert(pchn->relation->relid == RelationGetRelid(pCtx->cq_heap_rel));
pCtx->cq_tupdesc = RelationGetDescr(pCtx->cq_heap_rel);
}
}
/*
* If we're using syscache, we don't need to begin scan.
*/
if (pCtx->cq_usesyscache)
{
Assert(0 <= pCtx->cq_cacheId);
return NULL;
}
/*
* Otherwise, begin scan. Initialize scan keys with given parameters.
*/
foreach_with_count (l, predicates, i)
{
CaQLExpr *expr = (CaQLExpr *) lfirst(l);
int paramno;
paramno = expr->right;
ScanKeyInit(&pCtx->cq_scanKeys[i],
expr->attnum, expr->strategy, expr->fnoid,
pCtx->cq_datumKeys[paramno - 1]);
}
desc = systable_beginscan(pCtx->cq_heap_rel,
scanIndexId,
pCtx->cq_useidxOK,
pCtx->cq_snapshot, numScanKeys, pCtx->cq_scanKeys);
return desc;
}
/*
* Returns where clause if it's SELECT or DELETE
*/
static List *
caql_query_predicate(Node *query)
{
switch(nodeTag(query))
{
case T_CaQLSelect:
return ((CaQLSelect *) query)->where;
default:
elog(ERROR, "unexpected node type(%d)", nodeTag(query));
}
return NIL;
}
/*
* Preprocesses list of CaQLExpr and finds attribute number and operator
* information based on the attribute type. Returns true if all predicate
* operators are equality comparisons.
*/
static bool
caql_process_predicates(struct caql_hash_cookie *pchn, List *predicates)
{
ListCell *l;
bool all_equal = true;
foreach (l, predicates)
{
CaQLExpr *expr = (CaQLExpr *) lfirst(l);
all_equal &= caql_lookup_predicate(pchn, expr,
&expr->attnum,
&expr->strategy,
&expr->fnoid,
&expr->typid);
}
return all_equal;
}
/*
* Extracts attribute number, strategy number and function oid
* from a predicate expression. Returns true if the expression
* is an equality comparison.
*/
static bool
caql_lookup_predicate(struct caql_hash_cookie *pchn, CaQLExpr *expr,
AttrNumber *attnum, StrategyNumber *strategy, Oid *fnoid,
Oid *typid)
{
const CatCoreRelation *relation = pchn->relation;
const CatCoreAttr *attr;
const CatCoreType *typ;
char *attname = expr->left;
if (expr->right < 1 || expr->right > 5)
elog(ERROR, "invalid parameter number(%d) in %s",
expr->right, pchn->name);
attr = catcore_lookup_attr(relation, attname);
if (!attr)
elog(ERROR, "could not find attribute \"%s\" in %s",
attname, pchn->name);
*attnum = attr->attnum;
typ = attr->atttyp;
*typid = typ->typid;
if (strcmp(expr->op, "=") == 0)
{
*strategy = BTEqualStrategyNumber;
*fnoid = typ->eqfunc;
}
else if (strcmp(expr->op, "<") == 0)
{
*strategy = BTLessStrategyNumber;
*fnoid = typ->ltfunc;
}
else if (strcmp(expr->op, "<=") == 0)
{
*strategy = BTLessEqualStrategyNumber;
*fnoid = typ->lefunc;
}
else if (strcmp(expr->op, ">=") == 0)
{
*strategy = BTGreaterEqualStrategyNumber;
*fnoid = typ->gefunc;
}
else if (strcmp(expr->op, ">") == 0)
{
*strategy = BTGreaterStrategyNumber;
*fnoid = typ->gtfunc;
}
else
elog(ERROR, "could not find operator \"%s\" in %s",
expr->op, pchn->name);
if (*fnoid == InvalidOid)
elog(ERROR, "could not find btree operator for type \"%d\" in %s",
typ->typid, pchn->name);
return *strategy == BTEqualStrategyNumber;
}
/*
* Finds a usable index based on WHERE and ORDER BY clause.
*/
static const CatCoreIndex *
caql_find_index(caql_hash_cookie *pchn, Node *query)
{
const CatCoreIndex *index = NULL;
const CatCoreRelation *relation = pchn->relation;
List *predicates;
if (relation->nindexes == 0)
return NULL;
predicates = caql_query_predicate(query);
index = caql_find_index_by(pchn, predicates, false);
if (index)
return index;
if (IsA(query, CaQLSelect))
return caql_find_index_by(pchn, ((CaQLSelect *) query)->orderby, true);
return NULL;
}
/*
* Finds a usable index based on the clause. If is_orderby is true,
* it processes ORDER BY, otherwise WHERE.
*/
static const CatCoreIndex *
caql_find_index_by(caql_hash_cookie *pchn, List *keylist, bool is_orderby)
{
const CatCoreRelation *relation = pchn->relation;
const CatCoreIndex *indexes;
int i;
if (list_length(keylist) == 0 || list_length(keylist) > MAX_SCAN_NUM)
return NULL;
indexes = relation->indexes;
for (i = 0; i < relation->nindexes; i++)
{
ListCell *l;
const CatCoreIndex *index;
int j;
bool match = true;
index = &indexes[i];
foreach_with_count (l, keylist, j)
{
if (is_orderby)
{
char *sortcol = (char *) strVal(lfirst(l));
const CatCoreAttr *attr;
/*
* This is the only place to look up ORDER BY at the moment,
* but if we do it elsewhere we'd probably better speed things
* up by preprocess.
*/
attr = catcore_lookup_attr(relation, sortcol);
if (!attr)
elog(ERROR, "could not find attribute \"%s\" in %s",
sortcol, pchn->name);
if (attr->attnum != index->attnums[j])
{
match = false;
break;
}
}
else
{
CaQLExpr *expr = (CaQLExpr *) lfirst(l);
/*
* Index is usable only if attributes match in the same order.
* Because we only support btree operators, we are sure
* this is enough.
*/
if (expr->attnum != index->attnums[j])
{
match = false;
break;
}
}
}
/* found */
if (match)
return index;
}
return NULL;
}
/*
* Finds the pre-parsed cookie by the query string.
*/
static caql_hash_cookie *
caql_get_parser_cache(const char *str, unsigned int len)
{
uint32 hval;
int bucket_num, i;
caql_parser_cache *bucket;
/*
* Identify the bucket.
*/
hval = (uint32) hash_any((const unsigned char *) str, len);
bucket_num = hval % CAQL_PARSER_CACHE_BUCKET;
bucket = &parser_cache[bucket_num * CAQL_PARSER_CACHE_PER_BUCKET];
for (i = 0; i < CAQL_PARSER_CACHE_PER_BUCKET; i++)
{
caql_parser_cache *entry = &bucket[i];
/*
* Check the hash value first to speed up the comparison.
*/
if (entry->hval == hval && entry->context &&
entry->cookie.name && strcmp(entry->cookie.name, str) == 0)
return &entry->cookie;
}
return NULL;
}
/*
* Stores the parser cache entry to persist in cache memory context.
* relation and index are not copied because they are in the static memory.
* We create a small memory context for this cache, so that we can free the
* copied parse tree easily.
* We carefully set cache->context at the end, to deal with the out-of-memory
* situation during the copy. Otherwise, the cache entry will be used wrongly
* as it is half-backed.
*/
static void
copy_caql_parser_cache(caql_parser_cache *cache, int hval,
caql_hash_cookie *cookie)
{
MemoryContext mycontext, oldcontext;
cache->hval = hval;
memcpy(&cache->cookie, cookie, sizeof(caql_hash_cookie));
mycontext = AllocSetContextCreate(CacheMemoryContext,
cache->cookie.name,
ALLOCSET_SMALL_MINSIZE,
ALLOCSET_SMALL_INITSIZE,
ALLOCSET_SMALL_MAXSIZE);
/*
* We need to copy each pointer.
*/
oldcontext = MemoryContextSwitchTo(mycontext);
cache->cookie.name = pstrdup(cache->cookie.name);
cache->cookie.query = copyObject(cache->cookie.query);
if (cache->cookie.file)
cache->cookie.file = pstrdup(cache->cookie.file);
MemoryContextSwitchTo(oldcontext);
/* This tells this entry is ready */
cache->context = mycontext;
}
/*
* Frees the parser cache entry. Because everything is stored in the private
* context, it is only to delete that. Make sure all the pointer entries are
* set NULL.
*/
static void
free_caql_parser_cache(caql_parser_cache *cache)
{
MemoryContext mycontext;
mycontext = cache->context;
/* This tells that this entry is not valid anymore */
cache->context = NULL;
if (mycontext)
MemoryContextDelete(mycontext);
cache->cookie.name = NULL;
cache->cookie.query = NULL;
cache->cookie.file = NULL;
}
/*
* Save the cookie in the cache. The cache strategy is to split an array
* into several buckets and to balance inputs by hash value modulo. Once
* a bucket becomes full, we remove the first entry and slide others by one.
* This design assumes each bucket is small enough to move things around
* very quickly. By allocating enough buckets, we don't expect so many
* cache misses in normal use cases.
*/
static void
caql_put_parser_cache(const char *str, unsigned int len,
caql_hash_cookie *cookie)
{
uint32 hval;
int bucket_num, i;
caql_parser_cache *bucket;
/*
* Identify the bucket.
*/
hval = (uint32) hash_any((const unsigned char *) str, len);
bucket_num = hval % CAQL_PARSER_CACHE_BUCKET;
bucket = &parser_cache[bucket_num * CAQL_PARSER_CACHE_PER_BUCKET];
for (i = 0; i < CAQL_PARSER_CACHE_PER_BUCKET; i++)
{
caql_parser_cache *entry = &bucket[i];
/*
* If the entry is empty, context is NULL.
*/
if (entry->context == NULL)
copy_caql_parser_cache(entry, hval, cookie);
/*
* Return immediately if we already have it in our cache.
* If the context is not ready, it is an invalid cache.
*/
else if (entry->hval == hval && entry->context &&
strcmp(entry->cookie.name, str) == 0)
return;
}
/*
* If the bucket is full, remove the first and append the new.
*/
free_caql_parser_cache(bucket);
memmove(bucket, &bucket[1], sizeof(caql_parser_cache) *
(CAQL_PARSER_CACHE_PER_BUCKET - 1));
copy_caql_parser_cache(&bucket[CAQL_PARSER_CACHE_PER_BUCKET - 1],
hval, cookie);
}
/*-------------------------------------------------------------------------
*
* catquery.c
* general catalog table access methods (internal api)
*
* Copyright (c) 2011,2012,2013 Greenplum inc
*
*
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <string.h>
#include <unistd.h>
#include "catalog/caqlparse.h"
#include "catalog/catalog.h"
#include "catalog/catquery.h"
#include "catalog/indexing.h"
#include "catalog/pg_aggregate.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_auth_members.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_class.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
#include "catalog/pg_depend.h"
#include "catalog/pg_description.h"
#include "catalog/pg_extprotocol.h"
#include "catalog/pg_exttable.h"
#include "catalog/pg_filespace.h"
#include "catalog/pg_filespace_entry.h"
#include "catalog/pg_inherits.h"
#include "catalog/pg_language.h"
#include "catalog/pg_largeobject.h"
#include "catalog/pg_listener.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_partition.h"
#include "catalog/pg_partition_rule.h"
#include "catalog/pg_pltemplate.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_resqueue.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_shdepend.h"
#include "catalog/pg_shdescription.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_window.h"
#include "catalog/gp_configuration.h"
#include "catalog/gp_segment_config.h"
#include "catalog/gp_san_config.h"
#include "catalog/gp_fastsequence.h"
#include "catalog/gp_persistent.h"
#include "catalog/gp_global_sequence.h"
#include "catalog/gp_id.h"
#include "catalog/gp_version.h"
#include "catalog/gp_policy.h"
#include "miscadmin.h"
#include "utils/fmgroids.h"
#include "utils/relcache.h"
#include "utils/lsyscache.h"
#include "utils/syscache.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/inval.h"
#include "cdb/cdbpersistenttablespace.h"
#include "cdb/cdbvars.h"
#define caql_getattr_internal(pCtx, tup, attnum, isnull) \
(((pCtx)->cq_usesyscache) ? \
(SysCacheGetAttr((pCtx)->cq_cacheId, (tup), (attnum), (isnull))) : \
(heap_getattr((tup), (attnum), (pCtx)->cq_tupdesc, (isnull))))
static void
caql_heapclose(cqContext *pCtx)
{
if (!pCtx->cq_externrel)
{
heap_close(pCtx->cq_heap_rel, pCtx->cq_lockmode);
pCtx->cq_heap_rel = InvalidRelation;
}
}
/* ----------------------------------------------------------------
* cqclr
*
* ----------------------------------------------------------------
*/
cqContext *cqclr(cqContext *pCtx)
{
cqClearContext(pCtx);
return (pCtx);
}
/* ----------------------------------------------------------------
* caql_addrel()
*
* Add an existing relation as the heap_rel, and use its lockmode,
* and skip heap_open/heap_close
* ----------------------------------------------------------------
*/
cqContext *caql_addrel(cqContext *pCtx, Relation rel)
{
if (RelationIsValid(rel))
{
Assert(!RelationIsValid(pCtx->cq_heap_rel));
pCtx->cq_heap_rel = rel;
pCtx->cq_externrel = true;
}
return (pCtx);
}
/* ----------------------------------------------------------------
* caql_snapshot()
*
* Change the default snapshot (SnapshotNow) associated with the
* heap/index scan.
* ----------------------------------------------------------------
*/
cqContext *caql_snapshot(cqContext *pCtx, Snapshot ss)
{
pCtx->cq_setsnapshot = true;
pCtx->cq_snapshot = ss;
return (pCtx);
}
/* ----------------------------------------------------------------
* cql1()
* The underlying function for the cql() macro.
* find the bind parameters in a caql string and build a key list
* ----------------------------------------------------------------
*/
cq_list *cql1(const char* caqlStr, const char* filename, int lineno, ...)
{
int maxkeys = 0;
int badbind = 0;
cq_list *pcql = (cq_list *) palloc0(sizeof(cq_list));
const char* pc = caqlStr;
if ((NULL == caqlStr) ||
(0 == strlen(caqlStr)))
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(
"Invalid or undefined CaQL string"
)));
pcql->bGood = true;
pcql->caqlStr = caqlStr;
pcql->filename = filename;
pcql->lineno = lineno;
while (*pc && (maxkeys < 5))
{
if (*pc != ':')
{
pc++;
continue;
}
/* look for numeric bind parameter of the form ":1" to ":5" */
pc++;
if (!*pc)
break;
switch (*pc)
{
case '1':
if (maxkeys != 0)
{
badbind = 1;
goto L_wrong_args;
}
maxkeys++;
break;
case '2':
if (maxkeys != 1)
{
badbind = 2;
goto L_wrong_args;
}
maxkeys++;
break;
case '3':
if (maxkeys != 2)
{
badbind = 3;
goto L_wrong_args;
}
maxkeys++;
break;
case '4':
if (maxkeys != 3)
{
badbind = 4;
goto L_wrong_args;
}
maxkeys++;
break;
case '5':
if (maxkeys != 4)
{
badbind = 5;
goto L_wrong_args;
}
maxkeys++;
break;
case '6':
case '7':
case '8':
case '9':
case '0':
{
badbind = 6;
goto L_wrong_args;
}
default:
break;
} /* end switch */
} /* end while */
pcql->maxkeys = maxkeys;
if (maxkeys)
{
va_list ap;
va_start(ap, lineno);
for (int ii = 0; ii < maxkeys; ii++)
{
pcql->cqlKeys[ii] = va_arg(ap, Datum);
}
va_end(ap);
}
return (pcql);
L_wrong_args:
if (badbind != 5)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(
"bind parameter out of range (1-5)"
)));
else
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg(
"missing bind parameter before :%d, or out of sequence",
badbind
)));
return (NULL);
} /* end cql1 */
/* ----------------------------------------------------------------
* caql_getfirst_only()
* Return a copy the first tuple, pallocd in the current memory context,
* and end the scan. Clients should heap_freetuple() as necessary.
* If pbOnly is not NULL, return TRUE if a second tuple is not found,
* else return FALSE
* NOTE: this function will return NULL if no tuples satisfy the
* caql predicate -- use HeapTupleIsValid() to detect this condition.
* ----------------------------------------------------------------
*/
HeapTuple caql_getfirst_only(cqContext *pCtx0, bool *pbOnly, cq_list *pcql)
{
const char* caql_str = pcql->caqlStr;
const char* filenam = pcql->filename;
int lineno = pcql->lineno;
struct caql_hash_cookie *pchn = cq_lookup(caql_str, strlen(caql_str), pcql);
cqContext *pCtx;
cqContext cqc;
HeapTuple tuple, newTup = NULL;
if (NULL == pchn)
elog(ERROR, "invalid caql string: %s\nfile: %s, line %d",
caql_str, filenam, lineno);
Assert(!pchn->bInsert); /* INSERT not allowed */
/* use the provided context, or provide a clean local ctx */
if (pCtx0)
pCtx = pCtx0;
else
pCtx = cqclr(&cqc);
pCtx = caql_switch(pchn, pCtx, pcql);
/* NOTE: caql_switch frees the pcql */
if (pbOnly) *pbOnly = true;
/* use the SysCache */
if (pCtx->cq_usesyscache)
{
tuple = SearchSysCacheKeyArray(pCtx->cq_cacheId,
pCtx->cq_NumKeys,
pCtx->cq_cacheKeys);
if (HeapTupleIsValid(tuple))
{
newTup = heap_copytuple(tuple);
ReleaseSysCache(tuple);
/* only one */
}
caql_heapclose(pCtx);
pCtx->cq_lasttup = newTup; /* need this for update/delete */
return (newTup);
}
if (HeapTupleIsValid(tuple = systable_getnext(pCtx->cq_sysScan)))
{
/* always copy the tuple, because the endscan releases tup memory */
newTup = heap_copytuple(tuple);
if (pbOnly)
{
*pbOnly =
!(HeapTupleIsValid(systable_getnext(pCtx->cq_sysScan)));
}
}
systable_endscan(pCtx->cq_sysScan);
caql_heapclose(pCtx);
pCtx->cq_lasttup = newTup; /* need this for update/delete */
return (newTup);
}
/* ----------------------------------------------------------------
* caql_beginscan()
* Initialize the scan and open relations/acquire locks as necessary
* ----------------------------------------------------------------
*/
cqContext *caql_beginscan(cqContext *pCtx0, cq_list *pcql)
{
const char* caql_str = pcql->caqlStr;
const char* filenam = pcql->filename;
int lineno = pcql->lineno;
struct caql_hash_cookie *pchn = cq_lookup(caql_str, strlen(caql_str), pcql);
cqContext *pCtx;
/* use the provided context, or *allocate* a clean one */
if (pCtx0)
pCtx = pCtx0;
else
{
pCtx = (cqContext *) palloc0(sizeof(cqContext));
pCtx->cq_free = true; /* free this context in caql_endscan */
}
if (NULL == pchn)
elog(ERROR, "invalid caql string: %s\nfile: %s, line %d",
caql_str, filenam, lineno);
pCtx = caql_switch(pchn, pCtx, pcql);
/* NOTE: caql_switch frees the pcql */
pCtx->cq_bScanBlock = true; /* started a scan block */
pCtx->cq_freeScan = true;
if (pchn->bInsert) /* INSERT allowed, but no subsequent fetches */
{
pCtx->cq_freeScan = false; /* didn't allocate a scanner */
pCtx->cq_EOF = true;
}
return (pCtx);
}
/* ----------------------------------------------------------------
* caql_getnext()
* Return a tuple. The tuple is only valid until caql_endscan(),
* or until the next call of caql_getnext().
* NOTE: this function will return NULL when no tuples remain to
* satisfy the caql predicate -- use HeapTupleIsValid() to detect
* this condition.
* ----------------------------------------------------------------
*/
HeapTuple caql_getnext(cqContext *pCtx)
{
HeapTuple tuple;
/* set EOF when get invalid tuple */
if (pCtx->cq_EOF)
return (NULL);
if (!pCtx->cq_usesyscache)
{
tuple = systable_getnext(pCtx->cq_sysScan);
pCtx->cq_EOF = !(HeapTupleIsValid(tuple));
}
else
{
/* syscache is always 0 or 1 entry */
tuple = SearchSysCacheKeyArray(pCtx->cq_cacheId,
pCtx->cq_NumKeys,
pCtx->cq_cacheKeys);
pCtx->cq_EOF = true; /* at EOF always, because only 0 or 1 */
}
pCtx->cq_lasttup = tuple; /* need this for ReleaseSysCache */
return (tuple);
}
/* ----------------------------------------------------------------
* caql_endscan()
* free all resources associated with the scan, including tuples,
* tables and locks.
* NOTE: this function is *not* a "drop-in" replacement for
* ReleaseSysCache. ReleaseSysCache is only called for valid tuples,
* but you must always call endscan, even if getnext never returned a
* valid tuple.
* ----------------------------------------------------------------
*/
void caql_endscan(cqContext *pCtx)
{
if (pCtx->cq_indstate) /* close the indexes if they were open */
CatalogCloseIndexes(pCtx->cq_indstate);
pCtx->cq_indstate = NULL;
pCtx->cq_bScanBlock = false; /* scan block has ended */
if (pCtx->cq_freeScan)
{
if (!pCtx->cq_usesyscache)
systable_endscan(pCtx->cq_sysScan);
else
{
/* XXX XXX: no need to release if never fetched a valid tuple */
if (HeapTupleIsValid(pCtx->cq_lasttup))
ReleaseSysCache(pCtx->cq_lasttup);
}
}
caql_heapclose(pCtx);
if (pCtx->cq_free) /* free dynamic context */
pfree(pCtx);
pCtx->cq_freeScan = false;
pCtx->cq_free = false;
}
/* ----------------------------------------------------------------
* caql_getattr()
* during beginscan/endscan iteration, get a tuple attribute for
* current tuple
* ----------------------------------------------------------------
*/
Datum caql_getattr(cqContext *pCtx, AttrNumber attnum, bool *isnull)
{
Assert(HeapTupleIsValid(pCtx->cq_lasttup));
return caql_getattr_internal(pCtx, pCtx->cq_lasttup, attnum, isnull);
/*
NOTE: could this be used if caql is extended to support joins, eg
what would attnum be for
"SELECT * FROM pg_resqueue, pg_resqueuecapability ..." ?
Potentially, the attnum is just the ordinal position of the combined
SELECT list, eg you could reference pg_resqueuecapability.restypid
as (Natts_pg_resqueue+Anum_pg_resourcetype_restypid).
*/
}
/* XXX XXX: temp fix for gp_distro reference in getoid_plus */
typedef FormData_gp_policy *Form_gp_distribution_policy;
/* ----------------------------------------------------------------
* caql_getoid_plus()
* Return an oid column from the first tuple and end the scan.
* Note: this works for regproc columns as well, but you should cast
* the output as RegProcedure.
* ----------------------------------------------------------------
*/
Oid caql_getoid_plus(cqContext *pCtx0, int *pFetchcount,
bool *pbIsNull, cq_list *pcql)
{
const char *caql_str = pcql->caqlStr;
const char *filenam = pcql->filename;
int lineno = pcql->lineno;
struct caql_hash_cookie *pchn = cq_lookup(caql_str, strlen(caql_str), pcql);
cqContext *pCtx;
cqContext cqc;
HeapTuple tuple;
Oid result = InvalidOid;
if (NULL == pchn)
elog(ERROR, "invalid caql string: %s\nfile: %s, line %d",
caql_str, filenam, lineno);
Assert(!pchn->bInsert); /* INSERT not allowed */
/* use the provided context, or provide a clean local ctx */
if (pCtx0)
pCtx = pCtx0;
else
pCtx = cqclr(&cqc);
pCtx = caql_switch(pchn, pCtx, pcql);
/* NOTE: caql_switch frees the pcql */
if (pFetchcount)
*pFetchcount = 0;
if (pbIsNull)
*pbIsNull = true;
/* use the SysCache */
if (pCtx->cq_usesyscache)
{
tuple = SearchSysCacheKeyArray(pCtx->cq_cacheId,
pCtx->cq_NumKeys,
pCtx->cq_cacheKeys);
}
else
{
tuple = systable_getnext(pCtx->cq_sysScan);
}
if (HeapTupleIsValid(tuple))
{
if (pFetchcount)
*pFetchcount = 1;
/* if attnum not set, (InvalidAttrNumber == 0)
* use tuple oid, else extract oid from column
* (includes ObjectIdAttributeNumber == -2)
*/
if (pchn->attnum <= InvalidAttrNumber)
{
if (pbIsNull)
*pbIsNull = false;
result = HeapTupleGetOid(tuple);
}
else /* find oid column */
{
bool isnull;
Datum d = caql_getattr_internal(pCtx, tuple, pchn->attnum,
&isnull);
if (!isnull)
{
switch (pchn->atttype)
{
case OIDOID:
case REGPROCOID:
result = DatumGetObjectId(d);
break;
default:
elog(ERROR, "column not an oid: %s\nfile: %s, line %d",
caql_str, filenam, lineno);
}
}
if (pbIsNull)
*pbIsNull = isnull;
}
} /* end HeapTupleIsValid */
if (pCtx->cq_usesyscache)
{
if (HeapTupleIsValid(tuple))
ReleaseSysCache(tuple);
}
else
{
if (pFetchcount && HeapTupleIsValid(tuple))
{
if (HeapTupleIsValid(systable_getnext(pCtx->cq_sysScan)))
{
*pFetchcount = 2;
}
}
systable_endscan(pCtx->cq_sysScan);
}
caql_heapclose(pCtx);
return (result);
} /* end caql_getoid_plus */
/* ----------------------------------------------------------------
* caql_getoid_only()
* Return the oid of the first tuple and end the scan
* If pbOnly is not NULL, return TRUE if a second tuple is not found,
* else return FALSE
* ----------------------------------------------------------------
*/
Oid caql_getoid_only(cqContext *pCtx0, bool *pbOnly, cq_list *pcql)
{
const char *caql_str = pcql->caqlStr;
const char *filenam = pcql->filename;
int lineno = pcql->lineno;
struct caql_hash_cookie *pchn = cq_lookup(caql_str, strlen(caql_str), pcql);
cqContext *pCtx;
cqContext cqc;
HeapTuple tuple;
Oid result = InvalidOid;
if (NULL == pchn)
elog(ERROR, "invalid caql string: %s\nfile: %s, line %d",
caql_str, filenam, lineno);
Assert(!pchn->bInsert); /* INSERT not allowed */
/* use the provided context, or provide a clean local ctx */
if (pCtx0)
pCtx = pCtx0;
else
pCtx = cqclr(&cqc);
pCtx = caql_switch(pchn, pCtx, pcql);
/* NOTE: caql_switch frees the pcql */
if (pbOnly)
*pbOnly = true;
/* use the SysCache */
if (pCtx->cq_usesyscache)
{
tuple = SearchSysCacheKeyArray(pCtx->cq_cacheId,
pCtx->cq_NumKeys,
pCtx->cq_cacheKeys);
if (HeapTupleIsValid(tuple))
{
result = HeapTupleGetOid(tuple);
ReleaseSysCache(tuple);
/* only one */
}
caql_heapclose(pCtx);
return (result);
}
if (HeapTupleIsValid(tuple = systable_getnext(pCtx->cq_sysScan)))
{
result = HeapTupleGetOid(tuple);
if (pbOnly)
{
*pbOnly =
!(HeapTupleIsValid(tuple =
systable_getnext(pCtx->cq_sysScan)));
}
}
systable_endscan(pCtx->cq_sysScan);
caql_heapclose(pCtx);
return (result);
}
/* ----------------------------------------------------------------
* caql_getcstring_plus()
* Return a cstring column from the first tuple and end the scan.
* ----------------------------------------------------------------
*/
char *caql_getcstring_plus(cqContext *pCtx0, int *pFetchcount,
bool *pbIsNull, cq_list *pcql)
{
const char *caql_str = pcql->caqlStr;
const char *filenam = pcql->filename;
int lineno = pcql->lineno;
struct caql_hash_cookie *pchn = cq_lookup(caql_str, strlen(caql_str), pcql);
cqContext *pCtx;
cqContext cqc;
HeapTuple tuple;
char *result = NULL;
if (NULL == pchn)
elog(ERROR, "invalid caql string: %s\nfile: %s, line %d",
caql_str, filenam, lineno);
Assert(!pchn->bInsert); /* INSERT not allowed */
/* use the provided context, or provide a clean local ctx */
if (pCtx0)
pCtx = pCtx0;
else
pCtx = cqclr(&cqc);
pCtx = caql_switch(pchn, pCtx, pcql);
/* NOTE: caql_switch frees the pcql */
if (pFetchcount)
*pFetchcount = 0;
if (pbIsNull)
*pbIsNull = true;
/* use the SysCache */
if (pCtx->cq_usesyscache)
{
tuple = SearchSysCacheKeyArray(pCtx->cq_cacheId,
pCtx->cq_NumKeys,
pCtx->cq_cacheKeys);
}
else
{
tuple = systable_getnext(pCtx->cq_sysScan);
}
if (HeapTupleIsValid(tuple))
{
bool isnull;
Datum d;
if (pFetchcount)
*pFetchcount = 1;
d = caql_getattr_internal(pCtx, tuple, pchn->attnum, &isnull);
if (!isnull)
{
switch (pchn->atttype)
{
case NAMEOID:
result = DatumGetCString(DirectFunctionCall1(nameout, d));
break;
case TEXTOID:
result = DatumGetCString(DirectFunctionCall1(textout, d));
break;
default:
elog(ERROR, "column not a cstring: %s\nfile: %s, line %d",
caql_str, filenam, lineno);
}
}
if (pbIsNull)
*pbIsNull = isnull;
} /* end HeapTupleIsValid */
if (pCtx->cq_usesyscache)
{
if (HeapTupleIsValid(tuple))
ReleaseSysCache(tuple);
}
else
{
if (pFetchcount && HeapTupleIsValid(tuple))
{
if (HeapTupleIsValid(systable_getnext(pCtx->cq_sysScan)))
{
*pFetchcount = 2;
}
}
systable_endscan(pCtx->cq_sysScan);
}
caql_heapclose(pCtx);
return (result);
} /* end caql_getcstring_plus */
void
caql_logquery(const char *funcname, const char *filename, int lineno,
int uniqquery_code, Oid arg1)
{
}
%{
/*-------------------------------------------------------------------------
*
* gram.y
* grammar rules for CaQL
*
* CaQL is a small set of SQL that operates on catalog tables.
*
* We employ reentrant lexer and parser.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "catalog/caqlparse.h"
#include "nodes/nodes.h"
#include "nodes/pg_list.h"
#include "nodes/value.h"
/* include this AFTER caqlparse.h, where YYLTYPE is defined. */
#include "gram.h"
/*
* This tells bison how to pass the scanner state to lexer function (yylex)
*/
#define YYLEX_PARAM yyparser->scanner
#define caql_yyerror(yylloc, yyparser, msg) caql_scanner_yyerror(msg, yyparser->scanner)
/* Location tracking support --- simpler than bison's default */
#define YYLLOC_DEFAULT(Current, Rhs, N) \
do { \
if (N) \
(Current) = (Rhs)[1]; \
else \
(Current) = (Rhs)[0]; \
} while (0)
/*
* Function prototypes from scan.l. We declare them here since they are used
* in this file and we include scan.c at the end. Note YYSTYPE is defined
* in gram.h.
*/
static void caql_scanner_yyerror(const char *message, caql_scanner_t scanner);
static caql_scanner_t caql_scanner_init(const char *str, const char *file, int line);
static void caql_scanner_finish(caql_scanner_t yyscanner);
int caql_yylex(YYSTYPE *lvalp, YYLTYPE *llocp, caql_scanner_t yyscanner);
%}
%expect 0
%pure-parser
%name-prefix="caql_yy"
%parse-param { caql_parser_t yyparser }
%lex-param { caql_scanner_t YYLEX_PARAM }
%locations
%union
{
int ival;
char chr;
char *str;
const char *keyword;
Node *node;
List *list;
}
%type <node> stmtblock stmt SelectStmt SelectForUpdateStmt
%type <list> col_list opt_where opt_orderby expr_list
%type <str> col_elem table_name op_any
%type <node> expr_elem
/*
* All of CaQL keywords are reserved ones.
*/
%token <keyword> AND
BY
FOR
FROM
INTO
IS
ORDER
SELECT
UPDATE
WHERE
%token <str> IDENT FCONST SCONST
%token <ival> ICONST PARAM
%token <str> OP_EQUAL OP_LT OP_LE OP_GE OP_GT
%left AND
%left OP_EQUAL OP_LT OP_LE OP_GE OP_GT
%%
stmtblock: stmt
{
yyparser->parsetree = $1;
}
;
stmt:
SelectStmt
| SelectForUpdateStmt
| /*EMPTY*/
{ $$ = NULL; }
;
SelectStmt:
SELECT col_list FROM table_name opt_where opt_orderby
{
CaQLSelect *n = makeNode(CaQLSelect);
n->targetlist = $2;
n->from = $4;
n->where = $5;
n->orderby = $6;
n->forupdate = false;
$$ = (Node *) n;
}
;
SelectForUpdateStmt:
SELECT col_list FROM table_name opt_where opt_orderby FOR UPDATE
{
CaQLSelect *n = makeNode(CaQLSelect);
n->targetlist = $2;
n->from = $4;
n->where = $5;
n->orderby = $6;
n->forupdate = true;
$$ = (Node *) n;
}
;
col_list:
col_elem
{ $$ = list_make1(makeString($1)); }
| col_list ',' col_elem
{ $$ = lappend($1, makeString($3)); }
| '*'
{ $$ = list_make1(makeString("*")); }
;
col_elem:
IDENT { $$ = $1; }
;
table_name:
IDENT { $$ = $1; }
;
opt_where:
WHERE expr_list
{ $$ = $2; }
| /*EMPTY*/
{ $$ = NIL; }
;
opt_orderby:
ORDER BY col_list
{ $$ = $3; }
| /*EMPTY*/
{ $$ = NIL; }
;
expr_list:
expr_elem { $$ = list_make1($1); }
| expr_list AND expr_elem { $$ = lappend($1, $3); }
;
expr_elem:
IDENT op_any PARAM
{
CaQLExpr *n = makeNode(CaQLExpr);
n->left = $1;
n->op = $2;
n->right = $3;
$$ = (Node *) n;
}
/* | IDENT IS PARAM */
;
op_any:
OP_EQUAL { $$ = $1; }
| OP_LT { $$ = $1; }
| OP_LE { $$ = $1; }
| OP_GE { $$ = $1; }
| OP_GT { $$ = $1; }
;
%%
/*
* Setup CaQL parser. Returned parser is the same as input.
*/
caql_parser_t
caql_parser_init(const char *query, caql_parser_t yyparser,
const char *file, int line)
{
caql_scanner_t scanner;
scanner = caql_scanner_init(query, file, line);
yyparser->scanner = scanner;
yyparser->parsetree = NULL;
return yyparser;
}
/*
* Clean up the parser.
*/
void
caql_parser_finish(caql_parser_t yyparser)
{
caql_scanner_finish(yyparser->scanner);
}
/*
* This is the main interface to the outside. It returns NULL in unexpected
* failures, but usually errors out with ereport().
*/
Node *
caql_raw_parser(const char *query, const char *file, int line)
{
caql_parser pstate, *parser = &pstate;
int yyresult;
caql_parser_init(query, parser, file, line);
yyresult = caql_yyparse(parser);
caql_parser_finish(parser);
if (yyresult) /* failed? */
return NULL;
return pstate.parsetree;
}
/*
* Must undefine this stuff before including scan.c, since it has different
* definitions for these macros.
*/
#undef yyerror
#undef yylval
#undef yylloc
#include "scan.c"
%{
/*-------------------------------------------------------------------------
*
* scan.l
* lexical yyscanner for CaQL
*
*
* The rules are designed so that the yyscanner never has to backtrack,
* in the sense that there is always a rule that can match the input
* consumed so far (the rule action may internally throw back some input
* with yyless(), however). As explained in the flex manual, this makes
* for a useful speed increase --- about a third faster than a plain -CF
* lexer, in simple testing. The extra complexity is mostly in the rules
* for handling float numbers and continued string literals. If you change
* the lexical rules, verify that you haven't broken the no-backtrack
* property by running flex with the "-b" option and checking that the
* resulting "lex.backup" file says that no backing up is needed.
*
* We employ reentrant lexer and parser.
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include <ctype.h>
#include <unistd.h>
#include "catalog/caqlparse.h"
#include "parser/keywords.h"
#include "parser/scansup.h"
#include "gram.h"
/*
* user data for caql lexer
*
* literalbuf is used to accumulate literal values when multiple rules
* are needed to parse a single literal. Call startlit to reset buffer
* to empty, addlit to add text. Note that the buffer is palloc'd and
* starts life afresh on every parse cycle.
*/
typedef struct caql_yyextra_t
{
char *literalbuf;
int literallen;
int literalalloc;
const char *file;
int line;
char *scanbuf;
} caql_yyextra_t;
/*
* Declare here instead of separate header file, as the set is small enough.
* All CaQL keywords are reserved ones.
*/
#define CAQL_KEYWORD(a, b) {a, b, RESERVED_KEYWORD},
static const ScanKeyword CaQLScanKeywords[] = {
CAQL_KEYWORD("and", AND)
CAQL_KEYWORD("by", BY)
CAQL_KEYWORD("for", FOR)
CAQL_KEYWORD("from", FROM)
CAQL_KEYWORD("into", INTO)
CAQL_KEYWORD("is", IS)
CAQL_KEYWORD("order", ORDER)
CAQL_KEYWORD("select", SELECT)
CAQL_KEYWORD("update", UPDATE)
CAQL_KEYWORD("where", WHERE)
};
static const ScanKeyword *CaQLLastScanKeyword = endof(CaQLScanKeywords);
/* Avoid exit() on fatal yyscanner errors (a bit ugly -- see yy_fatal_error) */
#undef fprintf
#define fprintf(file, fmt, msg) ereport(ERROR, (errmsg_internal("%s", msg)))
static int xcdepth = 0; /* depth of nesting in slash-star comments */
static void addlit(char *ytext, int yleng, caql_scanner_t yyscanner);
static void addlitchar(unsigned char ychar, caql_scanner_t yyscanner);
static char *litbufdup(caql_scanner_t yyscanner);
/*
* Each call to yylex must set yylloc to the location of the found token
* (expressed as a byte offset from the start of the input text).
* When we parse a token that requires multiple lexer rules to process,
* this should be done in the first such rule, else yylloc will point
* into the middle of the token.
*/
#define SET_YYLLOC() (*(yylloc) = yytext - yyextra->scanbuf)
/*
* Work around a bug in flex 2.5.35: it emits a couple of functions that
* it forgets to emit declarations for. Since we use -Wmissing-prototypes,
* this would cause warnings. Providing our own declarations should be
* harmless even when the bug gets fixed.
*/
extern int caql_yyget_column(yyscan_t yyscanner);
extern void caql_yyset_column(int column_no, yyscan_t yyscanner);
%}
%option reentrant
%option 8bit
%option never-interactive
%option nodefault
%option noinput
%option nounput
%option noyywrap
%option noyyalloc
%option noyyrealloc
%option noyyfree
%option prefix="caql_yy"
%option extra-type="caql_yyextra_t *"
%option bison-bridge
%option bison-locations
/*
* OK, here is a short description of lex/flex rules behavior.
* The longest pattern which matches an input string is always chosen.
* For equal-length patterns, the first occurring in the rules list is chosen.
* INITIAL is the starting state, to which all non-conditional rules apply.
* Exclusive states change parsing rules while the state is active. When in
* an exclusive state, only those rules defined for that state apply.
*
* We use exclusive states for quoted strings, extended comments,
* and to eliminate parsing troubles for numeric strings.
* Exclusive states:
* <xc> extended C-style comments
* <xq> standard quoted strings
*/
%x xc
%x xq
/*
* In order to make the world safe for Windows and Mac clients as well as
* Unix ones, we accept either \n or \r as a newline. A DOS-style \r\n
* sequence will be seen as two successive newlines, but that doesn't cause
* any problems. Comments that start with -- and extend to the next
* newline are treated as equivalent to a single whitespace character.
*
* NOTE a fine point: if there is no newline following --, we will absorb
* everything to the end of the input as a comment. This is correct. Older
* versions of Postgres failed to recognize -- as a comment if the input
* did not end with a newline.
*
* XXX perhaps \f (formfeed) should be treated as a newline as well?
*/
space [ \t\n\r\f]
horiz_space [ \t\f]
newline [\n\r]
non_newline [^\n\r]
comment ("--"{non_newline}*)
whitespace ({space}+|{comment})
/*
* CaQL, as SQL does, requires at least one newline in the whitespace
* separating string literals that are to be concatenated. Note that
* {whitespace_with_newline} should not have * after it, whereas {whitespace}
* should generally have a * after it...
*/
special_whitespace ({space}+|{comment}{newline})
horiz_whitespace ({horiz_space}|{comment})
whitespace_with_newline ({horiz_whitespace}*{newline}{special_whitespace}*)
/*
* To ensure that {quotecontinue} can be scanned without having to back up
* if the full pattern isn't matched, we include trailing whitespace in
* {quotestop}. This matches all cases where {quotecontinue} fails to match,
* except for {quote} followed by whitespace and just one "-" (not two,
* which would start a {comment}). To cover that we have {quotefail}.
* The actions for {quotestop} and {quotefail} must throw back characters
* beyond the quote proper.
*/
quote '
quotestop {quote}{whitespace}*
quotecontinue {quote}{whitespace_with_newline}{quote}
quotefail {quote}{whitespace}*"-"
/* Extended quote
* xqdouble implements embedded quote, ''''
*/
xqstart {quote}
xqdouble {quote}{quote}
xqinside [^']+
/*
* C-style comments
*
* The "extended comment" syntax closely resembles allowable operator syntax.
* The tricky part here is to get lex to recognize a string starting with
* slash-star as a comment, when interpreting it as an operator would produce
* a longer match --- remember lex will prefer a longer match!
*/
xcstart \/\*
xcstop \*+\/
xcinside [^*/]+
digit [0-9]
ident_start [A-Za-z\200-\377_]
ident_cont [A-Za-z\200-\377_0-9\$]
identifier {ident_start}{ident_cont}*
equal =
lt <
le <=
ge >=
gt >
/*
* "self" is the set of chars that should be returned as single-character
* tokens. "op_chars" is the set of chars that can make up "Op" tokens,
* which can be one or more characters long (but if a single-char token
* appears in the "self" set, it is not to be returned as an Op). Note
* that the sets overlap, but each has some chars that are not in the other.
*
* If you change either set, adjust the character lists appearing in the
* rule for "operator"!
*/
self [,()\[\].;\:\+\-\*\/\%\^\<\>\=]
/*
* We do not allow unary minus in numbers. Instead we pass it separately
* to parser.
*
* {realfail1} and {realfail2} are added to prevent the need for yyscanner
* backup when the {real} rule fails to match completely.
*/
integer {digit}+
decimal (({digit}*\.{digit}+)|({digit}+\.{digit}*))
real ({integer}|{decimal})[Ee][-+]?{digit}+
realfail1 ({integer}|{decimal})[Ee]
realfail2 ({integer}|{decimal})[Ee][-+]
param \:{integer}
other .
%%
{whitespace} {
/* ignore */
}
{xcstart} {
/* Set location in case of syntax error in comment */
SET_YYLLOC();
xcdepth = 0;
BEGIN(xc);
/* Put back any characters past slash-star; see above */
yyless(2);
}
<xc>{xcstart} {
xcdepth++;
/* Put back any characters past slash-star; see above */
yyless(2);
}
<xc>{xcstop} {
if (xcdepth <= 0)
BEGIN(INITIAL);
else
xcdepth--;
}
<xc>{xcinside} {
/* ignore */
}
<xc>\*+ {
/* ignore */
}
<xc>\/+ {
/* ignore */
}
<xc><<EOF>> { caql_scanner_yyerror("unterminated /* comment", yyscanner); }
<xq>{quotestop} |
<xq>{quotefail} {
yyless(1);
BEGIN(INITIAL);
yylval->str = litbufdup(yyscanner);
return SCONST;
}
<xq>{xqdouble} {
addlitchar('\'', yyscanner);
}
<xq>{xqinside} {
addlit(yytext, yyleng, yyscanner);
}
<xq>{quotecontinue} {
/* ignore */
}
<xq><<EOF>> { caql_scanner_yyerror("unterminated quoted string", yyscanner); }
{equal} {
SET_YYLLOC();
yylval->str = pstrdup(yytext);
return OP_EQUAL;
}
{lt} {
SET_YYLLOC();
yylval->str = pstrdup(yytext);
return OP_LT;
}
{le} {
SET_YYLLOC();
yylval->str = pstrdup(yytext);
return OP_LE;
}
{ge} {
SET_YYLLOC();
yylval->str = pstrdup(yytext);
return OP_GE;
}
{gt} {
SET_YYLLOC();
yylval->str = pstrdup(yytext);
return OP_GT;
}
{self} {
SET_YYLLOC();
return yytext[0];
}
{param} {
SET_YYLLOC();
yylval->ival = atol(yytext + 1);
return PARAM;
}
{integer} {
long val;
char* endptr;
SET_YYLLOC();
errno = 0;
val = strtol(yytext, &endptr, 10);
if (*endptr != '\0' || errno == ERANGE
#ifdef HAVE_LONG_INT_64
/* if long > 32 bits, check for overflow of int4 */
|| val != (long) ((int32) val)
#endif
)
{
/* integer too large, treat it as a float */
yylval->str = pstrdup(yytext);
return FCONST;
}
yylval->ival = val;
return ICONST;
}
{decimal} {
SET_YYLLOC();
yylval->str = pstrdup(yytext);
return FCONST;
}
{real} {
SET_YYLLOC();
yylval->str = pstrdup(yytext);
return FCONST;
}
{realfail1} {
/*
* throw back the [Ee], and treat as {decimal}. Note
* that it is possible the input is actually {integer},
* but since this case will almost certainly lead to a
* syntax error anyway, we don't bother to distinguish.
*/
yyless(yyleng-1);
SET_YYLLOC();
yylval->str = pstrdup(yytext);
return FCONST;
}
{realfail2} {
/* throw back the [Ee][+-], and proceed as above */
yyless(yyleng-2);
SET_YYLLOC();
yylval->str = pstrdup(yytext);
return FCONST;
}
{identifier} {
const ScanKeyword *keyword;
char *ident;
SET_YYLLOC();
/* Is it a keyword? */
keyword = ScanKeywordLookupExt(yytext, CaQLScanKeywords,
CaQLLastScanKeyword);
if (keyword != NULL)
{
yylval->keyword = keyword->name;
return keyword->value;
}
/*
* No. Convert the identifier to lower case, and truncate
* if necessary.
*/
ident = downcase_truncate_identifier(yytext, yyleng, true);
yylval->str = ident;
return IDENT;
}
{other} {
SET_YYLLOC();
return yytext[0];
}
<<EOF>> {
SET_YYLLOC();
yyterminate();
}
%%
/*
* caql_scanner_yyerror
* Report a lexer or grammar error.
*
* The message's cursor position identifies the most recently lexed token.
* This is OK for syntax error messages from the Bison parser, because Bison
* parsers report error as soon as the first unparsable token is reached.
* Beware of using yyerror for other purposes, as the cursor position might
* be misleading!
*/
static void
caql_scanner_yyerror(const char *message, caql_scanner_t yyscanner)
{
caql_yyextra_t *extra = yyget_extra(yyscanner);
const char *loc = extra->scanbuf + *yyget_lloc(yyscanner);
if (*loc == YY_END_OF_BUFFER_CHAR)
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
/* translator: %s is typically the translation of "syntax error" */
errmsg("%s at end of input", _(message)),
errhint("%s (%s:%d)",
extra->scanbuf, extra->file, extra->line),
*yyget_lloc(yyscanner)));
}
else
{
ereport(ERROR,
(errcode(ERRCODE_SYNTAX_ERROR),
/* translator: first %s is typically the translation of "syntax error" */
errmsg("%s at or near \"%s\"", _(message), loc),
errhint("%s (%s:%d)",
extra->scanbuf, extra->file, extra->line),
*yyget_lloc(yyscanner)));
}
}
/*
* Called before any actual scanning is done
*/
static caql_scanner_t
caql_scanner_init(const char *str, const char *file, int line)
{
Size slen = strlen(str);
yyscan_t scanner;
caql_yyextra_t *extra;
if (caql_yylex_init(&scanner) != 0)
elog(ERROR, "caql_yylex_init() failed: %m");
extra = (caql_yyextra_t *) palloc(sizeof(caql_yyextra_t));
extra->file = file;
extra->line = line;
caql_yyset_extra(extra, scanner);
/*
* Make a scan buffer with special termination needed by flex.
*/
extra->scanbuf = (char *) palloc(slen + 2);
memcpy(extra->scanbuf, str, slen);
extra->scanbuf[slen] = extra->scanbuf[slen + 1] = YY_END_OF_BUFFER_CHAR;
caql_yy_scan_buffer(extra->scanbuf, slen + 2, scanner);
/* initialize literal buffer to a reasonable but expansible size */
extra->literalalloc = 1024;
extra->literalbuf = (char *) palloc(extra->literalalloc);
extra->literallen = 0;
return scanner;
}
/*
* Called after scanning is done to clean up after caql_scanner_init()
*/
static void
caql_scanner_finish(caql_scanner_t yyscanner)
{
pfree(yyget_extra(yyscanner)->scanbuf);
pfree(yyget_extra(yyscanner)->literalbuf);
pfree(yyget_extra(yyscanner));
caql_yyfree(yyscanner, yyscanner);
}
static void
addlit(char *ytext, int yleng, caql_scanner_t yyscanner)
{
caql_yyextra_t *extra = yyget_extra(yyscanner);
/* enlarge buffer if needed */
if ((extra->literallen + yleng) >= extra->literalalloc)
{
do
{
extra->literalalloc *= 2;
} while ((extra->literallen + yleng) >= extra->literalalloc);
extra->literalbuf = (char *) repalloc(extra->literalbuf, extra->literalalloc);
}
/* append new data, add trailing null */
memcpy(extra->literalbuf + extra->literallen, ytext, yleng);
extra->literallen += yleng;
extra->literalbuf[extra->literallen] = '\0';
}
static void
addlitchar(unsigned char ychar, caql_scanner_t yyscanner)
{
caql_yyextra_t *extra = yyget_extra(yyscanner);
/* enlarge buffer if needed */
if ((extra->literallen + 1) >= extra->literalalloc)
{
extra->literalalloc *= 2;
extra->literalbuf = (char *) repalloc(extra->literalbuf, extra->literalalloc);
}
/* append new data, add trailing null */
extra->literalbuf[extra->literallen] = ychar;
extra->literallen += 1;
extra->literalbuf[extra->literallen] = '\0';
}
/*
* One might be tempted to write pstrdup(literalbuf) instead of this,
* but for long literals this is much faster because the length is
* already known.
*/
static char *
litbufdup(caql_scanner_t yyscanner)
{
char *new;
caql_yyextra_t *extra = yyget_extra(yyscanner);
new = palloc(extra->literallen + 1);
memcpy(new, extra->literalbuf, extra->literallen + 1);
return new;
}
/*
* Interface functions to make flex use palloc() instead of malloc().
* It'd be better to make these static, but flex insists otherwise.
*/
void *
caql_yyalloc(yy_size_t bytes, yyscan_t yyscanner)
{
return palloc(bytes);
}
void *
caql_yyrealloc(void *ptr, yy_size_t bytes, yyscan_t yyscanner)
{
if (ptr)
return repalloc(ptr, bytes);
else
return palloc(bytes);
}
void
caql_yyfree(void *ptr, yyscan_t yyscanner)
{
if (ptr)
pfree(ptr);
}
subdir=src/backend/catalog/caql
top_builddir=../../../../..
include $(top_builddir)/src/Makefile.global
TARGETS=caqlanalyze gram
include $(top_builddir)/src/backend/mock.mk
caqlanalyze.t: \
$(MOCK_DIR)/backend/access/common/scankey_mock.o \
$(MOCK_DIR)/backend/access/heap/heapam_mock.o \
$(MOCK_DIR)/backend/access/index/genam_mock.o \
$(MOCK_DIR)/backend/utils/cache/syscache_mock.o
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include "cmockery.h"
/*
* In some cases, we hit segumentation fault around elog(ERROR, ...). To
* avoid any possibilities, let it do the minimum work.
*/
#define elog_finish __elog_finish
#include "../caqlanalyze.c"
#include "catalog/pg_constraint.h"
#include "catalog/pg_proc.h"
#include "utils/fmgroids.h"
#define expect_value_or_any(func, arg) do{ \
if (use_##arg) \
expect_value(func, arg, _##arg); \
else \
expect_any(func, arg); \
}while(0)
#define CaQL(q, nkeys, keys) CaQL1(q, nkeys, keys, __FILE__, __LINE__)
/*
* Currently the only needs for elog is for ERROR to call siglongjump().
* If you need more information such like ErrorData, you'll have to add
* code to set it up.
*/
void
__elog_finish(int elevel, const char *fmt, ...)
{
if (elevel == ERROR)
PG_RE_THROW();
}
/*
* Stub for caql1
*/
static cq_list *
CaQL1(const char *caqlStr, int maxkeys, Datum *keys, const char* filename, int lineno)
{
cq_list *pcql = palloc0(sizeof(cq_list));
pcql->bGood = true;
pcql->caqlStr = caqlStr;
pcql->numkeys = 0; /* TODO: not used? */
pcql->maxkeys = maxkeys;
pcql->filename = filename;
pcql->lineno = lineno;
if (maxkeys > 0)
{
int i;
for (i = 0; i < maxkeys; i++)
{
pcql->cqlKeys[i] = keys[i];
}
}
return pcql;
}
/*
* Set up expects for heap_open()
*/
static void
expect__heap_open(Oid _relationId, bool use_relationId,
LOCKMODE _lockmode, bool use_lockmode,
Relation retvalue)
{
expect_value_or_any(heap_open, relationId);
expect_value_or_any(heap_open, lockmode);
will_return(heap_open, retvalue);
}
/*
* Set up expects for ScanKeyInit()
*/
static void
expect__ScanKeyInit(ScanKey _entry, bool use_entry,
AttrNumber _attributeNumber, bool use_attributeNumber,
StrategyNumber _strategy, bool use_strategy,
RegProcedure _procedure, bool use_procedure,
Datum _argument, bool use_argument)
{
expect_value_or_any(ScanKeyInit, entry);
expect_value_or_any(ScanKeyInit, attributeNumber);
expect_value_or_any(ScanKeyInit, strategy);
expect_value_or_any(ScanKeyInit, procedure);
expect_value_or_any(ScanKeyInit, argument);
/* returns void */
will_return(ScanKeyInit, 0);
}
/*
* Set up expects for beginscan
*/
static void
expect__systable_beginscan(Relation _heapRelation, bool use_heapRelation,
Oid _indexId, bool use_indexId,
bool _indexOK, bool use_indexOK,
Snapshot _snapshot, bool use_snapshot,
int _nkeys, bool use_nkeys,
ScanKey _key, bool use_key,
SysScanDesc retvalue)
{
expect_value_or_any(systable_beginscan, heapRelation);
expect_value_or_any(systable_beginscan, indexId);
expect_value_or_any(systable_beginscan, indexOK);
expect_value_or_any(systable_beginscan, snapshot);
expect_value_or_any(systable_beginscan, nkeys);
expect_value_or_any(systable_beginscan, key);
will_return(systable_beginscan, retvalue);
}
/*
* This will use syscache.
*/
void
test__caql_switch1(void **state)
{
const char *query = "SELECT * FROM pg_class WHERE oid = :1";
struct caql_hash_cookie *hash_cookie;
cqContext context = {0}, *pCtx;
Datum keys[] = {ObjectIdGetDatum(ProcedureRelationId)};
cq_list *pcql = CaQL(query, 1, keys);
expect_any(GetSysCacheId, indexoid);
will_return(GetSysCacheId, 123); /* pretend there is a syscache */
hash_cookie = cq_lookup(query, strlen(query), pcql);
pCtx = caql_switch(hash_cookie, &context, pcql);
assert_true(pCtx != NULL);
assert_true(pCtx->cq_sysScan == NULL);
assert_true(pCtx->cq_usesyscache);
hash_cookie = cq_lookup(query, strlen(query), pcql);
}
/*
* This will use heap scan.
*/
void
test__caql_switch2(void **state)
{
const char *query = "INSERT into pg_proc";
struct caql_hash_cookie *hash_cookie;
cqContext context = {0}, *pCtx;
Datum keys[] = {};
cq_list *pcql = CaQL(query, 0, keys);
RelationData dummyrel;
dummyrel.rd_id = ProcedureRelationId;
hash_cookie = cq_lookup(query, strlen(query), pcql);
/* setup heap_open */
expect__heap_open(ProcedureRelationId, true,
RowExclusiveLock, true,
&dummyrel);
pCtx = caql_switch(hash_cookie, &context, pcql);
assert_true(pCtx != NULL);
assert_true(pCtx->cq_sysScan == NULL);
assert_int_equal(RelationGetRelid(pCtx->cq_heap_rel), ProcedureRelationId);
}
/*
* This will use heap scan.
*/
void
test__caql_switch3(void **state)
{
const char *query = "SELECT * FROM pg_class "
"WHERE oid = :1 AND relname = :2";
struct caql_hash_cookie *hash_cookie;
cqContext context = {0}, *pCtx;
NameData relname = {"pg_class"};
Datum keys[] = {ObjectIdGetDatum(ProcedureRelationId),
NameGetDatum(&relname)};
cq_list *pcql = CaQL(query, 1, keys);
RelationData dummyrel;
SysScanDescData dummydesc;
dummyrel.rd_id = RelationRelationId;
hash_cookie = cq_lookup(query, strlen(query), pcql);
/* setup heap_open */
expect__heap_open(RelationRelationId, true,
AccessShareLock, true,
&dummyrel);
/* setup ScanKeyInit */
expect__ScanKeyInit(NULL, false,
ObjectIdAttributeNumber, true,
BTEqualStrategyNumber, true,
F_OIDEQ, true,
NULL, false);
/* setup ScanKeyInit */
expect__ScanKeyInit(NULL, false,
Anum_pg_class_relname, true,
BTEqualStrategyNumber, true,
F_NAMEEQ, true,
NULL, false);
/* setup systable_beginscan */
expect__systable_beginscan(&dummyrel, true,
0, false,
0, false,
SnapshotNow, true,
2, true,
NULL, false,
&dummydesc);
pCtx = caql_switch(hash_cookie, &context, pcql);
assert_true(pCtx != NULL);
assert_true(pCtx->cq_sysScan == &dummydesc);
assert_true(pCtx->cq_heap_rel == &dummyrel);
assert_false(pCtx->cq_usesyscache);
}
/*
* This tests an index scan on pg_constraint.
*/
void
test__caql_switch4(void **state)
{
const char *query = "SELECT * FROM pg_constraint "
"WHERE conrelid = :1";
struct caql_hash_cookie *hash_cookie;
cqContext context = {0}, *pCtx;
Datum keys[] = {ObjectIdGetDatum(ProcedureRelationId)};
cq_list *pcql = CaQL(query, 1, keys);
RelationData dummyrel;
SysScanDescData dummydesc;
expect_any(GetSysCacheId, indexoid);
will_return(GetSysCacheId, -1); /* pretend there is no syscache */
dummyrel.rd_id = ConstraintRelationId;
hash_cookie = cq_lookup(query, strlen(query), pcql);
/*
* Add explicit relation
*/
pCtx = caql_addrel(cqclr(&context), &dummyrel);
/* setup ScanKeyInit */
expect__ScanKeyInit(NULL, false,
Anum_pg_constraint_conrelid, true,
BTEqualStrategyNumber, true,
F_OIDEQ, true,
NULL, false);
/* setup systable_beginscan */
expect__systable_beginscan(&dummyrel, true,
ConstraintRelidIndexId, true,
true, true,
SnapshotNow, true,
1, true,
NULL, false,
&dummydesc);
pCtx = caql_switch(hash_cookie, pCtx, pcql);
assert_true(pCtx != NULL);
assert_true(pCtx->cq_sysScan == &dummydesc);
assert_true(pCtx->cq_heap_rel == &dummyrel);
assert_false(pCtx->cq_usesyscache);
assert_true(pCtx->cq_useidxOK);
}
/*
* This tests if non-equal predicates also use index scan.
*/
void
test__caql_switch5(void **state)
{
const char *query = "SELECT * FROM pg_attribute "
"WHERE attrelid = :1 and attnum > :2";
struct caql_hash_cookie *hash_cookie;
cqContext context = {0}, *pCtx;
Datum keys[] = {ObjectIdGetDatum(ProcedureRelationId),
Int16GetDatum(0)};
cq_list *pcql = CaQL(query, 2, keys);
RelationData dummyrel;
SysScanDescData dummydesc;
expect_any(GetSysCacheId, indexoid);
will_return(GetSysCacheId, 123); /* pretend there is a syscache */
dummyrel.rd_id = AttributeRelationId;
hash_cookie = cq_lookup(query, strlen(query), pcql);
/*
* Add explicit relation
*/
pCtx = caql_addrel(cqclr(&context), &dummyrel);
/* setup ScanKeyInit */
expect__ScanKeyInit(NULL, false,
Anum_pg_attribute_attrelid, true,
BTEqualStrategyNumber, true,
F_OIDEQ, true,
NULL, false);
/* setup ScanKeyInit */
expect__ScanKeyInit(NULL, false,
Anum_pg_attribute_attnum, true,
BTGreaterStrategyNumber, true,
F_INT2GT, true,
NULL, false);
/* setup systable_beginscan */
expect__systable_beginscan(&dummyrel, true,
AttributeRelidNumIndexId, true,
true, true,
SnapshotNow, true,
2, true,
NULL, false,
&dummydesc);
pCtx = caql_switch(hash_cookie, pCtx, pcql);
assert_true(pCtx != NULL);
assert_true(pCtx->cq_sysScan == &dummydesc);
assert_true(pCtx->cq_heap_rel == &dummyrel);
assert_false(pCtx->cq_usesyscache);
assert_true(pCtx->cq_useidxOK);
}
/*
* This tests DELETE
*/
void
test__caql_switch6(void **state)
{
const char *query = "DELETE FROM pg_class WHERE oid = :1";
struct caql_hash_cookie *hash_cookie;
cqContext context = {0}, *pCtx;
Datum keys[] = {ObjectIdGetDatum(ProcedureRelationId)};
cq_list *pcql = CaQL(query, 1, keys);
RelationData dummyrel;
expect_any(GetSysCacheId, indexoid);
will_return(GetSysCacheId, 123); /* pretend there is a syscache */
dummyrel.rd_id = RelationRelationId;
hash_cookie = cq_lookup(query, strlen(query), pcql);
/* setup heap_open */
expect__heap_open(RelationRelationId, true,
RowExclusiveLock, true,
&dummyrel);
pCtx = caql_switch(hash_cookie, &context, pcql);
assert_true(pCtx != NULL);
assert_true(pCtx->cq_usesyscache);
assert_true(pCtx->cq_heap_rel->rd_id == RelationRelationId);
assert_true(pCtx->cq_useidxOK);
}
/*
* This tests operators and orderby
*/
void
test__caql_switch7(void **state)
{
const char *query = "SELECT * FROM pg_class "
"WHERE oid < :1 AND relnatts > :2 AND "
"relfilenode <= :3 AND relpages >= :4 "
"ORDER BY oid, relnatts, relfilenode";
struct caql_hash_cookie *hash_cookie;
cqContext context = {0}, *pCtx;
Datum keys[] = {ObjectIdGetDatum(10000),
Int16GetDatum(10),
ObjectIdGetDatum(10000),
Int32GetDatum(5)};
cq_list *pcql = CaQL(query, 1, keys);
RelationData dummyrel;
SysScanDescData dummydesc;
SnapshotData SnapshotDirty;
InitDirtySnapshot(SnapshotDirty);
dummyrel.rd_id = RelationRelationId;
hash_cookie = cq_lookup(query, strlen(query), pcql);
pCtx = caql_snapshot(cqclr(&context), &SnapshotDirty);
/* setup heap_open */
expect__heap_open(RelationRelationId, true,
AccessShareLock, true,
&dummyrel);
/* setup ScanKeyInit */
expect__ScanKeyInit(NULL, false,
ObjectIdAttributeNumber, true,
BTLessStrategyNumber, true,
F_OIDLT, true,
NULL, false);
expect__ScanKeyInit(NULL, false,
Anum_pg_class_relnatts, true,
BTGreaterStrategyNumber, true,
F_INT2GT, true,
NULL, false);
expect__ScanKeyInit(NULL, false,
Anum_pg_class_relfilenode, true,
BTLessEqualStrategyNumber, true,
F_OIDLE, true,
NULL, false);
expect__ScanKeyInit(NULL, false,
Anum_pg_class_relpages, true,
BTGreaterEqualStrategyNumber, true,
F_INT4GE, true,
NULL, false);
/* setup systable_beginscan */
expect__systable_beginscan(&dummyrel, true,
InvalidOid, false,
false, true,
&SnapshotDirty, true,
4, true,
NULL, false,
&dummydesc);
pCtx = caql_switch(hash_cookie, pCtx, pcql);
assert_true(pCtx != NULL);
assert_true(!pCtx->cq_usesyscache);
assert_true(pCtx->cq_heap_rel->rd_id == RelationRelationId);
assert_true(!pCtx->cq_useidxOK);
}
static void
common__cq_lookup_fail(void **state, const char *query, Datum keys[], int nkeys)
{
struct caql_hash_cookie *hash_cookie;
cq_list *pcql = CaQL(query, nkeys, keys);
bool result = false;
PG_TRY();
{
hash_cookie = cq_lookup(query, strlen(query), pcql);
}
PG_CATCH();
{
result = true;
}
PG_END_TRY();
assert_true(result);
}
/*
* Test for non-existent column name.
*/
void
test__cq_lookup_fail1(void **state)
{
const char *query = "SELECT proname from pg_type WHERE oid = :1";
Datum keys[] = {ObjectIdGetDatum(10000)};
common__cq_lookup_fail(state, query, keys, 1);
}
/*
* Test for non-existent relation name in UPDATE
*/
void
test__cq_lookup_fail2(void **state)
{
const char *query = "SELECT * FROM pg_nonexistent FOR UPDATE";
Datum keys[] = {};
common__cq_lookup_fail(state, query, keys, 0);
}
/*
* Test for non-existent relation name in INSERT
*/
void
test__cq_lookup_fail3(void **state)
{
const char *query = "INSERT INTO pg_nonexistent";
Datum keys[] = {};
common__cq_lookup_fail(state, query, keys, 0);
}
/*
* Test for non-existent relation name in SELECT
*/
void
test__cq_lookup_fail4(void **state)
{
const char *query = "SELECT count(*) FROM pg_nonexistent";
Datum keys[] = {};
common__cq_lookup_fail(state, query, keys, 0);
}
/*
* Test for non-existent relation name in DELETE.
*/
void
test__cq_lookup_fail5(void **state)
{
const char *query = "DELETE FROM pg_nonexistent";
Datum keys[] = {};
common__cq_lookup_fail(state, query, keys, 0);
}
/*
* Test for non-existent column in predicate.
*/
void
test__cq_lookup_fail6(void **state)
{
const char *query = "SELECT * FROM pg_class WHERE relnonexistent = :1";
Datum keys[] = {ObjectIdGetDatum(42)};
common__cq_lookup_fail(state, query, keys, 1);
}
/*
* Test for invalid paramter number.
*/
void
test__cq_lookup_fail7(void **state)
{
const char *query = "SELECT * FROM pg_class WHERE oid = :6";
Datum keys[] = {ObjectIdGetDatum(42)};
common__cq_lookup_fail(state, query, keys, 1);
}
int main(int argc, char* argv[]) {
cmockery_parse_arguments(argc, argv);
const UnitTest tests[] = {
unit_test(test__caql_switch1),
unit_test(test__caql_switch2),
unit_test(test__caql_switch3),
unit_test(test__caql_switch4),
unit_test(test__caql_switch5),
unit_test(test__caql_switch6),
unit_test(test__caql_switch7),
unit_test(test__cq_lookup_fail1),
unit_test(test__cq_lookup_fail2),
unit_test(test__cq_lookup_fail3),
unit_test(test__cq_lookup_fail4),
unit_test(test__cq_lookup_fail5),
unit_test(test__cq_lookup_fail6),
unit_test(test__cq_lookup_fail7),
};
MemoryContextInit();
return run_tests(tests);
}
#include <stdio.h>
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include "cmockery.h"
#include "../catquery.c"
static const FormData_pg_attribute Desc_pg_class[Natts_pg_class] = {Schema_pg_class};
static const FormData_pg_attribute Desc_pg_attribute[Natts_pg_attribute] = {Schema_pg_attribute};
static const FormData_pg_attribute Desc_pg_proc[Natts_pg_proc] = {Schema_pg_proc};
static const FormData_pg_attribute Desc_pg_type[Natts_pg_type] = {Schema_pg_type};
static const FormData_pg_attribute Desc_pg_index[Natts_pg_index] = {Schema_pg_index};
static TupleDesc
catcore_get_tuple_desc(Oid relid)
{
TupleDesc tuple_desc = palloc0(sizeof(struct tupleDesc));
if (relid == RelationRelationId)
{
tuple_desc->natts = Natts_pg_class;
tuple_desc->attrs = Desc_pg_class;
tuple_desc->tdhasoid = true;
}
else if (relid == AttributeRelationId)
{
int i;
tuple_desc->natts = Natts_pg_attribute;
tuple_desc->attrs = palloc0(sizeof(Form_pg_attribute) * Natts_pg_attribute);
for (i = 0; i < tuple_desc->natts; i++)
{
tuple_desc->attrs[i] = palloc(ATTRIBUTE_FIXED_PART_SIZE);
memcpy(tuple_desc->attrs[i], &Desc_pg_attribute[i],
ATTRIBUTE_FIXED_PART_SIZE);
tuple_desc->attrs[i]->attcacheoff = -1;
}
tuple_desc->tdhasoid = false;
}
else if (relid == TypeRelationId)
{
tuple_desc->natts = Natts_pg_type;
tuple_desc->attrs = Desc_pg_type;
tuple_desc->tdhasoid = true;
}
tuple_desc->constr = NULL;
tuple_desc->tdtypeid = InvalidOid;
tuple_desc->tdtypmod = -1;
tuple_desc->tdrefcount = -1;
return tuple_desc;
}
static HeapTuple
build_pg_class_tuple()
{
Datum values[Natts_pg_class];
bool nulls[Natts_pg_class];
TupleDesc tuple_desc = catcore_get_tuple_desc(RelationRelationId);
memset(nulls, 1, sizeof(nulls));
return heap_form_tuple(tuple_desc, values, nulls);
}
static HeapTuple
build_pg_attribute_tuple(Oid attrelid)
{
Datum values[Natts_pg_attribute];
bool nulls[Natts_pg_attribute];
TupleDesc tuple_desc = catcore_get_tuple_desc(AttributeRelationId);
memset(nulls, 1, sizeof(nulls));
nulls[0] = false;
values[0] = ObjectIdGetDatum(attrelid);
return heap_form_tuple(tuple_desc, values, nulls);
}
static HeapTuple
build_pg_type_tuple()
{
Datum values[Natts_pg_type];
bool nulls[Natts_pg_type];
TupleDesc tuple_desc = catcore_get_tuple_desc(TypeRelationId);
memset(nulls, 1, sizeof(nulls));
return heap_form_tuple(tuple_desc, values, nulls);
}
#include <stdarg.h>
#include <stddef.h>
#include <setjmp.h>
#include "cmockery.h"
#include "postgres.h"
#include "catalog/caqlparse.h"
#include "nodes/value.h"
#include "../gram.c"
/*
* Test for simple SELECT
*/
void
test__caql_raw_parse1(void **state)
{
const char *query = "SELECT * FROM pg_class";
CaQLSelect *ast;
ast = caql_raw_parser(query, __FILE__, __LINE__);
assert_true(IsA(ast, CaQLSelect));
assert_string_equal(strVal(linitial(ast->targetlist)), "*");
assert_string_equal(ast->from, "pg_class");
assert_true(ast->where == NIL);
}
/*
* Test for column name in SELECT
*/
void
test__caql_raw_parse2(void **state)
{
const char *query = "SELECT attrelid FROM pg_attribute";
CaQLSelect *ast;
ast = caql_raw_parser(query, __FILE__, __LINE__);
assert_true(IsA(ast, CaQLSelect));
assert_string_equal(strVal(linitial(ast->targetlist)), "attrelid");
assert_true(ast->orderby == NIL);
}
/*
* Test for WHERE clause
*/
void
test__caql_raw_parse3(void **state)
{
const char *query = "SELECT * FROM pg_type where oid = :1";
CaQLSelect *ast;
CaQLExpr *pred;
ast = caql_raw_parser(query, __FILE__, __LINE__);
assert_true(IsA(ast, CaQLSelect));
/* WHERE clause will be a list of CaQLExpr */
pred = linitial(ast->where);
assert_int_equal(pred->right, 1);
}
/*
* Test for count(*)
*/
void
test__caql_raw_parse4(void **state)
{
const char *query = "SELECT count(*) FROM pg_proc";
CaQLSelect *ast;
char *tle;
ast = caql_raw_parser(query, __FILE__, __LINE__);
assert_true(IsA(ast, CaQLSelect));
tle = strVal(linitial(ast->targetlist));
/* count(*) will be translated into "*" */
assert_string_equal(tle, "*");
assert_true(ast->count);
}
/*
* Test for ANDed predicates
*/
void
test__caql_raw_parse5(void **state)
{
const char *query = "SELECT * FROM pg_attribute "
"WHERE attrelid = :1 AND attnum > :2";
CaQLSelect *ast;
CaQLExpr *pred;
ast = caql_raw_parser(query, __FILE__, __LINE__);
assert_true(IsA(ast, CaQLSelect));
pred = list_nth(ast->where, 0);
assert_string_equal(pred->op, "=");
assert_int_equal(pred->right, 1);
pred = list_nth(ast->where, 1);
assert_string_equal(pred->op, ">");
assert_int_equal(pred->right, 2);
}
/*
* FOR UPDATE allows ORDER BY
*/
void
test__caql_raw_parse6(void **state)
{
const char *query = "SELECT * FROM pg_attribute "
"ORDER BY attrelid, attnum FOR UPDATE";
CaQLSelect *ast;
char *orderby;
ast = caql_raw_parser(query, __FILE__, __LINE__);
assert_true(IsA(ast, CaQLSelect));
assert_int_equal(list_length(ast->orderby), 2);
/* ORDER BY clause will be a list of single string */
orderby = strVal(list_nth(ast->orderby, 0));
assert_string_equal(orderby, "attrelid");
orderby = strVal(list_nth(ast->orderby, 1));
assert_string_equal(orderby, "attnum");
assert_true(ast->forupdate);
}
static void
negative_test_common(const char *query)
{
Node *ast;
bool result = false;
PG_TRY();
{
ast = caql_raw_parser(query, __FILE__, __LINE__);
}
PG_CATCH();
{
result = true;
}
PG_END_TRY();
assert_true(result);
}
/*
* Negative test with relation alias
*/
void
test__caql_raw_parse_fail1(void **state)
{
negative_test_common("SELECT t1.relname FROM pg_class t1");
}
/*
* Negative test with JOIN
*/
void
test__caql_raw_parse_fail2(void **state)
{
negative_test_common("SELECT * FROM pg_class JOIN pg_attribute "
"ON pg_class.oid = pg_attribute.attrelid");
}
/*
* Negative test with invalid parameter
* Currently CaQL does not accept literal in predicate.
*/
void
test__caql_raw_parse_fail3(void **state)
{
negative_test_common("SELECT * FROM pg_attribute "
"WHERE attrelid = :1 AND attnum > 0");
}
int
main(int argc, char* argv[])
{
cmockery_parse_arguments(argc, argv);
const UnitTest tests[] = {
unit_test(test__caql_raw_parse1),
unit_test(test__caql_raw_parse2),
unit_test(test__caql_raw_parse3),
unit_test(test__caql_raw_parse4),
unit_test(test__caql_raw_parse5),
unit_test(test__caql_raw_parse6),
unit_test(test__caql_raw_parse_fail1),
unit_test(test__caql_raw_parse_fail2),
unit_test(test__caql_raw_parse_fail3),
};
MemoryContextInit();
return run_tests(tests);
}
......@@ -336,9 +336,6 @@ bool gp_fts_probe_pause=false;
/* Force core dump on memory context error */
bool coredump_on_memerror=false;
/* if catquery.c is built with the logquery option, allow caql logging */
bool gp_enable_caql_logging = true;
/* Experimental feature for MPP-4082. Please read doc before setting this guc */
GpAutoStatsModeValue gp_autostats_mode;
char *gp_autostats_mode_string;
......
......@@ -17,7 +17,6 @@
#include "access/heapam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/catquery.h"
#include "catalog/dependency.h"
#include "catalog/indexing.h"
#include "catalog/pg_constraint.h"
......
......@@ -24,7 +24,6 @@
#include "postgres.h"
#include "access/attnum.h"
#include "catalog/caqlparse.h"
#include "catalog/gp_policy.h"
#include "miscadmin.h"
#include "nodes/plannodes.h"
......@@ -4393,36 +4392,6 @@ _copyAlterTypeStmt(AlterTypeStmt *from)
return newnode;
}
static CaQLSelect *
_copyCaQLSelect(const CaQLSelect *from)
{
CaQLSelect *newnode = makeNode(CaQLSelect);
COPY_NODE_FIELD(targetlist);
COPY_STRING_FIELD(from);
COPY_NODE_FIELD(where);
COPY_NODE_FIELD(orderby);
COPY_SCALAR_FIELD(forupdate);
return newnode;
}
static CaQLExpr *
_copyCaQLExpr(const CaQLExpr *from)
{
CaQLExpr *newnode = makeNode(CaQLExpr);
COPY_STRING_FIELD(left);
COPY_STRING_FIELD(op);
COPY_SCALAR_FIELD(right);
COPY_SCALAR_FIELD(attnum);
COPY_SCALAR_FIELD(strategy);
COPY_SCALAR_FIELD(fnoid);
COPY_SCALAR_FIELD(typid);
return newnode;
}
/* ****************************************************************
* pg_list.h copy functions
* ****************************************************************
......@@ -5331,13 +5300,6 @@ copyObject(void *from)
retval = _copyDenyLoginPoint(from);
break;
case T_CaQLSelect:
retval = _copyCaQLSelect(from);
break;
case T_CaQLExpr:
retval = _copyCaQLExpr(from);
break;
default:
elog(ERROR, "unrecognized node type: %d", (int) nodeTag(from));
retval = from; /* keep compiler quiet */
......
......@@ -767,43 +767,6 @@ ReleaseSysCache(HeapTuple tuple)
ReleaseCatCache(tuple);
}
/*
* SearchSysCacheKeyArray
*
* A convenience routine that does invokes SearchSysCache using an
* array of keys
*/
HeapTuple
SearchSysCacheKeyArray(int cacheId,
int numkeys,
Datum *keys)
{
/* for catquery */
Datum key1 = 0,
key2 = 0,
key3 = 0,
key4 = 0;
Assert(numkeys < 5);
switch(numkeys)
{
case 4:
key4 = keys[3];
case 3:
key3 = keys[2];
case 2:
key2 = keys[1];
case 1:
key1 = keys[0];
default:
break;
}
return SearchSysCache(cacheId, key1, key2, key3, key4);
}
/*
* SearchSysCacheCopy
*
......@@ -1000,19 +963,3 @@ SearchSysCacheList(int cacheId, int nkeys,
return SearchCatCacheList(SysCache[cacheId], nkeys,
key1, key2, key3, key4);
}
/*
* Look up the ID of a syscache that's backed by the given index.
*/
int
GetSysCacheId(Oid indexoid)
{
int syscacheid;
for (syscacheid = 0; syscacheid < SysCacheSize; syscacheid++)
{
if (cacheinfo[syscacheid].indoid == indexoid)
return syscacheid;
}
return -1;
}
......@@ -3254,16 +3254,6 @@ struct config_bool ConfigureNamesBool_gp[] =
true, NULL, NULL
},
{
{"gp_enable_caql_logging", PGC_USERSET, DEVELOPER_OPTIONS,
gettext_noop("Enable caql logging."),
NULL,
GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE
},
&gp_enable_caql_logging,
true, NULL, NULL
},
{
{"dml_ignore_target_partition_check", PGC_USERSET, DEVELOPER_OPTIONS,
gettext_noop("Ignores checking whether the user provided correct partition during a direct insert to a leaf partition"),
......
CaQL Usage
==========
CaQL, the Catalog Query Language, is a simplified SQL language and C
api for catalog operations. This api unifies and supplants the
existing heap, index, systable, and syscache api's. In addition, this
api can automatically use and enforce the correct locking and snapshot
modes for the specified catalog operation. The CaQL conversion
project is the initial phase of the Unified Catalog Service, an
initiative which attempts to address many deficiencies in catalog
correctness, stability, extensibility, scalability, and performance.
While the basic api is simple, it has sufficient extensions to support
legacy requirements for unusual locking or index operations. In this
document, the camelcase "CaQL" refers to the SQL-like language, while
the lowercase "caql" prefix describes functions in the catquery.c
module.
The C api supports a variety of methods that emulate the existing
interfaces, plus additional functionality. These functions take two
common arguments: a cqContext and a cq_list.
A cqContext encapsulates all the scan, locking, and snapshot state for
a query. If you supply a null cqContext, catquery will manufacture a
default context with the appropriate locking and snapshot modes. caql
also has several functions to initialize the context to use special
locking and snapshot modes to support legacy requirements.
A cq_list is a CaQL statement plus a variable-length list of "Datum"
arguments, bound in a cql() macro declaration. The cq_list must have
sufficient arguments to match the number of bind values (bindvals) in
the WHERE clause.
CaQL - the language
===================
CaQL is a small subset of SQL. The following operations are supported:
SELECT * FROM <tablename> [WHERE <colname> = <bindval> [ AND ...]] [FOR UPDATE]
SELECT COUNT(*) ...
SELECT <colname> ...
DELETE FROM <tablename> [WHERE <colname> = <bindval> [ AND ...]]
For a SELECT statement, the "FOR UPDATE" clause changes the default
locking from AccessShare to RowExclusive. The SELECT list only
supports "*", "COUNT(*)", or a single column name (where the column is
of type Oid, Name, or Text). The option WHERE clause supports an
ANDed list of "<colname> = <bindval>" expressions. In the current
implementation, only five bind values (labeled ":1" to ":5") are
supported.
Extended Usage - sorting/ordering
=================================
Clients may use the optional "ORDER BY <colname> [, <colname...]"
clause to specify an ordering when iterating over a set of tuples. If
no ordering is specified the tuple output order is random. The ORDER
BY clause immediately precedes the FOR UPDATE (or terminates the
SELECT statement if FOR UPDATE is not specified). CaQL also supports
GreaterThan/LessThan comparison in the WHERE clause for this case, eg
<,>,<=,>=. In the current implementation, ordering requires the
existence of a matching index. Only forward scans are supported.
Sample Usage
============
/*
* Example 1: find the first tuple for resource type and
* lock it for update
*/
HeapTuple tuple;
tuple = caql_getfirst(
NULL,
cql("SELECT * FROM pg_resourcetype"
" WHERE resname = :1 FOR UPDATE",
CStringGetDatum(pNameIn)));
/*
* Example 2: build a context using an existing open relation, and see
* if any queues match the supplied name
*/
pg_resqueue_rel = heap_open(ResQueueRelationId, RowExclusiveLock);
if (caql_getcount(
caql_addrel(cqclr(&cqc), pg_resqueue_rel),
cql("SELECT COUNT(*) FROM pg_resqueue WHERE rsqname = :1",
CStringGetDatum(stmt->queue))))
{
ereport(ERROR,
(errcode(ERRCODE_DUPLICATE_OBJECT),
errmsg("resource queue \"%s\" already exists",
stmt->queue)));
}
/* Example 3: drop the extended attributes for this queue */
int numDel;
numDel =
caql_getcount(
NULL,
cql("DELETE FROM pg_resqueuecapability WHERE resqueueid = :1",
ObjectIdGetDatum(queueid))
);
/*
* Example 4: build a context using an existing open relation,
* and iterate over all of the matching tuples
*/
gp_db_interface_rel = heap_open(GpDbInterfacesRelationId, AccessShareLock);
pcqCtx = caql_beginscan(
caql_addrel(cqclr(&cqc), gp_db_interface_rel),
cql("SELECT * FROM gp_db_interfaces "
" WHERE dbid = :1 ",
ObjectIdGetDatum(c->dbid)));
while (HeapTupleIsValid(tuple = caql_getnext(pcqCtx)))
{
...
}
/* Finish up scan and close appendonly catalog. */
caql_endscan(pcqCtx);
heap_close(gp_db_interface_rel, AccessShareLock);
/*
* Example 5: select only the dbid column from first matching tuple
* of this table, and set a count of how many tuples matched (0, 1, >1)
*/
Oid dbid;
int fetchCount;
dbid = caql_getoid_plus(
NULL,
&fetchCount,
NULL,
cql("SELECT dbid FROM gp_segment_configuration "
" WHERE content = :1 "
" AND role = :2 ",
Int16GetDatum(contentid),
CharGetDatum('m')));
/*
* Example 6: select only the constraint name from first matching tuple
* of this table
*/
char *cname;
cname = caql_getcstring(
NULL,
cql("SELECT conname FROM pg_constraint "
" WHERE oid = :1 ",
ObjectIdGetDatum(constraintId)));
Constructing and Modifying the caql context
===========================================
Almost all of the caql functions take a cqContext (caql context)
argument. This context maintains information on table locking,
visibility, and fetch iteration status. If a null context argument is
supplied, the caql function will internally generate a default
context. While functions like caql_getfirst() and caql_getoid() only
have a context for the duration of their execution, The
caql_beginscan() function returns an initialized context which is a
required argument for subsequent caql_getnext() and caql_endscan()
functions.
The most basic modification to the context is to add an existing,
open, locked relation. If this relation is not supplied, the caql
function will open and lock the tables specified in the cql() query,
and close them at the end of execution (or at caql_endscan() for an
iterator). If the relation is supplied using caql_addrel(), the caql
function will select tuples from the supplied relation, and leave it
open at the end of execution. This pattern is common when multiple
SELECTs and UPDATEs are performed against the same table in a single
transaction. The basic way to do this is:
cqContext cqc; /* declare the context on the stack */
cqclr(&cqc); /* clear the context */
caql_addrel(&cqc, some_open_relation); /* add an open relation */
However, since the context modification functions return a pointer to
the modified cqContext, you can nest or "chain" them:
/*
* clear the context, add open relation pg_resqueue_rel, and supply it
* to caql_getcount()
*/
numDel =
caql_getcount(
caql_addrel(cqclr(&cqc), pg_resqueue_rel),
cql("DELETE FROM pg_resqueue WHERE rsqname = :1",
CStringGetDatum(stmt->queue))))
Additional context modification function are caql_indexOK(),
caql_snapshot(), and caql_syscache(). These
functions mainly exist to support legacy code requirements for the
initial CaQL conversion, enforcing particular patterns of index,
heapscan or syscache usage. As the Unified Catalog Service evolves to
an external service, usage of these functions may be deprecated, as
the underlying implementation will be very different. Best practice
is to avoid their usage if possible.
CaQL Context Lifetime
=====================
The caql context is valid from caql_beginscan() to caql_endscan().
After caql_endscan(), any open tables are closed, and the fetched
tuple is freed. However, if a context pointer is supplied to
caql_getfirst() or caql_getfirst_only(), the tuple is copied out, so
the caql_cntext contains a valid pointer to the last fetched tuple
*after* the call. In addition, if the relation is supplied using
caql_addrel(), then the caql context relation pointer remains valid
until heap_close() is called for the supplied relation.
INSERT, UPDATE, DELETE
======================
CaQL also supports INSERT, UPDATE, and DELETE operations in a
cursor-like context. The caql context contains a pointer to the last
tuple fetched. Several functions can operate upon the last, or
"current" tuple:
void caql_delete_current(cqContext *pCtx);
void caql_update_current(cqContext *pCtx, HeapTuple tup);
HeapTuple caql_modify_current(cqContext *pCtx, Datum *replValues,
bool *replIsnull,
bool *doReplace);
caql_delete_current() performs a simple_heap_delete() on the current
tuple, and caql_update_current() performs a simple_heap_update() plus
CatalogUpdateIndexes(). caql_modify_current() peforms
heap_modify_tuple() on current.
CaQL supports an "INSERT" statement in caql_beginscan() which obtains
the correct table locking and maintains it until caql_endscan(), eg:
/* open pg_proc_callback */
pcqCtx = caql_beginscan(
NULL,
cql("INSERT INTO pg_proc_callback ",
NULL));
/* Build the tuple and insert it */
nulls[Anum_pg_proc_callback_profnoid - 1] = false;
nulls[Anum_pg_proc_callback_procallback - 1] = false;
nulls[Anum_pg_proc_callback_promethod - 1] = false;
values[Anum_pg_proc_callback_profnoid - 1] = ObjectIdGetDatum(profnoid);
values[Anum_pg_proc_callback_procallback - 1] = ObjectIdGetDatum(procbk);
values[Anum_pg_proc_callback_promethod - 1] = CharGetDatum(promethod);
tup = caql_form_tuple(pcqCtx, values, nulls);
/* Insert tuple into the relation */
caql_insert(pcqCtx, tup); /* implicit update of index as well */
caql_endscan(pcqCtx);
Note that the INSERT statement does not have a VALUES clause, or take
any arguments. The actual insertion happens when a tuple is supplied
to caql_insert(). The caql_form_tuple() performs a heap_form_tuple
for the table specified by INSERT, and caql_insert() performs a
simple_heap_insert() and CatalogUpdateIndexes(). The caql_insert()
statement does not require the usage of the cql("INSERT INTO...")
statement -- it also works with caql contexts from
cql("SELECT ... FOR UPDATE"), eg:
pg_type_desc = heap_open(TypeRelationId, RowExclusiveLock);
/* initialize a caql context for the subsequent INSERT and
UPDATE operations after the caql_getfirst()
*/
pcqCtx = caql_addrel(cqclr(&cqc), pg_type_desc);
tup = caql_getfirst(
pcqCtx,
cql("SELECT * FROM pg_type "
" WHERE typname = :1 "
" AND typnamespace = :2 "
" FOR UPDATE ",
CStringGetDatum((char *) typeName),
ObjectIdGetDatum(typeNamespace)));
if (HeapTupleIsValid(tup))
{
/*
* Okay to update existing shell type tuple
*/
tup = caql_modify_current(pcqCtx,
values,
nulls,
replaces);
caql_update_current(pcqCtx, tup);
/* and Update indexes (implicit) */
}
else
{
tup = caql_form_tuple(pcqCtx, values, nulls);
/* Insert tuple into the relation */
typeObjectId = caql_insert(pcqCtx, tup);
/* and Update indexes (implicit) */
}
heap_close(TypeRelationId, RowExclusiveLock);
In this example, the caql context is "preloaded" with a valid, open,
relation, and a single tuple is fetched. Since caql_getfirst() is
used, the context has a valid pointer to a copy of a tuple after the
call. If the tuple is valid, it is updated, else a new tuple is
constructed and inserted into the open table.
Operational Details
===================
The src/backend/catalog/caql/catquery.c file contains a set of "basic"
functions which perform the underlying primitive database operations for
the associated CaQL statement. At runtime, the caql functions hash the cql()
query text to find a previously cached instance of the same query. If not
found, the query is parsed, the relation names and their indexes are looked
up etc. and the pre-processed representation of the query is stored in a
caql_hash_cookie struct, and put in the cache.
Painful SysCache Brain Damage
=============================
I had to add some functions to handle SysCache routines that didn't
easily map into beginscan/getnext/endscan.
/* retrieve the last (current) tuple -- really only useful for
* caql_get_attname_scan()
*/
HeapTuple caql_get_current(cqContext *pCtx);
/*
* adapted from original lsyscache.c/get_attnum()
*
* Given the relation id and the attribute name,
* return the "attnum" field from the attribute relation.
*
* Returns InvalidAttrNumber if the attr doesn't exist (or is dropped).
*/
AttrNumber caql_getattnumber(Oid relid, const char *attname);
/* ----------------------------------------------------------------
* caql_getattname()
*
* The equivalent of SearchSysCacheCopyAttName -
* caql_getfirst(pCtx,
* "SELECT * FROM pg_attribute
* WHERE attrelid = :relid
* AND attname = :attname
* AND attisdropped is false"
* );
*
* That is, find the existing ("undropped") attribute and return
* a copy.
* NOTE: need to be careful if this pCtx is used for update, as this
* function "simulates" the ATTNAME cache lookup.
* ----------------------------------------------------------------
*/
HeapTuple caql_getattname(cqContext *pCtx, Oid relid, const char *attname);
/* ----------------------------------------------------------------
* caql_getattname_scan()
*
* The equivalent of SearchSysCacheAttName -
* caql_beginscan(pCtx,
* "SELECT * FROM pg_attribute
* WHERE attrelid = :relid
* AND attname = :attname
* AND attisdropped is false"
* );
*
* That is, find the existing ("undropped") attribute and return
* a context where the tuple is already fetched. Retrieve the tuple
* using caql_get_current()
* NOTE: this is hideous. My abject apologies.
* NOTE: need to be careful if this pCtx is used for update, as this
* function "simulates" the ATTNAME cache lookup.
* ----------------------------------------------------------------
*/
cqContext *
caql_getattname_scan(cqContext *pCtx0, Oid relid, const char *attname)
/*-------------------------------------------------------------------------
*
* caqlparse.h
* Declarations for routines exported from lexer and parser files.
*
*-------------------------------------------------------------------------
*/
#ifndef CAQLPARSE_H
#define CAQLPARSE_H
#include "access/skey.h"
#include "nodes/nodes.h"
#include "nodes/pg_list.h"
/*
* We track token locations in terms of byte offsets from the start of the
* source string, not the column number/line number representation that
* bison uses by default. Also, to minimize overhead we track only one
* location (usually the first token location) for each construct, not
* the beginning and ending locations as bison does by default. It's
* therefore sufficient to make YYLTYPE an int.
*/
#define YYLTYPE int
/*
* SELECT and SELECT ... FOR UPDATE statements.
*/
typedef struct CaQLSelect
{
NodeTag type; /* node tag */
List *targetlist; /* column name list of SELECT clause */
char *from; /* table name */
List *where; /* expression list in WHERE */
List *orderby; /* identifier list in ORDER BY */
bool forupdate; /* true if this is FOR UPDATE */
} CaQLSelect;
/*
* Expression node.
*
* Currently we only use expression in WHERE clause, and the right hand side
* is always parameter, which is an integer between 1 and 5.
*/
typedef struct CaQLExpr
{
NodeTag type; /* node tag */
char *left; /* left hand side identifier */
char *op; /* operator such as '=' */
int right; /* parameter number from 1 to 5 */
/* postprocess fields */
AttrNumber attnum; /* attribute number */
StrategyNumber strategy; /* btree strategy number */
Oid fnoid; /* operator function id */
Oid typid; /* type id of attribute */
} CaQLExpr;
/*
* The scanner state. This should be an opaque type to comply reentrant
* flex scanner.
*/
typedef void *caql_scanner_t;
/*
* The parser state.
*/
typedef struct caql_parser
{
caql_scanner_t scanner; /* scanner state to pass to yylex() */
Node *parsetree; /* the top node of result */
} caql_parser;
typedef struct caql_parser *caql_parser_t;
/* gram.y */
extern caql_parser_t caql_parser_init(const char *query, caql_parser_t yyparser,
const char *file, int line);
extern void caql_parser_finish(caql_parser_t yyparser);
extern Node *caql_raw_parser(const char *query, const char *file, int lineno);
#endif
/*-------------------------------------------------------------------------
*
* catquery.h
* catalog query
*
*
* Copyright (c) 2011, 2012 Greenplum inc
*
*-------------------------------------------------------------------------
*/
#ifndef CATQUERY_H
#define CATQUERY_H
#include "access/genam.h"
#include "access/relscan.h"
#include "access/sdir.h"
#include "catalog/catcore.h"
#include "nodes/primnodes.h"
#include "storage/lock.h"
#include "utils/catcache.h"
#include "utils/relcache.h"
/*
* TODO: probably "hash cookie" is meaningless now. More like parser state.
*/
typedef struct caql_hash_cookie
{
const char *name; /* caql string */
int basequery_code; /* corresponding base query */
int bUpdate; /* SELECT ... FOR UPDATE */
int bInsert; /* INSERT INTO */
bool bAllEqual; /* true if all equality operators */
AttrNumber attnum; /* column number (or 0 if no column specified) */
Oid atttype; /* type OID of the specified column */
const CatCoreRelation *relation; /* target relation */
const CatCoreIndex *index; /* available index */
int syscacheid; /* syscache matching the index (-1 if none) */
Node *query; /* parsed syntax tree */
/* debug info */
char *file; /* file location */
int lineno; /* line number */
} caql_hash_cookie;
typedef struct cqListData
{
bool bGood; /* if true, struct is allocated */
const char* caqlStr; /* caql string */
int numkeys;
int maxkeys;
const char* filename; /* FILE and LINE macros */
int lineno;
Datum cqlKeys[5]; /* key array */
} cq_list;
typedef struct cqContextData
{
int cq_basequery_code; /* corresponding base query */
int cq_uniqquery_code; /* unique query number */
bool cq_free; /* if true, free this ctx at endscan */
bool cq_freeScan; /* if true, free scan at endscan */
Oid cq_relationId; /* relation id */
Relation cq_heap_rel; /* catalog being scanned */
TupleDesc cq_tupdesc; /* tuple descriptor */
SysScanDesc cq_sysScan; /* heap or index scan */
bool cq_externrel; /* heap rel external to caql */
bool cq_setsnapshot; /* use cq_snapshot (else default) */
Snapshot cq_snapshot; /* snapshot to see */
LOCKMODE cq_lockmode; /* locking mode */
bool cq_EOF; /* true if hit end of fetch */
bool cq_useidxOK; /* use supplied indexOK mode) */
bool cq_pklock_excl; /* lock exclusive if true */
Datum cq_datumKeys[5];/* key array of datums */
int cq_NumKeys; /* number of keys */
ScanKeyData cq_scanKeys[5]; /* initialized sysscan key (from datums) */
const CatCoreRelation *relation; /* relation being read */
const CatCoreIndex *index; /* usable index on the relation */
/* index update information */
CatalogIndexState cq_indstate; /* non-NULL after CatalogOpenIndexes */
bool cq_bScanBlock; /* true if ctx in
* beginscan/endscan block */
/* these attributes control syscache vs normal heap/index
* scan. If usesyscache is set, then sysScan is NULL, and cq_cacheId is the
* SysCacheIdentifier.
*/
bool cq_usesyscache; /* use syscache (internal) */
int cq_cacheId; /* cache identifier */
Datum *cq_cacheKeys; /* array of keys */
HeapTuple cq_lasttup; /* last tuple fetched (for ReleaseSysCache) */
} cqContext;
/* return the first tuple
(and set a flag if the scan finds a second match)
*/
HeapTuple caql_getfirst_only(cqContext *pCtx,
bool *pbOnly,
cq_list *pcql);
#define caql_getfirst(pCtx, pcql) caql_getfirst_only(pCtx, NULL, pcql)
/* return the specified oid column of the first tuple
(and set a flag if the scan finds a second match)
*/
Oid caql_getoid_only(cqContext *pCtx,
bool *pbOnly,
cq_list *pcql);
Oid caql_getoid_plus(cqContext *pCtx0, int *pFetchcount,
bool *pbIsNull, cq_list *pcql);
#define caql_getoid(pCtx, pcql) caql_getoid_plus(pCtx, NULL, NULL, pcql)
cqContext *caql_beginscan(cqContext *pCtx, cq_list *pcql);
HeapTuple caql_getnext(cqContext *pCtx);
/* XXX XXX: endscan must specify if hold or release locks */
void caql_endscan(cqContext *pCtx);
/* retrieve the last (current) tuple */
#define caql_get_current(pCtx) ((pCtx)->cq_lasttup)
/*
NOTE: don't confuse caql_getattr and caql_getattname.
caql_getattr extracts the specified column (by attnum) from the
current tuple in the pcqCtx.
caql_getattname fetches a copy of tuple from pg_attribute that
_describes_ a column of a table. (should really be get_by_attname)
*/
/* equivalent of SysCacheGetAttr - extract a specific attribute for
* current tuple
*/
Datum caql_getattr(cqContext *pCtx, AttrNumber attnum, bool *isnull);
/* return the specified Name or Text column of the first tuple
(and set the fetchcount or isnull if specified)
*/
char *caql_getcstring_plus(cqContext *pCtx0, int *pFetchcount,
bool *pbIsNull, cq_list *pcql);
#define caql_getcstring(pCtx, pcql) \
caql_getcstring_plus(pCtx, NULL, NULL, pcql)
cq_list *cql1(const char* caqlStr, const char* filename, int lineno, ...);
/* caql context modification functions
*
* cqclr and caql_addrel() are ok, but the others are generally used
* to support legacy code requirements, and may be deprecated in
* future releases
*/
cqContext *caql_addrel(cqContext *pCtx, Relation rel); /* */
cqContext *caql_snapshot(cqContext *pCtx, Snapshot ss); /* */
cqContext *cqclr(cqContext *pCtx); /* */
#define cqClearContext(pcqContext) MemSet(pcqContext, 0, sizeof(cqContext))
void caql_logquery(const char *funcname, const char *filename, int lineno,
int uniqquery_code, Oid arg1);
/* ifdef gnuc ! */
#define cql(x, ...) cql1(x, __FILE__, __LINE__, __VA_ARGS__)
/* MPP-18975: wrapper to assuage type checker (only CString currently).
See calico.pl/check_datum_type() for details.
*/
#define cqlIsDatumForCString(d) (d)
/* caqlanalyze.c */
struct caql_hash_cookie * cq_lookup(const char *str, unsigned int len, cq_list *pcql);
cqContext *caql_switch(struct caql_hash_cookie *pchn, cqContext *pCtx, cq_list *pcql);
#endif /* CATQUERY_H */
......@@ -964,9 +964,6 @@ extern int gp_workfile_type_hashjoin;
extern bool gp_mapreduce_define;
extern bool coredump_on_memerror;
/* if catquery.c is built with the logquery option, allow caql logging */
extern bool gp_enable_caql_logging;
/* Autostats feature for MPP-4082. */
typedef enum
{
......
......@@ -513,11 +513,6 @@ typedef enum NodeTag
/* CDB: tags for random other stuff */
T_CdbExplain_StatHdr = 950, /* in cdb/cdbexplain.c */
/*
* TAGS FOR CAQL PARSER
*/
T_CaQLSelect = 2000,
T_CaQLExpr,
} NodeTag;
/*
......
......@@ -17,7 +17,6 @@
#include "fmgr.h"
#include "access/htup.h"
#include "catalog/catquery.h"
#include "nodes/relation.h"
......
......@@ -82,7 +82,6 @@ enum SysCacheIdentifier
WINFNOID
};
extern void InitCatalogCache(void);
extern void InitCatalogCachePhase2(void);
......@@ -91,8 +90,6 @@ extern HeapTuple SearchSysCache(int cacheId,
extern void ReleaseSysCache(HeapTuple tuple);
/* convenience routines */
extern HeapTuple SearchSysCacheKeyArray(int cacheId, int numkeys,
Datum *keys);
extern HeapTuple SearchSysCacheCopy(int cacheId,
Datum key1, Datum key2, Datum key3, Datum key4);
extern bool SearchSysCacheExists(int cacheId,
......@@ -111,8 +108,6 @@ extern Datum SysCacheGetAttr(int cacheId, HeapTuple tup,
extern struct catclist *SearchSysCacheList(int cacheId, int nkeys,
Datum key1, Datum key2, Datum key3, Datum key4);
extern int GetSysCacheId(Oid indexoid);
/*
* The use of the macros below rather than direct calls to the corresponding
* functions is encouraged, as it insulates the caller from changes in the
......
......@@ -98,7 +98,7 @@ uninstall:
# Build dynamically-loaded object file for CREATE FUNCTION ... LANGUAGE C.
NAME = regress
OBJS = regress.o caqltest.o partindex.o
OBJS = regress.o partindex.o
include $(top_srcdir)/src/Makefile.shlib
......
#include "postgres.h"
#include "miscadmin.h"
#include "cdb/cdbtm.h"
#include "cdb/cdbutil.h"
#include "cdb/cdbvars.h"
#include "commands/queue.h"
#include "executor/spi.h"
#include "postmaster/fts.h"
#include "utils/builtins.h"
#include "utils/lsyscache.h"
#include "utils/rel.h"
/* tests for caql coverage in regproc.c */
extern Datum caql_bootstrap_regproc(PG_FUNCTION_ARGS);
/* tests for caql coverage in lsyscache.c */
extern Datum check_get_atttypmod(PG_FUNCTION_ARGS);
extern Datum check_get_opname(PG_FUNCTION_ARGS);
extern Datum check_get_typ_typrelid(PG_FUNCTION_ARGS);
extern Datum check_get_base_element_type(PG_FUNCTION_ARGS);
/* test for caql coverage in spi.c */
extern Datum check_SPI_gettype(PG_FUNCTION_ARGS);
/* tests for caql coverage in cdbutil.c */
extern Datum check_master_standby_dbid(PG_FUNCTION_ARGS);
extern Datum check_my_mirror_dbid(PG_FUNCTION_ARGS);
extern Datum check_dbid_get_dbinfo(PG_FUNCTION_ARGS);
extern Datum check_contentid_get_dbid(PG_FUNCTION_ARGS);
/* tests for caql coverage in segadmin.c */
extern Datum check_gp_activate_standby(PG_FUNCTION_ARGS);
/* tests for caql coverage in queue.c */
extern Datum check_GetResqueueName(PG_FUNCTION_ARGS);
PG_FUNCTION_INFO_V1(caql_bootstrap_regproc);
Datum
caql_bootstrap_regproc(PG_FUNCTION_ARGS)
{
char *cstr_regprocin = "boolin";
char *cstr_regoperin = "#>="; /* we should pick up a unique name */
char *cstr_regclassin = "pg_class";
char *cstr_regtypein = "bool";
Datum result;
StringInfoData buf;
initStringInfo(&buf);
SetProcessingMode(BootstrapProcessing);
/* regproc */
result = DirectFunctionCall1(regprocin, CStringGetDatum(cstr_regprocin));
appendStringInfo(&buf, "regprocin(%s) = %d\n",
cstr_regprocin, DatumGetObjectId(result));
/* regoper */
result = DirectFunctionCall1(regoperin, CStringGetDatum(cstr_regoperin));
appendStringInfo(&buf, "regoperin(%s) = %d\n",
cstr_regoperin, DatumGetObjectId(result));
/* regclass */
result = DirectFunctionCall1(regclassin, CStringGetDatum(cstr_regclassin));
appendStringInfo(&buf, "regclassin(%s) = %d\n",
cstr_regclassin, DatumGetObjectId(result));
/* regtype */
result = DirectFunctionCall1(regtypein, CStringGetDatum(cstr_regtypein));
appendStringInfo(&buf, "regtypein(%s) = %d\n",
cstr_regtypein, DatumGetObjectId(result));
SetProcessingMode(NormalProcessing);
PG_RETURN_TEXT_P(cstring_to_text(buf.data));
}
/*
* Testing caql coverage for lsyscache.c-
*/
PG_FUNCTION_INFO_V1(check_get_atttypmod);
Datum
check_get_atttypmod(PG_FUNCTION_ARGS)
{
Oid relid = PG_GETARG_OID(0);
AttrNumber attnum = PG_GETARG_INT16(1);
int32 result;
result = get_atttypmod(relid, attnum);
PG_RETURN_INT32(result);
}
PG_FUNCTION_INFO_V1(check_get_opname);
Datum
check_get_opname(PG_FUNCTION_ARGS)
{
Oid opno = PG_GETARG_OID(0);
char *result;
StringInfoData buf;
initStringInfo(&buf);
result = get_opname(opno);
if (result)
appendStringInfo(&buf, "%s", result);
PG_RETURN_TEXT_P(cstring_to_text(buf.data));
}
PG_FUNCTION_INFO_V1(check_get_typ_typrelid);
Datum
check_get_typ_typrelid(PG_FUNCTION_ARGS)
{
Oid typid = PG_GETARG_OID(0);
Oid result;
result = get_typ_typrelid(typid);
PG_RETURN_OID(result);
}
PG_FUNCTION_INFO_V1(check_get_base_element_type);
Datum
check_get_base_element_type(PG_FUNCTION_ARGS)
{
Oid typid = PG_GETARG_OID(0);
Oid result;
result = get_base_element_type(typid);
PG_RETURN_OID(result);
}
PG_FUNCTION_INFO_V1(check_SPI_gettype);
Datum
check_SPI_gettype(PG_FUNCTION_ARGS)
{
int fnumber = PG_GETARG_INT32(0);
Relation rel = relation_open(RelationRelationId, AccessShareLock);
char *name = SPI_gettype(RelationGetDescr(rel), fnumber);
relation_close(rel, AccessShareLock);
PG_RETURN_TEXT_P(cstring_to_text(name));
}
PG_FUNCTION_INFO_V1(check_master_standby_dbid);
Datum
check_master_standby_dbid(PG_FUNCTION_ARGS)
{
int result;
result = master_standby_dbid();
PG_RETURN_OID(result);
}
PG_FUNCTION_INFO_V1(check_my_mirror_dbid);
Datum
check_my_mirror_dbid(PG_FUNCTION_ARGS)
{
int result;
result = my_mirror_dbid();
PG_RETURN_OID(result);
}
PG_FUNCTION_INFO_V1(check_dbid_get_dbinfo);
Datum
check_dbid_get_dbinfo(PG_FUNCTION_ARGS)
{
int16 result;
int dbid = PG_GETARG_INT16(0);
CdbComponentDatabaseInfo *i = NULL;
i = dbid_get_dbinfo(dbid);
result = i->dbid;
PG_RETURN_INT16(result);
}
PG_FUNCTION_INFO_V1(check_contentid_get_dbid);
Datum
check_contentid_get_dbid(PG_FUNCTION_ARGS)
{
int16 contentid = PG_GETARG_INT16(0);
char role = PG_GETARG_CHAR(1);
bool getPreferredRoleNotCurrentRole = PG_GETARG_BOOL(2);
int16 result;
result = contentid_get_dbid(contentid, role, getPreferredRoleNotCurrentRole);
PG_RETURN_INT16(result);
}
PG_FUNCTION_INFO_V1(check_gp_activate_standby);
Datum
check_gp_activate_standby(PG_FUNCTION_ARGS)
{
/* Pretend as if I'm a standby. The dbid is given as an argument. */
GpIdentity.dbid = PG_GETARG_INT16(0);
/* There is no DirectFunctionCall0 */
DirectFunctionCall1(gp_activate_standby, Int32GetDatum(0));
PG_RETURN_NULL();
}
PG_FUNCTION_INFO_V1(check_GetResqueueName);
Datum
check_GetResqueueName(PG_FUNCTION_ARGS)
{
Oid resqueueOid = PG_GETARG_OID(0);
PG_RETURN_TEXT_P(cstring_to_text(GetResqueueName(resqueueOid)));
}
......@@ -7,7 +7,6 @@
/misc.out
/tablespace.out
caql.out
constraints_optimizer.out
aocs.out
appendonly.out
......
-- ---------------------------------------------------------------------
-- caqlcov
--
-- This test aims to cover caql calls that are missed in the rest of
-- the installcheck-good schedule. Since this is not a sourcified
-- file, add to caql.source if you need sourcify.
-- This test aims to cover syscache calls that are missed in the rest of
-- the installcheck-good schedule.
-- ---------------------------------------------------------------------
-- start_ignore
drop schema if exists tschema;
......
-- --------------------------------------
-- caql
-- --------------------------------------
-- *********************************************************************
-- *********************************************************************
-- This script will produce diffs if you add or change caql/cql
-- definitions in src/. If you want to change the results, you must
-- make the changes in regress/output/caql.source, not
-- regress/expected, and use gpsourcify.pl to generate a ".source"
-- file.
--
-- From the regress directory invoke the command:
--
-- gpsourcify.pl results/caql.out > output/caql.source
--
-- *********************************************************************
-- *********************************************************************
-- *********************************************************************
-- *********************************************************************
-- extra catalog coverage
create user caql_luser;
create user caql_super_luser SUPERUSER;
set role caql_luser;
-- create a role as an unpriv user (shouldn't work)
create role caql_fff;
drop role if exists caql_fff ;
-- create and comment on an operator and an operator class
create operator #~#~#
(
rightarg = int8, -- right unary
procedure = numeric_fac
);
comment on operator #~#~# (int8, NONE) is 'bad operator';
comment on operator #~#~# (NONE, int8) is 'bad operator';
drop operator #~#~# (int8, NONE);
drop operator #~#~# (NONE, int8 );
-- try (and fail) renaming a language
alter LANGUAGE plpgsql RENAME TO funky;
select has_language_privilege('caql_luser', 14, 'USAGE');
select has_language_privilege('caql_luser', 14, 'USAGE WITH GRANT OPTION');
-- need privs to create op class and comment on language
set role caql_super_luser;
create operator class myi2ops
for type int2 using btree as
OPERATOR 1 < ,
OPERATOR 2 <= ,
OPERATOR 3 = ,
OPERATOR 4 >= ,
OPERATOR 5 > ,
FUNCTION 1 btint2cmp(int2, int2);
comment on operator class myi2ops using btree is 'wow';
drop operator class myi2ops using btree;
comment on language sql is 'wow';
comment on language sql is null;
select has_language_privilege('caql_luser', 14, 'USAGE');
select has_language_privilege('caql_luser', 14, 'USAGE WITH GRANT OPTION');
select has_language_privilege('caql_super_luser', 14, 'USAGE');
select has_language_privilege('caql_super_luser', 14, 'USAGE WITH GRANT OPTION');
-- no privs
set role caql_luser;
CREATE or replace FUNCTION caql_timestamp(float8)
RETURNS pg_catalog.timestamptz
LANGUAGE sql CONTAINS SQL
IMMUTABLE STRICT AS $$select ('epoch'::timestamptz + $1 * '1 second'::interval)$$;
CREATE or replace FUNCTION caql_timestamp(float8)
RETURNS pg_catalog.timestamptz
LANGUAGE sql CONTAINS SQL
IMMUTABLE STRICT AS $$select ('epoch'::timestamptz + $1 * '1 second'::interval)$$;
CREATE TABLE log_table (tstamp timestamp default timeofday()::timestamp);
CREATE TABLE main_table (a int, b int);
CREATE FUNCTION trigger_func() RETURNS trigger LANGUAGE plpgsql NO SQL AS '
BEGIN
RAISE NOTICE ''trigger_func() called: action = %, when = %, level = %'',
TG_OP, TG_WHEN, TG_LEVEL;
RETURN NULL;
END;';
CREATE TRIGGER before_ins_stmt_trig BEFORE INSERT ON main_table
FOR EACH STATEMENT EXECUTE PROCEDURE trigger_func();
comment on trigger before_ins_stmt_trig on main_table is 'wow';
-- rename of columns recurses to triggers
alter table main_table rename a to aaa;
alter table main_table rename b to bbb;
drop trigger before_ins_stmt_trig on main_table;
create table rtest_t1 (a int4, b int4);
create view rtest_v1 as select * from rtest_t1;
create rule rtest_v1_ins as on insert to rtest_v1 do instead
insert into rtest_t1 values (new.a, new.b);
comment on rule rtest_v1_ins is 'wow';
comment on rule rtest_v1_ins on rtest_v1 is 'ok';
set constraints all deferred;
set constraints funky deferred;
-- start_ignore
-- need to ignore warnings for objects caql_luser does not own...
vacuum;
-- end_ignore
vacuum main_table;
vacuum full main_table;
analyze main_table;
CREATE TYPE funkytype AS (
i int
);
CREATE FUNCTION toint(funkytype)
RETURNS integer
STRICT IMMUTABLE LANGUAGE SQL CONTAINS SQL AS
'SELECT 1;';
CREATE CAST (funkytype AS integer) WITH FUNCTION toint(funkytype);
comment on cast (funkytype AS integer) is 'wow';
grant all on language sql to caql_luser;
set role caql_super_luser;
grant all on language sql to caql_luser;
revoke usage on language sql from caql_luser;
CREATE FUNCTION caql_bootstrap_regproc() RETURNS text
AS '@abs_builddir@/regress@DLSUFFIX@', 'caql_bootstrap_regproc'
LANGUAGE C READS SQL DATA;
SELECT caql_bootstrap_regproc();
DROP FUNCTION caql_bootstrap_regproc();
CREATE FUNCTION check_get_atttypmod(oid, int) RETURNS integer
AS '@abs_builddir@/regress@DLSUFFIX@', 'check_get_atttypmod'
LANGUAGE C READS SQL DATA;
SELECT * FROM check_get_atttypmod(1255, 1);
DROP FUNCTION check_get_atttypmod(oid, int);
CREATE FUNCTION check_get_opname(oid) RETURNS text
AS '@abs_builddir@/regress@DLSUFFIX@', 'check_get_opname'
LANGUAGE C READS SQL DATA;
SELECT * FROM check_get_opname(15);
SELECT * FROM check_get_opname(0);
DROP FUNCTION check_get_opname(oid);
CREATE FUNCTION check_get_typ_typrelid(oid) RETURNS oid
AS '@abs_builddir@/regress@DLSUFFIX@', 'check_get_typ_typrelid'
LANGUAGE C READS SQL DATA;
SELECT * FROM check_get_typ_typrelid(1000);
DROP FUNCTION check_get_typ_typrelid(oid);
CREATE FUNCTION check_get_base_element_type(oid) RETURNS oid
AS '@abs_builddir@/regress@DLSUFFIX@', 'check_get_base_element_type'
LANGUAGE C READS SQL DATA;
SELECT * FROM check_get_base_element_type(1000);
DROP FUNCTION check_get_base_element_type(oid);
CREATE FUNCTION check_SPI_gettype(int4) RETURNS text
AS '@abs_builddir@/regress@DLSUFFIX@', 'check_SPI_gettype'
LANGUAGE C READS SQL DATA;
SELECT * FROM check_SPI_gettype(1);
DROP FUNCTION check_SPI_gettype(int4);
CREATE FUNCTION check_master_standby_dbid() RETURNS int2
AS '@abs_builddir@/regress@DLSUFFIX@', 'check_master_standby_dbid'
LANGUAGE C READS SQL DATA;
SELECT * FROM check_master_standby_dbid();
DROP FUNCTION check_master_standby_dbid();
CREATE FUNCTION check_my_mirror_dbid() RETURNS int2
AS '@abs_builddir@/regress@DLSUFFIX@', 'check_my_mirror_dbid'
LANGUAGE C READS SQL DATA;
SELECT * FROM check_my_mirror_dbid();
DROP FUNCTION check_my_mirror_dbid();
CREATE FUNCTION check_dbid_get_dbinfo(int2) RETURNS int2
AS '@abs_builddir@/regress@DLSUFFIX@', 'check_dbid_get_dbinfo'
LANGUAGE C READS SQL DATA;
SELECT * FROM check_dbid_get_dbinfo(1::int2);
DROP FUNCTION check_dbid_get_dbinfo(int2);
CREATE FUNCTION check_contentid_get_dbid(int2, "char", bool) RETURNS int2
AS '@abs_builddir@/regress@DLSUFFIX@', 'check_contentid_get_dbid'
LANGUAGE C READS SQL DATA;
SELECT * FROM check_contentid_get_dbid(-1::int2, 'p', true);
SELECT * FROM check_contentid_get_dbid(-1::int2, 'p', false);
DROP FUNCTION check_contentid_get_dbid(int2, "char", bool);
-- ---------------------------------------------------------------------
-- coverage for segadmin.c/filespace.c
-- ---------------------------------------------------------------------
CREATE FUNCTION check_gp_activate_standby(int2) RETURNS text
AS '@abs_builddir@/regress@DLSUFFIX@', 'check_gp_activate_standby'
LANGUAGE C READS SQL DATA;
-- we need configuration, but reading the table would hold a lock on it,
-- which blocks the subsequent remove/add operation.
create table gp_segment_configuration_copy as select * from gp_segment_configuration;
create or replace function gp_add_remove_segment() returns text as $$
declare
lastseg_conf record;
lastseg_fsentry record;
newdbid smallint;
result text;
begin
select * into lastseg_conf
from gp_segment_configuration_copy
where role = 'p' order by dbid desc limit 1;
select * into lastseg_fsentry
from pg_filespace_entry
where fsedbid = lastseg_conf.dbid;
set gp_role = utility;
select gp_add_segment(
lastseg_conf.hostname,
lastseg_conf.address,
lastseg_conf.port + 1,
array['pg_system', lastseg_fsentry.fselocation])
into newdbid;
perform gp_remove_segment(newdbid);
result := 'ok';
return result;
end;
$$ language plpgsql READS SQL DATA;
select gp_add_remove_segment();
-- restore from utility mode
\c regression
drop function gp_add_remove_segment();
create or replace function gp_add_remove_master_standby() returns int2 as $$
declare
master_conf record;
master_fsentry record;
result int2;
begin
select * into master_conf
from gp_segment_configuration_copy
where role = 'p' and dbid = 1;
select * into master_fsentry
from pg_filespace_entry
where fsedbid = 1;
set gp_role = utility;
perform gp_add_master_standby(
master_conf.hostname,
master_conf.address,
array[['pg_system', master_fsentry.fselocation]]);
perform gp_remove_master_standby();
-- for gp_activate_standby, add it once more
select gp_add_master_standby(
master_conf.hostname,
master_conf.address,
array[['pg_system', master_fsentry.fselocation]])
into result;
return result;
end;
$$ language plpgsql READS SQL DATA;
select check_gp_activate_standby(gp_add_remove_master_standby());
-- restore from utility mode
\c regression
drop table gp_segment_configuration_copy;
drop function gp_add_remove_master_standby();
DROP FUNCTION check_gp_activate_standby(int2);
CREATE FUNCTION check_GetResqueueName(oid) RETURNS text
AS '@abs_builddir@/regress@DLSUFFIX@', 'check_GetResqueueName'
LANGUAGE C READS SQL DATA;
select check_GetResqueueName(6055);
DROP FUNCTION check_GetResqueueName(oid);
-- cleanup (as caql_super_luser)
drop type funkytype;
-- need the cascade to the cast
drop type funkytype cascade;
-- should be superfluous after the cascade
DROP CAST (funkytype AS integer) ;
drop table main_table cascade;
drop table log_table cascade;
drop table rtest_t1 cascade;
drop function trigger_func();
drop function caql_timestamp(float8);
-- back to "normal"
reset role;
drop role caql_luser;
drop role caql_super_luser;
此差异已折叠。
......@@ -12,7 +12,6 @@ upg2.sql
gptokencheck.sql
bkuprestore_advance.sql
bkuprestore_cleanup.sql
caql.sql
aocs.sql
appendonly.sql
bkup_bkupdb.sql
......
-- ---------------------------------------------------------------------
-- caqlcov
--
-- This test aims to cover caql calls that are missed in the rest of
-- the installcheck-good schedule. Since this is not a sourcified
-- file, add to caql.source if you need sourcify.
-- This test aims to cover syscache calls that are missed in the rest of
-- the installcheck-good schedule.
-- ---------------------------------------------------------------------
-- start_ignore
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册