/* * 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 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; 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 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; dummyrel.rd_id = AttributeRelationId; hash_cookie = cq_lookup(query, strlen(query), pcql); /* * Add explicit relation, and set indexOK = true and syscache = false. */ pCtx = caql_addrel(cqclr(&context), &dummyrel); pCtx = caql_indexOK(pCtx, true); pCtx = caql_syscache(pCtx, false); /* 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 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 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); 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); }
/* ---------------------------------------------------------------- * 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); if (pchn->bCount) elog(ERROR, "Cannot scan: %s -- COUNTing or DELETing\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_begin_CacheList() * Return a catclist * * In general, catquery will choose the syscache when the cql * statement contains an equality predicate on *all* of the syscache * primary key index columns, eg: * * cql("SELECT * FROM pg_amop WHERE amopopr = :1 and amopclaid = :2 ") * * will use the AMOPOPID syscache with index * AccessMethodOperatorIndexId. However, the cql statement for a * list-search requires an equality predicate on a subset of the * initial columns of the index, with *all* of the index columns * specified in an ORDER BY clause, eg: * * cql("SELECT * FROM pg_amop WHERE amopopr = :1 " * " ORDER BY amopopr, amopclaid ") * * will use a syscache list-search if this cql statement is an * argument to caql_begin_CacheList(). However, the syscache will * *not* be used for this statement if it is supplied for * caql_beginscan(), since SearchSysCache() can only return (at most) * a single tuple. * * NOTE: caql_begin_CacheList() will assert (Insist!) at runtime if * the cql statement does not map to a syscache lookup. * NOTE: it may be possible to "collapse" this API into the existing * beginscan/getnext/endscan. * ---------------------------------------------------------------- */ struct catclist * caql_begin_CacheList(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; cqContext cqc; if (NULL == pchn) elog(ERROR, "invalid caql string: %s\nfile: %s, line %d", caql_str, filenam, lineno); Assert(!pchn->bInsert); /* INSERT not allowed */ Assert(!pchn->bUpdate); /* UPDATE not allowed */ Assert(!pchn->bDelete); /* DELETE not allowed */ /* use the provided context, or provide a clean local ctx */ if (pCtx0) pCtx = pCtx0; else pCtx = cqclr(&cqc); /* NOTE: for case of cache list search, we must use syscache */ pCtx->cq_bCacheList = true; pCtx = caql_switch(pchn, pCtx, pcql); /* NOTE: caql_switch frees the pcql */ /* NOTE: must use the SysCache */ Insist (pCtx->cq_usesyscache); caql_heapclose(pCtx); return SearchSysCacheKeyArrayList(pCtx->cq_cacheId, pCtx->cq_NumKeys, pCtx->cq_cacheKeys); }
/* ---------------------------------------------------------------- * 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_getcount() * Perform COUNT(*) or DELETE * ---------------------------------------------------------------- */ int caql_getcount(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; cqContext cqc; HeapTuple tuple; Relation rel; int ii = 0; 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 */ rel = pCtx->cq_heap_rel; /* use the SysCache */ if (pCtx->cq_usesyscache) { tuple = SearchSysCacheKeyArray(pCtx->cq_cacheId, pCtx->cq_NumKeys, pCtx->cq_cacheKeys); if (HeapTupleIsValid(tuple)) { ii++; pCtx->cq_lasttup = tuple; if (pchn->bDelete) simple_heap_delete(rel, &tuple->t_self); ReleaseSysCache(tuple); /* only one */ } caql_heapclose(pCtx); return (ii); } while (HeapTupleIsValid(tuple = systable_getnext(pCtx->cq_sysScan))) { if (HeapTupleIsValid(tuple) && pchn->bDelete) { pCtx->cq_lasttup = tuple; if (pchn->bDelete) simple_heap_delete(rel, &tuple->t_self); } ii++; } systable_endscan(pCtx->cq_sysScan); caql_heapclose(pCtx); return (ii); }
/* ---------------------------------------------------------------- * 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 */
/* ---------------------------------------------------------------- * 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_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 */
/* * 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; 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); }