static SV *
dbih_get_attr_k(h, keysv, dbikey)			/* XXX split into dr/db/st funcs */
    SV *h;
    SV *keysv;
    int dbikey;
{
    dPERINTERP;
    /* dTHR; not currently needed */
    D_imp_xxh(h);
    STRLEN lna;
    STRLEN keylen;
    char  *key = SvPV(keysv, keylen);
    int    htype = DBIc_TYPE(imp_xxh);
    SV	*valuesv = Nullsv;
    int    cacheit = FALSE;
    char *p;
    int i;
    SV	*sv;
    SV	**svp;

    /* DBI quick_FETCH will service some requests (e.g., cached values)	*/

    if (htype == DBIt_ST) {
        switch (*key) {

          case 'D':
            if (keylen==8 && strEQ(key, "Database")) {
                /* this is here but is, sadly, not called because
                 * not-preloading them into the handle attrib cache caused
                 * wierdness in t/proxy.t that I never got to the bottom
                 * of. One day maybe.  */
                D_imp_from_child(imp_dbh, imp_dbh_t, imp_xxh);
                valuesv = newRV((SV*)DBIc_MY_H(imp_dbh));
                cacheit = FALSE;  /* else create ref loop */
            }
            break;

          case 'N':
            if ((keylen==7 || keylen==9 || keylen==12)
                && strnEQ(key, "NAME_", 5)
                && (	(keylen==9 && strEQ(key, "NAME_hash"))
                      ||	((key[5]=='u' || key[5]=='l') && key[6] == 'c'
                               && (!key[7] || strnEQ(&key[7], "_hash", 5)))
                    )
                ) {
                D_imp_sth(h);
                AV *name_av = NULL;
                valuesv = &sv_undef;

                /* fetch from tied outer handle to trigger FETCH magic */
                svp = hv_fetch((HV*)DBIc_MY_H(imp_sth), "NAME",4, FALSE);
                sv = (svp) ? *svp : &sv_undef;
                if (SvGMAGICAL(sv))	/* resolve the magic		*/
                    mg_get(sv);       /* can core dump in 5.004   */
                name_av = (AV*)SvRV(sv);

                if (sv && name_av) {
                    char *name;
                    int upcase = (key[5] == 'u');
                    AV *av = Nullav;
                    HV *hv = Nullhv;
                    if (strEQ(&key[strlen(key)-5], "_hash"))
                        hv = newHV();
                    else av = newAV();
                    i = DBIc_NUM_FIELDS(imp_sth);
                    assert(i == AvFILL(name_av)+1);
                    while (--i >= 0) {
                        sv = newSVsv(AvARRAY(name_av)[i]);
                        name = SvPV(sv,lna);
                        if (key[5] != 'h') {	/* "NAME_hash" */
                            for (p = name; p && *p; ++p) {
#ifdef toUPPER_LC
                                *p = (upcase) ? toUPPER_LC(*p) : toLOWER_LC(*p);
#else
                                *p = (upcase) ? toUPPER(*p) : toLOWER(*p);
#endif
                            }
                        }
                        if (av)
                            av_store(av, i, sv);
                        else
                            hv_store(hv, name, SvCUR(sv), newSViv(i), 0);
                    }
                    valuesv = newRV(sv_2mortal( (av ? (SV*)av : (SV*)hv) ));
                    cacheit = TRUE;	/* can't change */
                }
            }
            else if (keylen==13 && strEQ(key, "NUM_OF_FIELDS")) {
                D_imp_sth(h);
                valuesv = newSViv(DBIc_NUM_FIELDS(imp_sth));
                if (DBIc_NUM_FIELDS(imp_sth) > 0)
                    cacheit = TRUE;	/* can't change once set */
            }
            else if (keylen==13 && strEQ(key, "NUM_OF_PARAMS")) {
                D_imp_sth(h);
                valuesv = newSViv(DBIc_NUM_PARAMS(imp_sth));
                cacheit = TRUE;	/* can't change */
            }
            break;

          case 'R':
            if (keylen==11 && strEQ(key, "RowsInCache")) {
                valuesv = &sv_undef;
            }
            break;
        }
        
    }
    else if (htype == DBIt_DB) {
        /* this is here but is, sadly, not called because
         * not-preloading them into the handle attrib cache caused
         * wierdness in t/proxy.t that I never got to the bottom
         * of. One day maybe.  */
        if (keylen==6 && strEQ(key, "Driver")) {
            D_imp_from_child(imp_dbh, imp_dbh_t, imp_xxh);
            valuesv = newRV((SV*)DBIc_MY_H(imp_dbh));
            cacheit = FALSE;  /* else create ref loop */
        }
    }

    if (valuesv == Nullsv && htype <= DBIt_DB) {
        if (keylen==10  && strEQ(key, "CachedKids")) {
            /* just return undef for sth */
	    D_imp_dbh(h);
	    HV *hv = DBIc_CACHED_KIDS(imp_dbh);
	    valuesv = (hv) ? newRV((SV*)hv) : &sv_undef;
	}
        else if (keylen==10 && strEQ(key, "AutoCommit")) {
            valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_AutoCommit));
        }
    }

    if (valuesv == Nullsv) {
        switch (*key) {
          case 'A':
            if (keylen==6 && strEQ(key, "Active")) {
                valuesv = boolSV(DBIc_ACTIVE(imp_xxh));
            }
            else if (keylen==10 && strEQ(key, "ActiveKids")) {
                valuesv = newSViv(DBIc_ACTIVE_KIDS(imp_xxh));
            }
            break;
            
          case 'B':
            if (keylen==9 && strEQ(key, "BegunWork")) {
                valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_BegunWork));
            }
            break;

          case 'C':
            if (keylen==10 && strEQ(key, "CachedKids")) {
                valuesv = &sv_undef;
            }
            else if (keylen==10 && strEQ(key, "CompatMode")) {
                valuesv = boolSV(DBIc_COMPAT(imp_xxh));
            }
            else if (keylen==10 && strEQ(key, "ChopBlanks")) {
                valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_ChopBlanks));
            }
            break;

          case 'I':
            if (keylen==15 && strEQ(key, "InactiveDestroy")) {
                valuesv = boolSV(DBIc_IADESTROY(imp_xxh));
            }
            break;

          case 'K':
            if (keylen==4 && strEQ(key, "Kids")) {
                valuesv = newSViv(DBIc_KIDS(imp_xxh));
            }
            break;

          case 'L':
            if (keylen==11 && strEQ(key, "LongReadLen")) {
                valuesv = newSVnv((double)DBIc_LongReadLen(imp_xxh));
            }
            else if (keylen==11 && strEQ(key, "LongTruncOk")) {
                valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_LongTruncOk));
            }
            break;

          case 'M':
            if (keylen==10 && strEQ(key, "MultiThread")) {
                valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_MultiThread));
            }
            else if (keylen==11  && strEQ(key, "MultiThread")) {
                valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_BegunWork));
            }
            break;

          case 'P':
            if (keylen==10 && strEQ(key, "PrintError")) {
                valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_PrintError));
            }
            break;

          case 'R':
            if (keylen==10 && strEQ(key, "RaiseError")) {
                valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_RaiseError));
            }
            else if (keylen==12 && strEQ(key, "RowCacheSize")) {
                valuesv = &sv_undef;
            }
            break;

          case 'S':
            if (keylen==18 && strEQ(key, "ShowErrorStatement")) {
                valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_ShowErrorStatement));
            }
            break;

          case 'T':
            if (keylen==4 && strEQ(key, "Type")) {
                char *type = dbih_htype_name(htype);
                valuesv = newSVpv(type,0);
                cacheit = TRUE;	/* can't change */
            }
            else if (keylen==10  && strEQ(key, "TraceLevel")) {
                valuesv = newSViv(DBIc_DEBUGIV(imp_xxh));
            }
            else if (keylen==5  && strEQ(key, "Taint")) {
                valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_TaintIn) &&
                                 DBIc_has(imp_xxh,DBIcf_TaintOut));
            }
            else if (keylen==7  && strEQ(key, "TaintIn")) {
                valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_TaintIn));
            }
            else if (keylen==8  && strEQ(key, "TaintOut")) {
                valuesv = boolSV(DBIc_has(imp_xxh,DBIcf_TaintOut));
            }
            break;

          case 'W':
            if (keylen==4  && strEQ(key, "Warn")) {
                valuesv = boolSV(DBIc_WARN(imp_xxh));
            }
            break;
        }
    }

    /* finally check the actual hash just in case	*/
    if (valuesv == Nullsv) {
	svp = hv_fetch((HV*)SvRV(h), key, keylen, FALSE);
	if (svp)
	    valuesv = *svp;
	else if (!isUPPER(*key))	/* dbd_*, private_* etc */
	    valuesv = &sv_undef;
	else if (	(*key=='H' && strEQ(key, "HandleError"))
		||	(*key=='P' && strEQ(key, "ParamValues"))
		||	(*key=='P' && strEQ(key, "Profile"))
		||	(*key=='C' && strEQ(key, "CursorName"))
	)
	    valuesv = &sv_undef;
	else
	    croak("Can't get %s->{%s}: unrecognised attribute",neatsvpv(h,0),key);
    }
    
    if (cacheit) {
	svp = hv_fetch((HV*)SvRV(h), key, keylen, TRUE);
	sv = *svp;
	*svp = SvREFCNT_inc(valuesv);
	sv_free(sv);
    }
    if (DBIS->debug >= 3)
	PerlIO_printf(DBILOGFP,"    .. FETCH %s %s = %s%s\n", neatsvpv(h,0),
	    neatsvpv(keysv,0), neatsvpv(valuesv,0), cacheit?" (cached)":"");
    if (valuesv == &sv_yes || valuesv == &sv_no || valuesv == &sv_undef)
	return valuesv;	/* no need to mortalize yes or no */
    return sv_2mortal(valuesv);
}

