Argonne National Laboratory

Experimental Physics and
Industrial Control System

2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  <2020 Index 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  <2020
<== Date ==> <== Thread ==>

Subject: [Merge] ~bfrk/epics-base:write-filters into epics-base:7.0
From: Ben Franksen via Core-talk <core-talk at aps.anl.gov>
To: mp+381125 at code.launchpad.net
Date: Tue, 24 Mar 2020 19:28:23 -0000
Ben Franksen has proposed merging ~bfrk/epics-base:write-filters into epics-base:7.0.

Requested reviews:
  EPICS Core Developers (epics-core)

For more details, see:
https://code.launchpad.net/~bfrk/epics-base/+git/epics-base/+merge/381125

This is just the initial refactor, no new features yet.
-- 
Your team EPICS Core Developers is requested to review the proposed merge of ~bfrk/epics-base:write-filters into epics-base:7.0.
diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md
index bdd2468..681495e 100644
--- a/documentation/RELEASE_NOTES.md
+++ b/documentation/RELEASE_NOTES.md
@@ -36,6 +36,15 @@ As long as all support modules and IOCs are rebuilt from source after updating
 them to use this release of EPICS Base, these changes should not have any
 affect.
 
+### Filters in database links
+
+Input links can now use filters, most importantly array element and sub array
+access, even if they are not channel access links.
+
+### ai Soft Channel support
+
+The Soft Channel device support for ai records now returns failure when
+fetching the INP link fails.
 
 ### logClient reliability
 
diff --git a/modules/database/src/ioc/db/dbAccess.c b/modules/database/src/ioc/db/dbAccess.c
index 19f6038..a6c31f2 100644
--- a/modules/database/src/ioc/db/dbAccess.c
+++ b/modules/database/src/ioc/db/dbAccess.c
@@ -34,7 +34,7 @@
 #include "errlog.h"
 #include "errMdef.h"
 
-#include "epicsExport.h" /* #define epicsExportSharedSymbols */
+#include "epicsExport.h"                /* #define epicsExportSharedSymbols */
 #include "caeventmask.h"
 #include "callback.h"
 #include "dbAccessDefs.h"
@@ -63,7 +63,7 @@
 #include "special.h"
 
 epicsShareDef struct dbBase *pdbbase = 0;
-epicsShareDef volatile int interruptAccept=FALSE;
+epicsShareDef volatile int interruptAccept = FALSE;
 
 epicsShareDef int dbAccessDebugPUTF = 0;
 epicsExportAddress(int, dbAccessDebugPUTF);
@@ -73,24 +73,24 @@ epicsExportAddress(int, dbAccessDebugPUTF);
 epicsShareDef DB_LOAD_RECORDS_HOOK_ROUTINE dbLoadRecordsHook = NULL;
 
 static short mapDBFToDBR[DBF_NTYPES] = {
-    /* DBF_STRING   => */    DBR_STRING,
-    /* DBF_CHAR     => */    DBR_CHAR,
-    /* DBF_UCHAR    => */    DBR_UCHAR,
-    /* DBF_SHORT    => */    DBR_SHORT,
-    /* DBF_USHORT   => */    DBR_USHORT,
-    /* DBF_LONG     => */    DBR_LONG,
-    /* DBF_ULONG    => */    DBR_ULONG,
-    /* DBF_INT64    => */    DBR_INT64,
-    /* DBF_UINT64   => */    DBR_UINT64,
-    /* DBF_FLOAT    => */    DBR_FLOAT,
-    /* DBF_DOUBLE   => */    DBR_DOUBLE,
-    /* DBF_ENUM,    => */    DBR_ENUM,
-    /* DBF_MENU,    => */    DBR_ENUM,
-    /* DBF_DEVICE   => */    DBR_ENUM,
-    /* DBF_INLINK   => */    DBR_STRING,
-    /* DBF_OUTLINK  => */    DBR_STRING,
-    /* DBF_FWDLINK  => */    DBR_STRING,
-    /* DBF_NOACCESS => */    DBR_NOACCESS
+    /* DBF_STRING   => */ DBR_STRING,
+    /* DBF_CHAR     => */ DBR_CHAR,
+    /* DBF_UCHAR    => */ DBR_UCHAR,
+    /* DBF_SHORT    => */ DBR_SHORT,
+    /* DBF_USHORT   => */ DBR_USHORT,
+    /* DBF_LONG     => */ DBR_LONG,
+    /* DBF_ULONG    => */ DBR_ULONG,
+    /* DBF_INT64    => */ DBR_INT64,
+    /* DBF_UINT64   => */ DBR_UINT64,
+    /* DBF_FLOAT    => */ DBR_FLOAT,
+    /* DBF_DOUBLE   => */ DBR_DOUBLE,
+    /* DBF_ENUM,    => */ DBR_ENUM,
+    /* DBF_MENU,    => */ DBR_ENUM,
+    /* DBF_DEVICE   => */ DBR_ENUM,
+    /* DBF_INLINK   => */ DBR_STRING,
+    /* DBF_OUTLINK  => */ DBR_STRING,
+    /* DBF_FWDLINK  => */ DBR_STRING,
+    /* DBF_NOACCESS => */ DBR_NOACCESS
 };
 
 /*
@@ -109,217 +109,219 @@ void dbSpcAsRegisterCallback(SPC_ASCALLBACK func)
     spcAsCallback = func;
 }
 
-long dbPutSpecial(DBADDR *paddr,int pass)
+long dbPutSpecial(DBADDR *paddr, int pass)
 {
-    long int	(*pspecial)()=NULL;
-    rset        *prset;
-    dbCommon 	*precord = paddr->precord;
-    long	status=0;
-    long	special=paddr->special;
+    long int (*pspecial) () = NULL;
+    rset *prset;
+    dbCommon *precord = paddr->precord;
+    long status = 0;
+    long special = paddr->special;
 
     prset = dbGetRset(paddr);
-    if(special<100) { /*global processing*/
-	if((special==SPC_NOMOD) && (pass==0)) {
-	    status = S_db_noMod;
-	    recGblDbaddrError(status,paddr,"dbPut");
-	    return(status);
-	}else if(special==SPC_SCAN){
-	    if(pass==0)
-		scanDelete(precord);
-	    else
-		scanAdd(precord);
-	}else if((special==SPC_AS) && (pass==1)) {
-            if(spcAsCallback) (*spcAsCallback)(precord);
-	}
-    }else {
-	if( prset && (pspecial = (prset->special))) {
-	    status=(*pspecial)(paddr,pass);
-	    if(status) return(status);
-	} else if(pass==0){
-	    recGblRecSupError(S_db_noSupport,paddr,"dbPut", "special");
-	    return(S_db_noSupport);
-	}
+    if (special < 100) {                /*global processing */
+        if ((special == SPC_NOMOD) && (pass == 0)) {
+            status = S_db_noMod;
+            recGblDbaddrError(status, paddr, "dbPut");
+            return (status);
+        } else if (special == SPC_SCAN) {
+            if (pass == 0)
+                scanDelete(precord);
+            else
+                scanAdd(precord);
+        } else if ((special == SPC_AS) && (pass == 1)) {
+            if (spcAsCallback)
+                (*spcAsCallback) (precord);
+        }
+    } else {
+        if (prset && (pspecial = (prset->special))) {
+            status = (*pspecial) (paddr, pass);
+            if (status)
+                return (status);
+        } else if (pass == 0) {
+            recGblRecSupError(S_db_noSupport, paddr, "dbPut", "special");
+            return (S_db_noSupport);
+        }
     }
-    return(0);
+    return (0);
 }
 
 static void get_enum_strs(DBADDR *paddr, char **ppbuffer,
-    rset *prset,long	*options)
+    rset * prset, long *options)
 {
-	short		field_type=paddr->field_type;
-	dbFldDes	*pdbFldDes = paddr->pfldDes;
-	dbMenu		*pdbMenu;
-	dbDeviceMenu	*pdbDeviceMenu;
-	char		**papChoice;
-	unsigned long	no_str;
-	char		*ptemp;
-	struct dbr_enumStrs *pdbr_enumStrs=(struct dbr_enumStrs*)(*ppbuffer);
+    short field_type = paddr->field_type;
+    dbFldDes *pdbFldDes = paddr->pfldDes;
+    dbMenu *pdbMenu;
+    dbDeviceMenu *pdbDeviceMenu;
+    char **papChoice;
+    unsigned long no_str;
+    char *ptemp;
+    struct dbr_enumStrs *pdbr_enumStrs = (struct dbr_enumStrs *)(*ppbuffer);
     unsigned int i;
 
-	memset(pdbr_enumStrs,'\0',dbr_enumStrs_size);
-	switch(field_type) {
-		case DBF_ENUM:
-		    if( prset && prset->get_enum_strs ) {
-			(*prset->get_enum_strs)(paddr,pdbr_enumStrs);
-		    } else {
-			*options = (*options)^DBR_ENUM_STRS;/*Turn off option*/
-		    }
-		    break;
-		case DBF_MENU:
-		    pdbMenu = (dbMenu *)pdbFldDes->ftPvt;
-		    no_str = pdbMenu->nChoice;
-		    papChoice= pdbMenu->papChoiceValue;
-		    goto choice_common;
-		case DBF_DEVICE:
-		    pdbDeviceMenu = (dbDeviceMenu *)pdbFldDes->ftPvt;
-                    if(!pdbDeviceMenu) {
-                        *options = (*options)^DBR_ENUM_STRS;/*Turn off option*/
-                        break;
-                    }
-		    no_str = pdbDeviceMenu->nChoice;
-		    papChoice = pdbDeviceMenu->papChoice;
-		    goto choice_common;
-choice_common:
-		    i = sizeof(pdbr_enumStrs->strs)/
-			sizeof(pdbr_enumStrs->strs[0]);
-		    if(i<no_str) no_str = i;
-		    pdbr_enumStrs->no_str = no_str;
-		    ptemp = &(pdbr_enumStrs->strs[0][0]);
-		    for (i=0; i<no_str; i++) {
-			if(papChoice[i]==NULL) *ptemp=0;
-			else {
-			    strncpy(ptemp,papChoice[i],
-				sizeof(pdbr_enumStrs->strs[0]));
-			    *(ptemp+sizeof(pdbr_enumStrs->strs[0])-1) = 0;
-			}
-			ptemp += sizeof(pdbr_enumStrs->strs[0]);
-		    }
-		    break;
-		default:
-		    *options = (*options)^DBR_ENUM_STRS;/*Turn off option*/
-		    break;
-	}
-	*ppbuffer = ((char *)*ppbuffer) + dbr_enumStrs_size;
-	return;
+    memset(pdbr_enumStrs, '\0', dbr_enumStrs_size);
+    switch (field_type) {
+    case DBF_ENUM:
+        if (prset && prset->get_enum_strs) {
+            (*prset->get_enum_strs) (paddr, pdbr_enumStrs);
+        } else {
+            *options = (*options) ^ DBR_ENUM_STRS; /*Turn off option */
+        }
+        break;
+    case DBF_MENU:
+        pdbMenu = (dbMenu *) pdbFldDes->ftPvt;
+        no_str = pdbMenu->nChoice;
+        papChoice = pdbMenu->papChoiceValue;
+        goto choice_common;
+    case DBF_DEVICE:
+        pdbDeviceMenu = (dbDeviceMenu *) pdbFldDes->ftPvt;
+        if (!pdbDeviceMenu) {
+            *options = (*options) ^ DBR_ENUM_STRS; /*Turn off option */
+            break;
+        }
+        no_str = pdbDeviceMenu->nChoice;
+        papChoice = pdbDeviceMenu->papChoice;
+        goto choice_common;
+      choice_common:
+        i = sizeof(pdbr_enumStrs->strs) / sizeof(pdbr_enumStrs->strs[0]);
+        if (i < no_str)
+            no_str = i;
+        pdbr_enumStrs->no_str = no_str;
+        ptemp = &(pdbr_enumStrs->strs[0][0]);
+        for (i = 0; i < no_str; i++) {
+            if (papChoice[i] == NULL)
+                *ptemp = 0;
+            else {
+                strncpy(ptemp, papChoice[i], sizeof(pdbr_enumStrs->strs[0]));
+                *(ptemp + sizeof(pdbr_enumStrs->strs[0]) - 1) = 0;
+            }
+            ptemp += sizeof(pdbr_enumStrs->strs[0]);
+        }
+        break;
+    default:
+        *options = (*options) ^ DBR_ENUM_STRS; /*Turn off option */
+        break;
+    }
+    *ppbuffer = ((char *)*ppbuffer) + dbr_enumStrs_size;
+    return;
 }
 
 static void get_graphics(DBADDR *paddr, char **ppbuffer,
-    rset *prset,long	*options)
+    rset * prset, long *options)
 {
-	struct			dbr_grDouble grd;
-	int			got_data=FALSE;
-
-        grd.upper_disp_limit = grd.lower_disp_limit = 0.0;
-	if( prset && prset->get_graphic_double ) {
-		(*prset->get_graphic_double)(paddr,&grd);
-		got_data=TRUE;
-	}
-	if( (*options) & (DBR_GR_LONG) ) {
-		char	*pbuffer=*ppbuffer;
-
-		if(got_data) {
-		    struct dbr_grLong *pgr=(struct dbr_grLong*)pbuffer;
-		    pgr->upper_disp_limit = (epicsInt32)grd.upper_disp_limit;
-		    pgr->lower_disp_limit = (epicsInt32)grd.lower_disp_limit;
-		} else {
-		    memset(pbuffer,'\0',dbr_grLong_size);
-		    *options = (*options) ^ DBR_GR_LONG; /*Turn off option*/
-		}
-		*ppbuffer = ((char *)*ppbuffer) + dbr_grLong_size;
-	}
-	if( (*options) & (DBR_GR_DOUBLE) ) {
-		char	*pbuffer=*ppbuffer;
-
-		if(got_data) {
-		    struct dbr_grDouble *pgr=(struct dbr_grDouble*)pbuffer;
-		    pgr->upper_disp_limit = grd.upper_disp_limit;
-		    pgr->lower_disp_limit = grd.lower_disp_limit;
-		} else {
-		    memset(pbuffer,'\0',dbr_grDouble_size);
-		    *options = (*options) ^ DBR_GR_DOUBLE; /*Turn off option*/
-		}
-		*ppbuffer = ((char *)*ppbuffer) + dbr_grDouble_size;
-	}
-	return;
+    struct dbr_grDouble grd;
+    int got_data = FALSE;
+
+    grd.upper_disp_limit = grd.lower_disp_limit = 0.0;
+    if (prset && prset->get_graphic_double) {
+        (*prset->get_graphic_double) (paddr, &grd);
+        got_data = TRUE;
+    }
+    if ((*options) & (DBR_GR_LONG)) {
+        char *pbuffer = *ppbuffer;
+
+        if (got_data) {
+            struct dbr_grLong *pgr = (struct dbr_grLong *)pbuffer;
+            pgr->upper_disp_limit = (epicsInt32) grd.upper_disp_limit;
+            pgr->lower_disp_limit = (epicsInt32) grd.lower_disp_limit;
+        } else {
+            memset(pbuffer, '\0', dbr_grLong_size);
+            *options = (*options) ^ DBR_GR_LONG; /*Turn off option */
+        }
+        *ppbuffer = ((char *)*ppbuffer) + dbr_grLong_size;
+    }
+    if ((*options) & (DBR_GR_DOUBLE)) {
+        char *pbuffer = *ppbuffer;
+
+        if (got_data) {
+            struct dbr_grDouble *pgr = (struct dbr_grDouble *)pbuffer;
+            pgr->upper_disp_limit = grd.upper_disp_limit;
+            pgr->lower_disp_limit = grd.lower_disp_limit;
+        } else {
+            memset(pbuffer, '\0', dbr_grDouble_size);
+            *options = (*options) ^ DBR_GR_DOUBLE; /*Turn off option */
+        }
+        *ppbuffer = ((char *)*ppbuffer) + dbr_grDouble_size;
+    }
+    return;
 }
 
 static void get_control(DBADDR *paddr, char **ppbuffer,
-    rset *prset,long	*options)
+    rset * prset, long *options)
 {
-	struct dbr_ctrlDouble	ctrld;
-	int			got_data=FALSE;
-
-        ctrld.upper_ctrl_limit = ctrld.lower_ctrl_limit = 0.0;
-	if( prset && prset->get_control_double ) {
-		(*prset->get_control_double)(paddr,&ctrld);
-		got_data=TRUE;
-	}
-	if( (*options) & (DBR_CTRL_LONG) ) {
-		char	*pbuffer=*ppbuffer;
-
-		if(got_data) {
-		    struct dbr_ctrlLong *pctrl=(struct dbr_ctrlLong*)pbuffer;
-		    pctrl->upper_ctrl_limit = (epicsInt32)ctrld.upper_ctrl_limit;
-		    pctrl->lower_ctrl_limit = (epicsInt32)ctrld.lower_ctrl_limit;
-		} else {
-		    memset(pbuffer,'\0',dbr_ctrlLong_size);
-		    *options = (*options) ^ DBR_CTRL_LONG; /*Turn off option*/
-		}
-		*ppbuffer = ((char *)*ppbuffer) + dbr_ctrlLong_size;
-	}
-	if( (*options) & (DBR_CTRL_DOUBLE) ) {
-		char	*pbuffer=*ppbuffer;
-
-		if(got_data) {
-		   struct dbr_ctrlDouble *pctrl=(struct dbr_ctrlDouble*)pbuffer;
-		   pctrl->upper_ctrl_limit = ctrld.upper_ctrl_limit;
-		   pctrl->lower_ctrl_limit = ctrld.lower_ctrl_limit;
-		} else {
-		    memset(pbuffer,'\0',dbr_ctrlDouble_size);
-		    *options = (*options) ^ DBR_CTRL_DOUBLE; /*Turn off option*/
-		}
-		*ppbuffer = ((char *)*ppbuffer) + dbr_ctrlDouble_size;
-	}
-	return;
+    struct dbr_ctrlDouble ctrld;
+    int got_data = FALSE;
+
+    ctrld.upper_ctrl_limit = ctrld.lower_ctrl_limit = 0.0;
+    if (prset && prset->get_control_double) {
+        (*prset->get_control_double) (paddr, &ctrld);
+        got_data = TRUE;
+    }
+    if ((*options) & (DBR_CTRL_LONG)) {
+        char *pbuffer = *ppbuffer;
+
+        if (got_data) {
+            struct dbr_ctrlLong *pctrl = (struct dbr_ctrlLong *)pbuffer;
+            pctrl->upper_ctrl_limit = (epicsInt32) ctrld.upper_ctrl_limit;
+            pctrl->lower_ctrl_limit = (epicsInt32) ctrld.lower_ctrl_limit;
+        } else {
+            memset(pbuffer, '\0', dbr_ctrlLong_size);
+            *options = (*options) ^ DBR_CTRL_LONG; /*Turn off option */
+        }
+        *ppbuffer = ((char *)*ppbuffer) + dbr_ctrlLong_size;
+    }
+    if ((*options) & (DBR_CTRL_DOUBLE)) {
+        char *pbuffer = *ppbuffer;
+
+        if (got_data) {
+            struct dbr_ctrlDouble *pctrl = (struct dbr_ctrlDouble *)pbuffer;
+            pctrl->upper_ctrl_limit = ctrld.upper_ctrl_limit;
+            pctrl->lower_ctrl_limit = ctrld.lower_ctrl_limit;
+        } else {
+            memset(pbuffer, '\0', dbr_ctrlDouble_size);
+            *options = (*options) ^ DBR_CTRL_DOUBLE; /*Turn off option */
+        }
+        *ppbuffer = ((char *)*ppbuffer) + dbr_ctrlDouble_size;
+    }
+    return;
 }
 
 static void get_alarm(DBADDR *paddr, char **ppbuffer,
-    rset *prset, long *options)
+    rset * prset, long *options)
 {
     char *pbuffer = *ppbuffer;
-    struct dbr_alDouble ald = {epicsNAN, epicsNAN, epicsNAN, epicsNAN};
+    struct dbr_alDouble ald = { epicsNAN, epicsNAN, epicsNAN, epicsNAN };
     long no_data = TRUE;
 
     if (prset && prset->get_alarm_double)
         no_data = prset->get_alarm_double(paddr, &ald);
 
     if (*options & DBR_AL_LONG) {
-        struct dbr_alLong *pal = (struct dbr_alLong*) pbuffer;
+        struct dbr_alLong *pal = (struct dbr_alLong *)pbuffer;
 
-        pal->upper_alarm_limit   = finite(ald.upper_alarm_limit) ?
+        pal->upper_alarm_limit = finite(ald.upper_alarm_limit) ?
             (epicsInt32) ald.upper_alarm_limit : 0;
         pal->upper_warning_limit = finite(ald.upper_warning_limit) ?
             (epicsInt32) ald.upper_warning_limit : 0;
         pal->lower_warning_limit = finite(ald.lower_warning_limit) ?
             (epicsInt32) ald.lower_warning_limit : 0;
-        pal->lower_alarm_limit   = finite(ald.lower_alarm_limit) ?
+        pal->lower_alarm_limit = finite(ald.lower_alarm_limit) ?
             (epicsInt32) ald.lower_alarm_limit : 0;
 
         if (no_data)
-            *options ^= DBR_AL_LONG; /*Turn off option*/
+            *options ^= DBR_AL_LONG;    /*Turn off option */
 
         *ppbuffer += dbr_alLong_size;
     }
     if (*options & DBR_AL_DOUBLE) {
-        struct dbr_alDouble *pal = (struct dbr_alDouble*) pbuffer;
+        struct dbr_alDouble *pal = (struct dbr_alDouble *)pbuffer;
 
-        pal->upper_alarm_limit   = ald.upper_alarm_limit;
+        pal->upper_alarm_limit = ald.upper_alarm_limit;
         pal->upper_warning_limit = ald.upper_warning_limit;
         pal->lower_warning_limit = ald.lower_warning_limit;
-        pal->lower_alarm_limit   = ald.lower_alarm_limit;
+        pal->lower_alarm_limit = ald.lower_alarm_limit;
 
         if (no_data)
-            *options ^= DBR_AL_DOUBLE; /*Turn off option*/
+            *options ^= DBR_AL_DOUBLE;  /*Turn off option */
 
         *ppbuffer += dbr_alDouble_size;
     }
@@ -330,88 +332,89 @@ static void get_alarm(DBADDR *paddr, char **ppbuffer,
  * blocks only changing the buffer pointer in a way that does not break alignment.
  */
 static void getOptions(DBADDR *paddr, char **poriginal, long *options,
-        void *pflin)
+    void *pflin)
 {
-	db_field_log	*pfl= (db_field_log *)pflin;
-    rset	*prset;
-        short		field_type;
-	dbCommon	*pcommon;
-	char		*pbuffer = *poriginal;
-
-        if (!pfl || pfl->type == dbfl_type_rec)
-            field_type = paddr->field_type;
-        else
-            field_type = pfl->field_type;
-	prset=dbGetRset(paddr);
-	/* Process options */
-	pcommon = paddr->precord;
-	if( (*options) & DBR_STATUS ) {
-	    unsigned short *pushort = (unsigned short *)pbuffer;
-
-            if (!pfl || pfl->type == dbfl_type_rec) {
-                *pushort++ = pcommon->stat;
-                *pushort++ = pcommon->sevr;
-            } else {
-                *pushort++ = pfl->stat;
-                *pushort++ = pfl->sevr;
-            }
-	    *pushort++ = pcommon->acks;
-	    *pushort++ = pcommon->ackt;
-	    pbuffer = (char *)pushort;
-	}
-	if( (*options) & DBR_UNITS ) {
-	    memset(pbuffer,'\0',dbr_units_size);
-	    if( prset && prset->get_units ){
-		(*prset->get_units)(paddr, pbuffer);
-		pbuffer[DB_UNITS_SIZE-1] = '\0';
-	    } else {
-		*options ^= DBR_UNITS; /*Turn off DBR_UNITS*/
-	    }
-	    pbuffer += dbr_units_size;
-	}
-	if( (*options) & DBR_PRECISION ) {
-	    memset(pbuffer, '\0', dbr_precision_size);
-	    if((field_type==DBF_FLOAT || field_type==DBF_DOUBLE)
-	    &&  prset && prset->get_precision ){
-                (*prset->get_precision)(paddr,(long *)pbuffer);
-	    } else {
-		*options ^= DBR_PRECISION; /*Turn off DBR_PRECISION*/
-	    }
-	    pbuffer += dbr_precision_size;
-	}
-	if( (*options) & DBR_TIME ) {
-	    epicsUInt32 *ptime = (epicsUInt32 *)pbuffer;
-
-            if (!pfl || pfl->type == dbfl_type_rec) {
-                *ptime++ = pcommon->time.secPastEpoch;
-                *ptime++ = pcommon->time.nsec;
-            } else {
-                *ptime++ = pfl->time.secPastEpoch;
-                *ptime++ = pfl->time.nsec;
-	    }
-	    pbuffer = (char *)ptime;
-	}
-	if( (*options) & DBR_ENUM_STRS )
-	    get_enum_strs(paddr, &pbuffer, prset, options);
-	if( (*options) & (DBR_GR_LONG|DBR_GR_DOUBLE ))
-	    get_graphics(paddr, &pbuffer, prset, options);
-	if((*options) & (DBR_CTRL_LONG | DBR_CTRL_DOUBLE ))
-	    get_control(paddr, &pbuffer, prset, options);
-	if((*options) & (DBR_AL_LONG | DBR_AL_DOUBLE ))
-	    get_alarm(paddr, &pbuffer, prset, options);
-	*poriginal = pbuffer;
+    db_field_log *pfl = (db_field_log *) pflin;
+    rset *prset;
+    short field_type;
+    dbCommon *pcommon;
+    char *pbuffer = *poriginal;
+
+    if (!pfl)
+        field_type = paddr->field_type;
+    else
+        field_type = pfl->field_type;
+    prset = dbGetRset(paddr);
+    /* Process options */
+    pcommon = paddr->precord;
+    if ((*options) & DBR_STATUS) {
+        unsigned short *pushort = (unsigned short *)pbuffer;
+
+        if (!pfl) {
+            *pushort++ = pcommon->stat;
+            *pushort++ = pcommon->sevr;
+        } else {
+            *pushort++ = pfl->stat;
+            *pushort++ = pfl->sevr;
+        }
+        *pushort++ = pcommon->acks;
+        *pushort++ = pcommon->ackt;
+        pbuffer = (char *)pushort;
+    }
+    if ((*options) & DBR_UNITS) {
+        memset(pbuffer, '\0', dbr_units_size);
+        if (prset && prset->get_units) {
+            (*prset->get_units) (paddr, pbuffer);
+            pbuffer[DB_UNITS_SIZE - 1] = '\0';
+        } else {
+            *options ^= DBR_UNITS;      /*Turn off DBR_UNITS */
+        }
+        pbuffer += dbr_units_size;
+    }
+    if ((*options) & DBR_PRECISION) {
+        memset(pbuffer, '\0', dbr_precision_size);
+        if ((field_type == DBF_FLOAT || field_type == DBF_DOUBLE)
+            && prset && prset->get_precision) {
+            (*prset->get_precision) (paddr, (long *)pbuffer);
+        } else {
+            *options ^= DBR_PRECISION;  /*Turn off DBR_PRECISION */
+        }
+        pbuffer += dbr_precision_size;
+    }
+    if ((*options) & DBR_TIME) {
+        epicsUInt32 *ptime = (epicsUInt32 *) pbuffer;
+
+        if (!pfl) {
+            *ptime++ = pcommon->time.secPastEpoch;
+            *ptime++ = pcommon->time.nsec;
+        } else {
+            *ptime++ = pfl->time.secPastEpoch;
+            *ptime++ = pfl->time.nsec;
+        }
+        pbuffer = (char *)ptime;
+    }
+    if ((*options) & DBR_ENUM_STRS)
+        get_enum_strs(paddr, &pbuffer, prset, options);
+    if ((*options) & (DBR_GR_LONG | DBR_GR_DOUBLE))
+        get_graphics(paddr, &pbuffer, prset, options);
+    if ((*options) & (DBR_CTRL_LONG | DBR_CTRL_DOUBLE))
+        get_control(paddr, &pbuffer, prset, options);
+    if ((*options) & (DBR_AL_LONG | DBR_AL_DOUBLE))
+        get_alarm(paddr, &pbuffer, prset, options);
+    *poriginal = pbuffer;
 }
 
-rset * dbGetRset(const struct dbAddr *paddr)
+rset *dbGetRset(const struct dbAddr *paddr)
 {
-	struct dbFldDes *pfldDes = paddr->pfldDes;
+    struct dbFldDes *pfldDes = paddr->pfldDes;
 
-	if(!pfldDes) return(0);
-	return(pfldDes->pdbRecordType->prset);
+    if (!pfldDes)
+        return (0);
+    return (pfldDes->pdbRecordType->prset);
 }
 
-long dbPutAttribute(
-    const char *recordTypename, const char *name, const char *value)
+long dbPutAttribute(const char *recordTypename, const char *name,
+    const char *value)
 {
     DBENTRY dbEntry;
     DBENTRY *pdbEntry = &dbEntry;
@@ -430,7 +433,7 @@ long dbPutAttribute(
     if (!status)
         status = dbPutRecordAttribute(pdbEntry, name, value);
     dbFinishEntry(pdbEntry);
-done:
+  done:
     if (status)
         errMessage(status, "dbPutAttribute failure");
     return status;
@@ -458,7 +461,7 @@ int dbGetFieldIndex(const struct dbAddr *paddr)
  *     5.  Run the process routine specific to the record type.
  *     6.  Check to see if record contents should be automatically printed.
  */
-long dbProcess(dbCommon *precord)
+long dbProcess(dbCommon * precord)
 {
     rset *prset = precord->rset;
     dbRecordType *pdbRecordType = precord->rdes;
@@ -466,7 +469,7 @@ long dbProcess(dbCommon *precord)
     char context[40] = "";
     long status = 0;
     int *ptrace;
-    int	set_trace = FALSE;
+    int set_trace = FALSE;
     dbFldDes *pdbFldDes;
     int callNotifyCompletion = FALSE;
 
@@ -492,7 +495,7 @@ long dbProcess(dbCommon *precord)
             goto all_done;
     }
 
-    /* check for trace processing*/
+    /* check for trace processing */
     if (tpro) {
         if (!*ptrace) {
             *ptrace = 1;
@@ -519,19 +522,17 @@ long dbProcess(dbCommon *precord)
 
         /* raise scan alarm after MAX_LOCK times */
         if ((precord->stat == SCAN_ALARM) ||
-            (precord->lcnt++ < MAX_LOCK) ||
-            (precord->sevr >= INVALID_ALARM)) goto all_done;
+            (precord->lcnt++ < MAX_LOCK) || (precord->sevr >= INVALID_ALARM))
+            goto all_done;
 
         recGblSetSevr(precord, SCAN_ALARM, INVALID_ALARM);
         monitor_mask = recGblResetAlarms(precord);
-        monitor_mask |= DBE_VALUE|DBE_LOG;
+        monitor_mask |= DBE_VALUE | DBE_LOG;
         pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->indvalFlddes];
         db_post_events(precord,
-                (void *)(((char *)precord) + pdbFldDes->offset),
-                monitor_mask);
+            (void *)(((char *)precord) + pdbFldDes->offset), monitor_mask);
         goto all_done;
-    }
-    else
+    } else
         precord->lcnt = 0;
 
     /*
@@ -547,7 +548,7 @@ long dbProcess(dbCommon *precord)
             printf("%s: dbProcess of Disabled '%s'\n",
                 context, precord->name);
 
-        /*take care of caching and notifyCompletion*/
+        /*take care of caching and notifyCompletion */
         precord->rpro = FALSE;
         precord->putf = FALSE;
         callNotifyCompletion = TRUE;
@@ -564,8 +565,8 @@ long dbProcess(dbCommon *precord)
         db_post_events(precord, &precord->sevr, DBE_VALUE);
         pdbFldDes = pdbRecordType->papFldDes[pdbRecordType->indvalFlddes];
         db_post_events(precord,
-                (void *)(((char *)precord) + pdbFldDes->offset),
-                DBE_VALUE|DBE_ALARM);
+            (void *)(((char *)precord) + pdbFldDes->offset),
+            DBE_VALUE | DBE_ALARM);
         goto all_done;
     }
 
@@ -573,7 +574,7 @@ long dbProcess(dbCommon *precord)
     /* FIXME: put this in iocInit() !!! */
     if (!prset || !prset->process) {
         callNotifyCompletion = TRUE;
-        precord->pact = 1;/*set pact so error is issued only once*/
+        precord->pact = 1;              /*set pact so error is issued only once */
         recGblRecordError(S_db_noRSET, (void *)precord, "dbProcess");
         status = S_db_noRSET;
         if (*ptrace)
@@ -592,7 +593,7 @@ long dbProcess(dbCommon *precord)
         dbPrint(precord);
     }
 
-all_done:
+  all_done:
     if (set_trace)
         *ptrace = 0;
     if (callNotifyCompletion && precord->ppn)
@@ -601,7 +602,7 @@ all_done:
     return status;
 }
 
-long dbEntryToAddr(const DBENTRY *pdbentry, DBADDR *paddr)
+long dbEntryToAddr(const DBENTRY * pdbentry, DBADDR *paddr)
 {
     dbFldDes *pflddes = pdbentry->pflddes;
     short dbfType = pflddes->field_type;
@@ -610,9 +611,9 @@ long dbEntryToAddr(const DBENTRY *pdbentry, DBADDR *paddr)
     paddr->pfield = pdbentry->pfield;
     paddr->pfldDes = pflddes;
     paddr->no_elements = 1;
-    paddr->field_type  = dbfType;
-    paddr->field_size  = pflddes->size;
-    paddr->special     = pflddes->special;
+    paddr->field_type = dbfType;
+    paddr->field_size = pflddes->size;
+    paddr->special = pflddes->special;
     paddr->dbr_field_type = mapDBFToDBR[dbfType];
 
     if (paddr->special == SPC_DBADDR) {
@@ -643,16 +644,20 @@ long dbNameToAddr(const char *pname, DBADDR *paddr)
 
     dbInitEntry(pdbbase, &dbEntry);
     status = dbFindRecordPart(&dbEntry, &pname);
-    if (status) goto finish;
+    if (status)
+        goto finish;
 
-    if (*pname == '.') ++pname;
+    if (*pname == '.')
+        ++pname;
     status = dbFindFieldPart(&dbEntry, &pname);
     if (status == S_dbLib_fieldNotFound)
         status = dbGetAttributePart(&dbEntry, &pname);
-    if (status) goto finish;
+    if (status)
+        goto finish;
 
     status = dbEntryToAddr(&dbEntry, paddr);
-    if (status) goto finish;
+    if (status)
+        goto finish;
 
     /* Handle field modifiers */
     if (*pname++ == '$') {
@@ -664,29 +669,27 @@ long dbNameToAddr(const char *pname, DBADDR *paddr)
             paddr->field_type = DBF_CHAR;
             paddr->field_size = 1;
             paddr->dbr_field_type = DBR_CHAR;
-        }
-        else if (dbfType >= DBF_INLINK && dbfType <= DBF_FWDLINK) {
+        } else if (dbfType >= DBF_INLINK && dbfType <= DBF_FWDLINK) {
             /* Clients see a char array, but keep original dbfType */
             paddr->no_elements = PVLINK_STRINGSZ;
             paddr->field_size = 1;
             paddr->dbr_field_type = DBR_CHAR;
-        }
-        else {
+        } else {
             status = S_dbLib_fieldNotFound;
         }
     }
 
-finish:
+  finish:
     dbFinishEntry(&dbEntry);
     return status;
 }
 
-void dbInitEntryFromAddr(struct dbAddr *paddr, DBENTRY *pdbentry)
+void dbInitEntryFromAddr(struct dbAddr *paddr, DBENTRY * pdbentry)
 {
     struct dbCommon *prec = paddr->precord;
     dbCommonPvt *ppvt = dbRec2Pvt(prec);
 
-    memset((char *)pdbentry,'\0',sizeof(DBENTRY));
+    memset((char *)pdbentry, '\0', sizeof(DBENTRY));
 
     pdbentry->pdbbase = pdbbase;
     pdbentry->precordType = prec->rdes;
@@ -696,24 +699,24 @@ void dbInitEntryFromAddr(struct dbAddr *paddr, DBENTRY *pdbentry)
     pdbentry->indfield = paddr->pfldDes->indRecordType;
 }
 
-void dbInitEntryFromRecord(struct dbCommon *prec, DBENTRY *pdbentry)
+void dbInitEntryFromRecord(struct dbCommon *prec, DBENTRY * pdbentry)
 {
     dbCommonPvt *ppvt = dbRec2Pvt(prec);
 
-    memset((char *)pdbentry,'\0',sizeof(DBENTRY));
+    memset((char *)pdbentry, '\0', sizeof(DBENTRY));
 
     pdbentry->pdbbase = pdbbase;
     pdbentry->precordType = prec->rdes;
     pdbentry->precnode = ppvt->recnode;
 }
 
-struct link* dbGetDevLink(struct dbCommon* prec)
+struct link *dbGetDevLink(struct dbCommon *prec)
 {
     DBLINK *plink = 0;
     DBENTRY entry;
     dbInitEntryFromRecord(prec, &entry);
-    if(dbFindField(&entry, "INP")==0 || dbFindField(&entry, "OUT")==0) {
-        plink = (DBLINK*)entry.pfield;
+    if (dbFindField(&entry, "INP") == 0 || dbFindField(&entry, "OUT") == 0) {
+        plink = (DBLINK *) entry.pfield;
     }
     dbFinishEntry(&entry);
     return plink;
@@ -723,41 +726,53 @@ long dbValueSize(short dbr_type)
 {
     /* sizes for value associated with each DBR request type */
     static long size[] = {
-        MAX_STRING_SIZE,             /* STRING       */
-        sizeof(epicsInt8),           /* CHAR         */
-        sizeof(epicsUInt8),          /* UCHAR        */
-        sizeof(epicsInt16),          /* SHORT        */
-        sizeof(epicsUInt16),         /* USHORT       */
-        sizeof(epicsInt32),          /* LONG         */
-        sizeof(epicsUInt32),         /* ULONG        */
-        sizeof(epicsInt64),          /* INT64        */
-        sizeof(epicsUInt64),         /* UINT64       */
-        sizeof(epicsFloat32),        /* FLOAT        */
-        sizeof(epicsFloat64),        /* DOUBLE       */
-        sizeof(epicsEnum16)};        /* ENUM         */
-
-    return(size[dbr_type]);
+        MAX_STRING_SIZE,                /* STRING       */
+        sizeof(epicsInt8),              /* CHAR         */
+        sizeof(epicsUInt8),             /* UCHAR        */
+        sizeof(epicsInt16),             /* SHORT        */
+        sizeof(epicsUInt16),            /* USHORT       */
+        sizeof(epicsInt32),             /* LONG         */
+        sizeof(epicsUInt32),            /* ULONG        */
+        sizeof(epicsInt64),             /* INT64        */
+        sizeof(epicsUInt64),            /* UINT64       */
+        sizeof(epicsFloat32),           /* FLOAT        */
+        sizeof(epicsFloat64),           /* DOUBLE       */
+        sizeof(epicsEnum16)
+    };                                  /* ENUM         */
+
+    return (size[dbr_type]);
 }
 
-
 long dbBufferSize(short dbr_type, long options, long no_elements)
 {
-    long nbytes=0;
+    long nbytes = 0;
 
     nbytes += dbValueSize(dbr_type) * no_elements;
-    if (options & DBR_STATUS)      nbytes += dbr_status_size;
-    if (options & DBR_UNITS)       nbytes += dbr_units_size;
-    if (options & DBR_PRECISION)   nbytes += dbr_precision_size;
-    if (options & DBR_TIME)        nbytes += dbr_time_size;
-    if (options & DBR_ENUM_STRS)   nbytes += dbr_enumStrs_size;
-    if (options & DBR_GR_LONG)     nbytes += dbr_grLong_size;
-    if (options & DBR_GR_DOUBLE)   nbytes += dbr_grDouble_size;
-    if (options & DBR_CTRL_LONG)   nbytes += dbr_ctrlLong_size;
-    if (options & DBR_CTRL_DOUBLE) nbytes += dbr_ctrlDouble_size;
-    if (options & DBR_AL_LONG)     nbytes += dbr_alLong_size;
-    if (options & DBR_AL_DOUBLE)   nbytes += dbr_alDouble_size;
-    return(nbytes);
+    if (options & DBR_STATUS)
+        nbytes += dbr_status_size;
+    if (options & DBR_UNITS)
+        nbytes += dbr_units_size;
+    if (options & DBR_PRECISION)
+        nbytes += dbr_precision_size;
+    if (options & DBR_TIME)
+        nbytes += dbr_time_size;
+    if (options & DBR_ENUM_STRS)
+        nbytes += dbr_enumStrs_size;
+    if (options & DBR_GR_LONG)
+        nbytes += dbr_grLong_size;
+    if (options & DBR_GR_DOUBLE)
+        nbytes += dbr_grDouble_size;
+    if (options & DBR_CTRL_LONG)
+        nbytes += dbr_ctrlLong_size;
+    if (options & DBR_CTRL_DOUBLE)
+        nbytes += dbr_ctrlDouble_size;
+    if (options & DBR_AL_LONG)
+        nbytes += dbr_alLong_size;
+    if (options & DBR_AL_DOUBLE)
+        nbytes += dbr_alDouble_size;
+    return (nbytes);
 }
+
 int dbLoadDatabase(const char *file, const char *path, const char *subs)
 {
     if (!file) {
@@ -767,7 +782,7 @@ int dbLoadDatabase(const char *file, const char *path, const char *subs)
     return dbReadDatabase(&pdbbase, file, path, subs);
 }
 
-int dbLoadRecords(const char* file, const char* subs)
+int dbLoadRecords(const char *file, const char *subs)
 {
     int status;
 
@@ -781,7 +796,6 @@ int dbLoadRecords(const char* file, const char* subs)
     return status;
 }
 
-
 static long getLinkValue(DBADDR *paddr, short dbrType,
     char *pbuf, long *nRequest)
 {
@@ -797,7 +811,7 @@ static long getLinkValue(DBADDR *paddr, short dbrType,
      * valid DBADDR, so no point to check again.
      * Request for zero elements always succeeds
      */
-    if(!nReq)
+    if (!nReq)
         return 0;
 
     switch (dbrType) {
@@ -806,8 +820,9 @@ static long getLinkValue(DBADDR *paddr, short dbrType,
         nReq = 1;
         break;
 
-    case DBR_DOUBLE:    /* Needed for dbCa links */
-        if (nRequest) *nRequest = 1;
+    case DBR_DOUBLE:                   /* Needed for dbCa links */
+        if (nRequest)
+            *nRequest = 1;
         *(double *)pbuf = epicsNAN;
         return 0;
 
@@ -821,27 +836,30 @@ static long getLinkValue(DBADDR *paddr, short dbrType,
 
     dbInitEntry(pdbbase, &dbEntry);
     status = dbFindRecord(&dbEntry, precord->name);
-    if (!status) status = dbFindField(&dbEntry, pfldDes->name);
+    if (!status)
+        status = dbFindField(&dbEntry, pfldDes->name);
     if (!status) {
         const char *rtnString = dbGetString(&dbEntry);
 
-        strncpy(pbuf, rtnString, maxlen-1);
-        pbuf[maxlen-1] = 0;
-        if(dbrType!=DBR_STRING)
-            nReq = strlen(pbuf)+1;
-        if(nRequest) *nRequest = nReq;
+        strncpy(pbuf, rtnString, maxlen - 1);
+        pbuf[maxlen - 1] = 0;
+        if (dbrType != DBR_STRING)
+            nReq = strlen(pbuf) + 1;
+        if (nRequest)
+            *nRequest = nReq;
     }
     dbFinishEntry(&dbEntry);
     return status;
 }
 
 static long getAttrValue(DBADDR *paddr, short dbrType,
-        char *pbuf, long *nRequest)
+    char *pbuf, long *nRequest)
 {
     int maxlen;
     long nReq = nRequest ? *nRequest : 1;
 
-    if (!paddr->pfield) return S_db_badField;
+    if (!paddr->pfield)
+        return S_db_badField;
 
     switch (dbrType) {
     case DBR_STRING:
@@ -859,22 +877,23 @@ static long getAttrValue(DBADDR *paddr, short dbrType,
         return S_db_badDbrtype;
     }
 
-    strncpy(pbuf, paddr->pfield, maxlen-1);
-    pbuf[maxlen-1] = 0;
-    if(dbrType!=DBR_STRING)
-        nReq = strlen(pbuf)+1;
-    if(nRequest) *nRequest = nReq;
+    strncpy(pbuf, paddr->pfield, maxlen - 1);
+    pbuf[maxlen - 1] = 0;
+    if (dbrType != DBR_STRING)
+        nReq = strlen(pbuf) + 1;
+    if (nRequest)
+        *nRequest = nReq;
     return 0;
 }
 
-long dbGetField(DBADDR *paddr,short dbrType,
-    void *pbuffer, long *options, long *nRequest, void *pflin)
+long dbGetField(DBADDR *paddr, short dbrType,
+    void *pbuffer, long *options, long *nRequest)
 {
     dbCommon *precord = paddr->precord;
     long status = 0;
 
     dbScanLock(precord);
-    status = dbGet(paddr, dbrType, pbuffer, options, nRequest, pflin);
+    status = dbGet(paddr, dbrType, pbuffer, options, nRequest, NULL);
     dbScanUnlock(precord);
     return status;
 }
@@ -884,7 +903,7 @@ long dbGet(DBADDR *paddr, short dbrType,
 {
     char *pbuf = pbuffer;
     void *pfieldsave = paddr->pfield;
-    db_field_log *pfl = (db_field_log *)pflin;
+    db_field_log *pfl = (db_field_log *) pflin;
     short field_type;
     long capacity, no_elements, offset;
     rset *prset;
@@ -895,7 +914,7 @@ long dbGet(DBADDR *paddr, short dbrType,
     if (nRequest && *nRequest == 0)
         return 0;
 
-    if (!pfl || pfl->type == dbfl_type_rec) {
+    if (!pfl) {
         field_type = paddr->field_type;
         no_elements = capacity = paddr->no_elements;
 
@@ -903,15 +922,14 @@ long dbGet(DBADDR *paddr, short dbrType,
          * may modify paddr->pfield
          */
         if (paddr->pfldDes->special == SPC_DBADDR &&
-            (prset = dbGetRset(paddr)) &&
-            prset->get_array_info) {
+            (prset = dbGetRset(paddr)) && prset->get_array_info) {
             status = prset->get_array_info(paddr, &no_elements, &offset);
         } else
             offset = 0;
     } else {
         field_type = pfl->field_type;
         no_elements = capacity = pfl->no_elements;
-        offset = 0;
+        offset = pfl->offset;
     }
 
     if (field_type >= DBF_INLINK && field_type <= DBF_FWDLINK) {
@@ -937,19 +955,25 @@ long dbGet(DBADDR *paddr, short dbrType,
     if (offset == 0 && (!nRequest || no_elements == 1)) {
         if (nRequest)
             *nRequest = 1;
-        if (!pfl || pfl->type == dbfl_type_rec) {
+        if (!pfl) {
             status = dbFastGetConvertRoutine[field_type][dbrType]
                 (paddr->pfield, pbuf, paddr);
         } else {
-            DBADDR localAddr = *paddr; /* Structure copy */
+            DBADDR localAddr = *paddr;  /* Structure copy */
+
+            if (pfl->no_elements < 1) {
+                status = S_db_badField;
+                goto done;
+            }
 
             localAddr.field_type = pfl->field_type;
             localAddr.field_size = pfl->field_size;
+            /* not used by dbFastConvert: */
             localAddr.no_elements = pfl->no_elements;
             if (pfl->type == dbfl_type_val)
-                localAddr.pfield = (char *) &pfl->u.v.field;
+                localAddr.pfield = (char *)&pfl->u.v.field;
             else
-                localAddr.pfield = (char *)  pfl->u.r.field;
+                localAddr.pfield = (char *)pfl->u.r.field;
             status = dbFastGetConvertRoutine[field_type][dbrType]
                 (localAddr.pfield, pbuf, &localAddr);
         }
@@ -969,52 +993,55 @@ long dbGet(DBADDR *paddr, short dbrType,
             char message[80];
 
             sprintf(message, "dbGet: Missing conversion for [%d][%d]\n",
-                    field_type, dbrType);
+                field_type, dbrType);
             recGblDbaddrError(S_db_badDbrtype, paddr, message);
             status = S_db_badDbrtype;
             goto done;
         }
         /* convert data into the caller's buffer */
         if (n <= 0) {
-            ;/*do nothing*/
-        } else if (!pfl || pfl->type == dbfl_type_rec) {
+            ;                           /*do nothing */
+        } else if (!pfl) {
             status = convert(paddr, pbuf, n, capacity, offset);
         } else {
-            DBADDR localAddr = *paddr; /* Structure copy */
+            DBADDR localAddr = *paddr;  /* Structure copy */
 
             localAddr.field_type = pfl->field_type;
             localAddr.field_size = pfl->field_size;
+            /* not used by dbConvert, it uses the passed capacity instead: */
             localAddr.no_elements = pfl->no_elements;
             if (pfl->type == dbfl_type_val)
-                localAddr.pfield = (char *) &pfl->u.v.field;
+                localAddr.pfield = (char *)&pfl->u.v.field;
             else
-                localAddr.pfield = (char *)  pfl->u.r.field;
+                localAddr.pfield = (char *)pfl->u.r.field;
             status = convert(&localAddr, pbuf, n, capacity, offset);
         }
 
-        if(!status && dbrType==DBF_CHAR && nRequest &&
-                paddr->pfldDes && paddr->pfldDes->field_type==DBF_STRING)
-        {
+        if (!status && dbrType == DBF_CHAR && nRequest &&
+            paddr->pfldDes && paddr->pfldDes->field_type == DBF_STRING) {
             /* long string ensure nil and truncate to actual length */
             long nReq = *nRequest;
-            pbuf[nReq-1] = '\0';
-            *nRequest = strlen(pbuf)+1;
+            pbuf[nReq - 1] = '\0';
+            *nRequest = strlen(pbuf) + 1;
         }
     }
-done:
+  done:
     paddr->pfield = pfieldsave;
     return status;
 }
 
-devSup* dbDTYPtoDevSup(dbRecordType *prdes, int dtyp) {
-    return (devSup *)ellNth(&prdes->devList, dtyp+1);
+devSup *dbDTYPtoDevSup(dbRecordType * prdes, int dtyp)
+{
+    return (devSup *) ellNth(&prdes->devList, dtyp + 1);
 }
 
-devSup* dbDSETtoDevSup(dbRecordType *prdes, dset *pdset) {
-    devSup *pdevSup = (devSup *)ellFirst(&prdes->devList);
+devSup *dbDSETtoDevSup(dbRecordType * prdes, dset * pdset)
+{
+    devSup *pdevSup = (devSup *) ellFirst(&prdes->devList);
     while (pdevSup) {
-        if (pdset == pdevSup->pdset) return pdevSup;
-        pdevSup = (devSup *)ellNext(&pdevSup->node);
+        if (pdset == pdevSup->pdset)
+            return pdevSup;
+        pdevSup = (devSup *) ellNext(&pdevSup->node);
     }
     return NULL;
 }
@@ -1022,24 +1049,24 @@ devSup* dbDSETtoDevSup(dbRecordType *prdes, dset *pdset) {
 static long dbPutFieldLink(DBADDR *paddr,
     short dbrType, const void *pbuffer, long nRequest)
 {
-    dbLinkInfo  link_info;
-    DBADDR      *pdbaddr = NULL;
-    dbCommon    *precord = paddr->precord;
-    dbCommon    *lockrecs[2];
-    dbLocker    locker;
-    dbFldDes    *pfldDes = paddr->pfldDes;
-    long        special = paddr->special;
+    dbLinkInfo link_info;
+    dbChannel *chan = NULL;
+    dbCommon *precord = paddr->precord;
+    dbCommon *lockrecs[2];
+    dbLocker locker;
+    dbFldDes *pfldDes = paddr->pfldDes;
+    long special = paddr->special;
     struct link *plink = (struct link *)paddr->pfield;
-    const char  *pstring = (const char *)pbuffer;
+    const char *pstring = (const char *)pbuffer;
     struct dsxt *old_dsxt = NULL;
     dset *new_dset = NULL;
     struct dsxt *new_dsxt = NULL;
-    devSup      *new_devsup = NULL;
-    long        status;
-    int         isDevLink;
-    short       scan;
+    devSup *new_devsup = NULL;
+    long status;
+    int isDevLink;
+    short scan;
 
-    STATIC_ASSERT(DBLOCKER_NALLOC>=2);
+    STATIC_ASSERT(DBLOCKER_NALLOC >= 2);
 
     switch (dbrType) {
     case DBR_CHAR:
@@ -1061,25 +1088,20 @@ static long dbPutFieldLink(DBADDR *paddr,
 
     if (link_info.ltype == PV_LINK &&
         (link_info.modifiers & (pvlOptCA | pvlOptCP | pvlOptCPP)) == 0) {
-        DBADDR tempaddr;
-
-        if (dbNameToAddr(link_info.target, &tempaddr)==0) {
-            /* This will become a DB link. */
-            pdbaddr = malloc(sizeof(*pdbaddr));
-            if (!pdbaddr) {
-                status = S_db_noMemory;
-                goto cleanup;
-            }
-            *pdbaddr = tempaddr; /* struct copy */
+        chan = dbChannelCreate(link_info.target);
+        if (chan && dbChannelOpen(chan) != 0) {
+            errlogPrintf
+                ("ERROR: dbPutFieldLink %s.%s=%s: dbChannelOpen() failed\n",
+                precord->name, pfldDes->name, link_info.target);
+            goto cleanup;
         }
     }
 
-    isDevLink = ellCount(&precord->rdes->devList) > 0 &&
-                pfldDes->isDevLink;
+    isDevLink = ellCount(&precord->rdes->devList) > 0 && pfldDes->isDevLink;
 
     memset(&locker, 0, sizeof(locker));
     lockrecs[0] = precord;
-    lockrecs[1] = pdbaddr ? pdbaddr->precord : NULL;
+    lockrecs[1] = chan ? dbChannelRecord(chan) : NULL;
     dbLockerPrepare(&locker, lockrecs, 2);
 
     dbScanLockMany(&locker);
@@ -1130,17 +1152,19 @@ static long dbPutFieldLink(DBADDR *paddr,
 
     if (dbLinkIsDefined(plink)) {
         dbRemoveLink(&locker, plink);   /* Clear out old link */
-    }
-    else if (!isDevLink) {
+    } else if (!isDevLink) {
         status = S_db_badHWaddr;
         goto restoreScan;
     }
 
-    if (special) status = dbPutSpecial(paddr, 0);
+    if (special)
+        status = dbPutSpecial(paddr, 0);
 
-    if (!status) status = dbSetLink(plink, &link_info, new_devsup);
+    if (!status)
+        status = dbSetLink(plink, &link_info, new_devsup);
 
-    if (!status && special) status = dbPutSpecial(paddr, 1);
+    if (!status && special)
+        status = dbPutSpecial(paddr, 1);
 
     if (status) {
         if (isDevLink) {
@@ -1163,19 +1187,20 @@ static long dbPutFieldLink(DBADDR *paddr,
         }
     }
 
-    switch (plink->type) { /* New link type */
+    switch (plink->type) {              /* New link type */
     case PV_LINK:
     case CONSTANT:
     case JSON_LINK:
-        dbAddLink(&locker, plink, pfldDes->field_type, pdbaddr);
+        dbAddLink(&locker, plink, pfldDes->field_type, chan);
+        chan = NULL;                    /* don't clean it up */
         break;
 
     case DB_LINK:
     case CA_LINK:
     case MACRO_LINK:
-        break;  /* should never get here */
+        break;                          /* should never get here */
 
-    default: /* Hardware address */
+    default:                           /* Hardware address */
         if (!isDevLink) {
             status = S_db_badHWaddr;
             goto postScanEvent;
@@ -1184,19 +1209,20 @@ static long dbPutFieldLink(DBADDR *paddr,
     }
     db_post_events(precord, plink, DBE_VALUE | DBE_LOG);
 
-restoreScan:
-    if (isDevLink &&
-        scan == menuScanI_O_Intr) { /* undo scanDelete() */
+  restoreScan:
+    if (isDevLink && scan == menuScanI_O_Intr) { /* undo scanDelete() */
         precord->scan = scan;
         scanAdd(precord);
     }
-postScanEvent:
+  postScanEvent:
     if (scan != precord->scan)
         db_post_events(precord, &precord->scan, DBE_VALUE | DBE_LOG);
-unlock:
+  unlock:
     dbScanUnlockMany(&locker);
     dbLockerFinalize(&locker);
-cleanup:
+  cleanup:
+    if (chan)
+        dbChannelDelete(chan);
     free(link_info.target);
     return status;
 }
@@ -1204,16 +1230,16 @@ cleanup:
 long dbPutField(DBADDR *paddr, short dbrType,
     const void *pbuffer, long nRequest)
 {
-    long	status = 0;
-    long	special  = paddr->special;
-    dbFldDes	*pfldDes = paddr->pfldDes;
-    dbCommon	*precord = paddr->precord;
-    short	dbfType  = paddr->field_type;
+    long status = 0;
+    long special = paddr->special;
+    dbFldDes *pfldDes = paddr->pfldDes;
+    dbCommon *precord = paddr->precord;
+    short dbfType = paddr->field_type;
 
     if (special == SPC_ATTRIBUTE)
         return S_db_noMod;
 
-    /*check for putField disabled*/
+    /*check for putField disabled */
     if (precord->disp && paddr->pfield != &precord->disp)
         return S_db_putDisabled;
 
@@ -1225,8 +1251,7 @@ long dbPutField(DBADDR *paddr, short dbrType,
     if (status == 0) {
         if (paddr->pfield == &precord->proc ||
             (pfldDes->process_passive &&
-             precord->scan == 0 &&
-             dbrType < DBR_PUT_ACKT)) {
+                precord->scan == 0 && dbrType < DBR_PUT_ACKT)) {
             if (precord->pact) {
                 if (dbAccessDebugPUTF && precord->tpro)
                     printf("%s: dbPutField to Active '%s', setting RPRO=1\n",
@@ -1249,11 +1274,11 @@ static long putAckt(DBADDR *paddr, const void *pbuffer, long nRequest,
     dbCommon *precord = paddr->precord;
     const unsigned short *ptrans = pbuffer;
 
-    if (*ptrans == precord->ackt) return 0;
+    if (*ptrans == precord->ackt)
+        return 0;
     precord->ackt = *ptrans;
     db_post_events(precord, &precord->ackt, DBE_VALUE | DBE_ALARM);
-    if (!precord->ackt &&
-        precord->acks > precord->sevr) {
+    if (!precord->ackt && precord->acks > precord->sevr) {
         precord->acks = precord->sevr;
         db_post_events(precord, &precord->acks, DBE_VALUE | DBE_ALARM);
     }
@@ -1275,14 +1300,13 @@ static long putAcks(DBADDR *paddr, const void *pbuffer, long nRequest,
     return 0;
 }
 
-long dbPut(DBADDR *paddr, short dbrType,
-    const void *pbuffer, long nRequest)
+long dbPut(DBADDR *paddr, short dbrType, const void *pbuffer, long nRequest)
 {
     dbCommon *precord = paddr->precord;
-    short field_type  = paddr->field_type;
-    long no_elements  = paddr->no_elements;
-    long special      = paddr->special;
-    void *pfieldsave  = paddr->pfield;
+    short field_type = paddr->field_type;
+    long no_elements = paddr->no_elements;
+    long special = paddr->special;
+    void *pfieldsave = paddr->pfield;
     rset *prset = dbGetRset(paddr);
     long status = 0;
     long offset;
@@ -1306,7 +1330,8 @@ long dbPut(DBADDR *paddr, short dbrType,
 
     if (special) {
         status = dbPutSpecial(paddr, 0);
-        if (status) return status;
+        if (status)
+            return status;
     }
 
     if (paddr->pfldDes->special == SPC_DBADDR &&
@@ -1315,18 +1340,19 @@ long dbPut(DBADDR *paddr, short dbrType,
 
         status = prset->get_array_info(paddr, &dummy, &offset);
         /* paddr->pfield may be modified */
-        if (status) goto done;
+        if (status)
+            goto done;
     } else
         offset = 0;
 
     if (no_elements <= 1) {
-        status = dbFastPutConvertRoutine[dbrType][field_type](pbuffer,
+        status = dbFastPutConvertRoutine[dbrType][field_type] (pbuffer,
             paddr->pfield, paddr);
         nRequest = 1;
     } else {
         if (no_elements < nRequest)
             nRequest = no_elements;
-        status = dbPutConvertRoutine[dbrType][field_type](paddr, pbuffer,
+        status = dbPutConvertRoutine[dbrType][field_type] (paddr, pbuffer,
             nRequest, no_elements, offset);
     }
 
@@ -1340,17 +1366,19 @@ long dbPut(DBADDR *paddr, short dbrType,
     /* Always do special processing if needed */
     if (special) {
         long status2 = dbPutSpecial(paddr, 1);
-        if (status2) goto done;
+        if (status2)
+            goto done;
     }
-    if (status) goto done;
+    if (status)
+        goto done;
 
     /* Propagate monitor events for this field, */
     /* unless the field is VAL and PP is true. */
     pfldDes = paddr->pfldDes;
     isValueField = dbIsValueField(pfldDes);
-    if (isValueField) precord->udf = FALSE;
-    if (precord->mlis.count &&
-        !(isValueField && pfldDes->process_passive))
+    if (isValueField)
+        precord->udf = FALSE;
+    if (precord->mlis.count && !(isValueField && pfldDes->process_passive))
         db_post_events(precord, pfieldsave, DBE_VALUE | DBE_LOG);
     /* If this field is a property (metadata) field,
      * then post a property change event (even if the field
@@ -1358,7 +1386,7 @@ long dbPut(DBADDR *paddr, short dbrType,
      */
     if (precord->mlis.count && pfldDes->prop)
         db_post_events(precord, NULL, DBE_PROPERTY);
-done:
+  done:
     paddr->pfield = pfieldsave;
     return status;
 }
diff --git a/modules/database/src/ioc/db/dbAccessDefs.h b/modules/database/src/ioc/db/dbAccessDefs.h
index 805dfd4..4a95989 100644
--- a/modules/database/src/ioc/db/dbAccessDefs.h
+++ b/modules/database/src/ioc/db/dbAccessDefs.h
@@ -243,7 +243,7 @@ epicsShareFunc devSup* dbDTYPtoDevSup(dbRecordType *prdes, int dtyp);
 epicsShareFunc devSup* dbDSETtoDevSup(dbRecordType *prdes, dset *pdset);
 epicsShareFunc long dbGetField(
     struct dbAddr *,short dbrType,void *pbuffer,long *options,
-    long *nRequest,void *pfl);
+    long *nRequest);
 epicsShareFunc long dbGet(
     struct dbAddr *,short dbrType,void *pbuffer,long *options,
     long *nRequest,void *pfl);
diff --git a/modules/database/src/ioc/db/dbChannel.c b/modules/database/src/ioc/db/dbChannel.c
index bc3c8e4..e361f3b 100644
--- a/modules/database/src/ioc/db/dbChannel.c
+++ b/modules/database/src/ioc/db/dbChannel.c
@@ -49,14 +49,12 @@ typedef struct parseContext {
 
 static void *dbChannelFreeList;
 static void *chFilterFreeList;
-static void *dbchStringFreeList;
 
 void dbChannelExit(void)
 {
     freeListCleanup(dbChannelFreeList);
     freeListCleanup(chFilterFreeList);
-    freeListCleanup(dbchStringFreeList);
-    dbChannelFreeList = chFilterFreeList = dbchStringFreeList = NULL;
+    dbChannelFreeList = chFilterFreeList = NULL;
 }
 
 void dbChannelInit (void)
@@ -66,7 +64,7 @@ void dbChannelInit (void)
 
     freeListInitPvt(&dbChannelFreeList,  sizeof(dbChannel), 128);
     freeListInitPvt(&chFilterFreeList,  sizeof(chFilter), 64);
-    freeListInitPvt(&dbchStringFreeList, sizeof(epicsOldString), 128);
+    db_init_event_freelists();
 }
 
 static void chf_value(parseContext *parser, parse_result *presult)
@@ -445,28 +443,6 @@ static long parseArrayRange(dbChannel* chan, const char *pname, const char **ppn
     return status;
 }
 
-/* Stolen from dbAccess.c: */
-static short mapDBFToDBR[DBF_NTYPES] =
-    {
-    /* DBF_STRING   => */DBR_STRING,
-    /* DBF_CHAR     => */DBR_CHAR,
-    /* DBF_UCHAR    => */DBR_UCHAR,
-    /* DBF_SHORT    => */DBR_SHORT,
-    /* DBF_USHORT   => */DBR_USHORT,
-    /* DBF_LONG     => */DBR_LONG,
-    /* DBF_ULONG    => */DBR_ULONG,
-    /* DBF_INT64    => */DBR_INT64,
-    /* DBF_UINT64   => */DBR_UINT64,
-    /* DBF_FLOAT    => */DBR_FLOAT,
-    /* DBF_DOUBLE   => */DBR_DOUBLE,
-    /* DBF_ENUM,    => */DBR_ENUM,
-    /* DBF_MENU,    => */DBR_ENUM,
-    /* DBF_DEVICE   => */DBR_ENUM,
-    /* DBF_INLINK   => */DBR_STRING,
-    /* DBF_OUTLINK  => */DBR_STRING,
-    /* DBF_FWDLINK  => */DBR_STRING,
-    /* DBF_NOACCESS => */DBR_NOACCESS };
-
 dbChannel * dbChannelCreate(const char *name)
 {
     const char *pname = name;
@@ -735,39 +711,6 @@ void dbChannelDelete(dbChannel *chan)
     freeListFree(dbChannelFreeList, chan);
 }
 
-static void freeArray(db_field_log *pfl) {
-    if (pfl->field_type == DBF_STRING && pfl->no_elements == 1) {
-        freeListFree(dbchStringFreeList, pfl->u.r.field);
-    } else {
-        free(pfl->u.r.field);
-    }
-}
-
-void dbChannelMakeArrayCopy(void *pvt, db_field_log *pfl, dbChannel *chan)
-{
-    void *p;
-    struct dbCommon *prec = dbChannelRecord(chan);
-
-    if (pfl->type != dbfl_type_rec) return;
-
-    pfl->type = dbfl_type_ref;
-    pfl->stat = prec->stat;
-    pfl->sevr = prec->sevr;
-    pfl->time = prec->time;
-    pfl->field_type  = chan->addr.field_type;
-    pfl->no_elements = chan->addr.no_elements;
-    pfl->field_size  = chan->addr.field_size;
-    pfl->u.r.dtor = freeArray;
-    pfl->u.r.pvt = pvt;
-    if (pfl->field_type == DBF_STRING && pfl->no_elements == 1) {
-        p = freeListCalloc(dbchStringFreeList);
-    } else {
-        p = calloc(pfl->no_elements, pfl->field_size);
-    }
-    if (p) dbGet(&chan->addr, mapDBFToDBR[pfl->field_type], p, NULL, &pfl->no_elements, NULL);
-    pfl->u.r.field = p;
-}
-
 /* FIXME: Do these belong in a different file? */
 
 void dbRegisterFilter(const char *name, const chFilterIf *fif, void *puser)
diff --git a/modules/database/src/ioc/db/dbChannel.h b/modules/database/src/ioc/db/dbChannel.h
index fab9c66..6e98dee 100644
--- a/modules/database/src/ioc/db/dbChannel.h
+++ b/modules/database/src/ioc/db/dbChannel.h
@@ -64,8 +64,9 @@ typedef struct dbChannel {
 /* Prototype for the channel event function that is called in filter stacks
  *
  * When invoked the scan lock for the record associated with 'chan' _may_ be locked.
- * If pLog->type==dbfl_type_rec then dbScanLock() must be called before copying
- * data out of the associated record.
+ * If pLog->type==dbfl_type_ref and pLog->u.r.dtor is NULL, then dbScanLock() must
+ * be called before accessing the data, as this indicates the data is owned by the
+ * record.
  *
  * This function has ownership of the field log pLog, if it wishes to discard
  * this update it should free the field log with db_delete_field_log() and
@@ -224,7 +225,6 @@ epicsShareFunc void dbRegisterFilter(const char *key, const chFilterIf *fif, voi
 epicsShareFunc db_field_log* dbChannelRunPreChain(dbChannel *chan, db_field_log *pLogIn);
 epicsShareFunc db_field_log* dbChannelRunPostChain(dbChannel *chan, db_field_log *pLogIn);
 epicsShareFunc const chFilterPlugin * dbFindFilter(const char *key, size_t len);
-epicsShareFunc void dbChannelMakeArrayCopy(void *pvt, db_field_log *pfl, dbChannel *chan);
 
 #ifdef __cplusplus
 }
diff --git a/modules/database/src/ioc/db/dbDbLink.c b/modules/database/src/ioc/db/dbDbLink.c
index f2cb0c5..8677add 100644
--- a/modules/database/src/ioc/db/dbDbLink.c
+++ b/modules/database/src/ioc/db/dbDbLink.c
@@ -60,6 +60,7 @@
 #include "dbConvertFast.h"
 #include "dbConvert.h"
 #include "db_field_log.h"
+#include "db_access_routines.h"
 #include "dbFldTypes.h"
 #include "dbLink.h"
 #include "dbLockPvt.h"
@@ -73,7 +74,7 @@
 #include "recSup.h"
 #include "special.h"
 #include "dbDbLink.h"
-
+#include "dbChannel.h"
 
 /***************************** Database Links *****************************/
 
@@ -82,45 +83,51 @@ static lset dbDb_lset;
 
 static long processTarget(dbCommon *psrc, dbCommon *pdst);
 
+#define linkChannel(plink) ((dbChannel *) (plink)->value.pv_link.pvt)
+
 long dbDbInitLink(struct link *plink, short dbfType)
 {
-    DBADDR dbaddr;
     long status;
-    DBADDR *pdbAddr;
+    dbChannel *chan;
+    dbCommon *precord;
 
-    status = dbNameToAddr(plink->value.pv_link.pvname, &dbaddr);
+    chan = dbChannelCreate(plink->value.pv_link.pvname);
+    if (!chan)
+        return S_db_notFound;
+    status = dbChannelOpen(chan);
     if (status)
         return status;
 
+    precord = dbChannelRecord(chan);
+
     plink->lset = &dbDb_lset;
     plink->type = DB_LINK;
-    pdbAddr = dbCalloc(1, sizeof(struct dbAddr));
-    *pdbAddr = dbaddr; /* structure copy */
-    plink->value.pv_link.pvt = pdbAddr;
-    ellAdd(&dbaddr.precord->bklnk, &plink->value.pv_link.backlinknode);
+    plink->value.pv_link.pvt = chan;
+    ellAdd(&precord->bklnk, &plink->value.pv_link.backlinknode);
     /* merging into the same lockset is deferred to the caller.
      * cf. initPVLinks()
      */
-    dbLockSetMerge(NULL, plink->precord, dbaddr.precord);
-    assert(plink->precord->lset->plockSet == dbaddr.precord->lset->plockSet);
+    dbLockSetMerge(NULL, plink->precord, precord);
+    assert(plink->precord->lset->plockSet == precord->lset->plockSet);
     return 0;
 }
 
 void dbDbAddLink(struct dbLocker *locker, struct link *plink, short dbfType,
-    DBADDR *ptarget)
+    dbChannel *chan)
 {
     plink->lset = &dbDb_lset;
     plink->type = DB_LINK;
-    plink->value.pv_link.pvt = ptarget;
-    ellAdd(&ptarget->precord->bklnk, &plink->value.pv_link.backlinknode);
+    plink->value.pv_link.pvt = chan;
+    ellAdd(&dbChannelRecord(chan)->bklnk, &plink->value.pv_link.backlinknode);
 
     /* target record is already locked in dbPutFieldLink() */
-    dbLockSetMerge(locker, plink->precord, ptarget->precord);
+    dbLockSetMerge(locker, plink->precord, dbChannelRecord(chan));
 }
 
 static void dbDbRemoveLink(struct dbLocker *locker, struct link *plink)
 {
-    DBADDR *pdbAddr = (DBADDR *) plink->value.pv_link.pvt;
+    dbChannel *chan = linkChannel(plink);
+    dbCommon *precord = dbChannelRecord(chan);
 
     plink->type = PV_LINK;
 
@@ -130,10 +137,10 @@ static void dbDbRemoveLink(struct dbLocker *locker, struct link *plink)
         plink->value.pv_link.getCvt = 0;
         plink->value.pv_link.pvlMask = 0;
         plink->value.pv_link.lastGetdbrType = 0;
-        ellDelete(&pdbAddr->precord->bklnk, &plink->value.pv_link.backlinknode);
-        dbLockSetSplit(locker, plink->precord, pdbAddr->precord);
+        ellDelete(&precord->bklnk, &plink->value.pv_link.backlinknode);
+        dbLockSetSplit(locker, plink->precord, precord);
     }
-    free(pdbAddr);
+    dbChannelDelete(chan);
 }
 
 static int dbDbIsConnected(const struct link *plink)
@@ -143,16 +150,14 @@ static int dbDbIsConnected(const struct link *plink)
 
 static int dbDbGetDBFtype(const struct link *plink)
 {
-    DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
-
-    return paddr->field_type;
+    dbChannel *chan = linkChannel(plink);
+    return dbChannelFinalFieldType(chan);
 }
 
 static long dbDbGetElements(const struct link *plink, long *nelements)
 {
-    DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
-
-    *nelements = paddr->no_elements;
+    dbChannel *chan = linkChannel(plink);
+    *nelements = dbChannelFinalElements(chan);
     return 0;
 }
 
@@ -160,30 +165,52 @@ static long dbDbGetValue(struct link *plink, short dbrType, void *pbuffer,
         long *pnRequest)
 {
     struct pv_link *ppv_link = &plink->value.pv_link;
-    DBADDR *paddr = ppv_link->pvt;
+    dbChannel *chan = linkChannel(plink);
+    DBADDR *paddr = &chan->addr;
     dbCommon *precord = plink->precord;
     long status;
 
     /* scan passive records if link is process passive  */
     if (ppv_link->pvlMask & pvlOptPP) {
-        status = dbScanPassive(precord, paddr->precord);
+        status = dbScanPassive(precord, dbChannelRecord(chan));
         if (status)
             return status;
     }
 
-    if (ppv_link->getCvt && ppv_link->lastGetdbrType == dbrType) {
-        status = ppv_link->getCvt(paddr->pfield, pbuffer, paddr);
+    /* If filters are involved in a read, create field log and run filters */
+    if (ellCount(&chan->filters)) {
+        db_field_log *pfl;
+
+        /*
+         * For the moment, empty arrays are not supported by EPICS.
+         * See the remark in src/std/filters/arr.c for details.
+         */
+        if (dbChannelFinalElements(chan) <= 0) /* empty array request */
+            return S_db_badField;
+        pfl = db_create_read_log(chan);
+        if (!pfl)
+            return S_db_noMemory;
+        pfl = dbChannelRunPreChain(chan, pfl);
+        pfl = dbChannelRunPostChain(chan, pfl);
+        status = dbChannelGet(chan, dbrType, pbuffer, NULL, pnRequest, pfl);
+        db_delete_field_log(pfl);
+        if (status)
+            return status;
+        if (pnRequest && *pnRequest <= 0) /* empty array result */
+            return S_db_badField;
+    } else if (ppv_link->getCvt && ppv_link->lastGetdbrType == dbrType) {
+        status = ppv_link->getCvt(dbChannelField(chan), pbuffer, paddr);
     } else {
-        unsigned short dbfType = paddr->field_type;
+        unsigned short dbfType = dbChannelFinalFieldType(chan);
 
         if (dbrType < 0 || dbrType > DBR_ENUM || dbfType > DBF_DEVICE)
             return S_db_badDbrtype;
 
-        if (paddr->no_elements == 1 && (!pnRequest || *pnRequest == 1)
-                && paddr->special != SPC_DBADDR
-                && paddr->special != SPC_ATTRIBUTE) {
+        if (dbChannelFinalElements(chan) == 1 && (!pnRequest || *pnRequest == 1)
+                && dbChannelSpecial(chan) != SPC_DBADDR
+                && dbChannelSpecial(chan) != SPC_ATTRIBUTE) {
             ppv_link->getCvt = dbFastGetConvertRoutine[dbfType][dbrType];
-            status = ppv_link->getCvt(paddr->pfield, pbuffer, paddr);
+            status = ppv_link->getCvt(dbChannelField(chan), pbuffer, paddr);
         } else {
             ppv_link->getCvt = NULL;
             status = dbGet(paddr, dbrType, pbuffer, NULL, pnRequest, NULL);
@@ -191,16 +218,18 @@ static long dbDbGetValue(struct link *plink, short dbrType, void *pbuffer,
         ppv_link->lastGetdbrType = dbrType;
     }
 
-    if (!status && precord != paddr->precord)
+    if (!status && precord != dbChannelRecord(chan))
         recGblInheritSevr(plink->value.pv_link.pvlMask & pvlOptMsMode,
-            plink->precord, paddr->precord->stat, paddr->precord->sevr);
+            plink->precord,
+            dbChannelRecord(chan)->stat, dbChannelRecord(chan)->sevr);
     return status;
 }
 
 static long dbDbGetControlLimits(const struct link *plink, double *low,
         double *high)
 {
-    DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
+    dbChannel *chan = linkChannel(plink);
+    DBADDR *paddr = &chan->addr;
     struct buffer {
         DBRctrlDouble
         double value;
@@ -221,7 +250,8 @@ static long dbDbGetControlLimits(const struct link *plink, double *low,
 static long dbDbGetGraphicLimits(const struct link *plink, double *low,
         double *high)
 {
-    DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
+    dbChannel *chan = linkChannel(plink);
+    DBADDR *paddr = &chan->addr;
     struct buffer {
         DBRgrDouble
         double value;
@@ -242,7 +272,8 @@ static long dbDbGetGraphicLimits(const struct link *plink, double *low,
 static long dbDbGetAlarmLimits(const struct link *plink, double *lolo,
         double *low, double *high, double *hihi)
 {
-    DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
+    dbChannel *chan = linkChannel(plink);
+    DBADDR *paddr = &chan->addr;
     struct buffer {
         DBRalDouble
         double value;
@@ -264,7 +295,8 @@ static long dbDbGetAlarmLimits(const struct link *plink, double *lolo,
 
 static long dbDbGetPrecision(const struct link *plink, short *precision)
 {
-    DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
+    dbChannel *chan = linkChannel(plink);
+    DBADDR *paddr = &chan->addr;
     struct buffer {
         DBRprecision
         double value;
@@ -283,7 +315,8 @@ static long dbDbGetPrecision(const struct link *plink, short *precision)
 
 static long dbDbGetUnits(const struct link *plink, char *units, int unitsSize)
 {
-    DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
+    dbChannel *chan = linkChannel(plink);
+    DBADDR *paddr = &chan->addr;
     struct buffer {
         DBRunits
         double value;
@@ -303,20 +336,20 @@ static long dbDbGetUnits(const struct link *plink, char *units, int unitsSize)
 static long dbDbGetAlarm(const struct link *plink, epicsEnum16 *status,
         epicsEnum16 *severity)
 {
-    DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
-
+    dbChannel *chan = linkChannel(plink);
+    dbCommon *precord = dbChannelRecord(chan);
     if (status)
-        *status = paddr->precord->stat;
+        *status = precord->stat;
     if (severity)
-        *severity = paddr->precord->sevr;
+        *severity = precord->sevr;
     return 0;
 }
 
 static long dbDbGetTimeStamp(const struct link *plink, epicsTimeStamp *pstamp)
 {
-    DBADDR *paddr = (DBADDR *) plink->value.pv_link.pvt;
-
-    *pstamp = paddr->precord->time;
+    dbChannel *chan = linkChannel(plink);
+    dbCommon *precord = dbChannelRecord(chan);
+    *pstamp = precord->time;
     return 0;
 }
 
@@ -324,9 +357,10 @@ static long dbDbPutValue(struct link *plink, short dbrType,
         const void *pbuffer, long nRequest)
 {
     struct pv_link *ppv_link = &plink->value.pv_link;
+    dbChannel *chan = linkChannel(plink);
     struct dbCommon *psrce = plink->precord;
-    DBADDR *paddr = (DBADDR *) ppv_link->pvt;
-    dbCommon *pdest = paddr->precord;
+    DBADDR *paddr = &chan->addr;
+    dbCommon *pdest = dbChannelRecord(chan);
     long status = dbPut(paddr, dbrType, pbuffer, nRequest);
 
     recGblInheritSevr(ppv_link->pvlMask & pvlOptMsMode, pdest, psrce->nsta,
@@ -334,7 +368,7 @@ static long dbDbPutValue(struct link *plink, short dbrType,
     if (status)
         return status;
 
-    if (paddr->pfield == (void *) &pdest->proc ||
+    if (dbChannelField(chan) == (void *) &pdest->proc ||
         (ppv_link->pvlMask & pvlOptPP && pdest->scan == 0)) {
         status = processTarget(psrce, pdest);
     }
@@ -345,9 +379,8 @@ static long dbDbPutValue(struct link *plink, short dbrType,
 static void dbDbScanFwdLink(struct link *plink)
 {
     dbCommon *precord = plink->precord;
-    dbAddr *paddr = (dbAddr *) plink->value.pv_link.pvt;
-
-    dbScanPassive(precord, paddr->precord);
+    dbChannel *chan = linkChannel(plink);
+    dbScanPassive(precord, dbChannelRecord(chan));
 }
 
 static long doLocked(struct link *plink, dbLinkUserCallback rtn, void *priv)
diff --git a/modules/database/src/ioc/db/dbDbLink.h b/modules/database/src/ioc/db/dbDbLink.h
index c367720..27972d8 100644
--- a/modules/database/src/ioc/db/dbDbLink.h
+++ b/modules/database/src/ioc/db/dbDbLink.h
@@ -26,7 +26,7 @@ struct dbLocker;
 
 epicsShareFunc long dbDbInitLink(struct link *plink, short dbfType);
 epicsShareFunc void dbDbAddLink(struct dbLocker *locker, struct link *plink,
-    short dbfType, DBADDR *ptarget);
+    short dbfType, dbChannel *ptarget);
 
 #ifdef __cplusplus
 }
diff --git a/modules/database/src/ioc/db/dbEvent.c b/modules/database/src/ioc/db/dbEvent.c
index 0d96e12..532c2ba 100644
--- a/modules/database/src/ioc/db/dbEvent.c
+++ b/modules/database/src/ioc/db/dbEvent.c
@@ -251,18 +251,15 @@ int dbel ( const char *pname, unsigned level )
 }
 
 /*
- * DB_INIT_EVENTS()
+ * DB_INIT_EVENT_FREELISTS()
  *
  *
- * Initialize the event facility for this task. Must be called at least once
- * by each task which uses the db event facility
+ * Initialize the free lists used by the event facility.
+ * Safe to be called multiple times.
  *
- * returns: ptr to event user block or NULL if memory can't be allocated
  */
-dbEventCtx db_init_events (void)
+void db_init_event_freelists (void)
 {
-    struct event_user * evUser;
-
     if (!dbevEventUserFreeList) {
         freeListInitPvt(&dbevEventUserFreeList,
             sizeof(struct event_user),8);
@@ -279,6 +276,22 @@ dbEventCtx db_init_events (void)
         freeListInitPvt(&dbevFieldLogFreeList,
             sizeof(struct db_field_log),2048);
     }
+}
+
+/*
+ * DB_INIT_EVENTS()
+ *
+ *
+ * Initialize the event facility for this task. Must be called at least once
+ * by each task which uses the db event facility
+ *
+ * returns: ptr to event user block or NULL if memory can't be allocated
+ */
+dbEventCtx db_init_events (void)
+{
+    struct event_user * evUser;
+
+    db_init_event_freelists();
 
     evUser = (struct event_user *)
         freeListCalloc(dbevEventUserFreeList);
@@ -654,27 +667,22 @@ int db_post_extra_labor (dbEventCtx ctx)
     return DB_EVENT_OK;
 }
 
-/*
- *  DB_CREATE_EVENT_LOG()
- *
- *  NOTE: This assumes that the db scan lock is already applied
- *        (as it copies data from the record)
- */
-db_field_log* db_create_event_log (struct evSubscrip *pevent)
+static db_field_log* db_create_field_log (struct dbChannel *chan, int use_val)
 {
     db_field_log *pLog = (db_field_log *) freeListCalloc(dbevFieldLogFreeList);
 
     if (pLog) {
-        struct dbChannel *chan = pevent->chan;
         struct dbCommon  *prec = dbChannelRecord(chan);
-        pLog->ctx = dbfl_context_event;
-        if (pevent->useValque) {
+        pLog->stat = prec->stat;
+        pLog->sevr = prec->sevr;
+        pLog->time = prec->time;
+        pLog->field_type  = dbChannelFieldType(chan);
+        pLog->field_size  = dbChannelFieldSize(chan);
+        pLog->no_elements = dbChannelElements(chan);
+        pLog->offset = 0;
+
+        if (use_val) {
             pLog->type = dbfl_type_val;
-            pLog->stat = prec->stat;
-            pLog->sevr = prec->sevr;
-            pLog->time = prec->time;
-            pLog->field_type  = dbChannelFieldType(chan);
-            pLog->no_elements = dbChannelElements(chan);
             /*
              * use memcpy to avoid a bus error on
              * union copy of char in the db at an odd
@@ -684,23 +692,62 @@ db_field_log* db_create_event_log (struct evSubscrip *pevent)
                    dbChannelField(chan),
                    dbChannelFieldSize(chan));
         } else {
-            pLog->type = dbfl_type_rec;
+            rset *prset;
+
+            pLog->type = dbfl_type_ref;
+
+            if (dbChannelSpecial(chan) == SPC_DBADDR &&
+                (prset = dbGetRset(&chan->addr)) &&
+                prset->get_array_info)
+                /* This condition implies !use_val, see db_add_event */
+            {
+                void *pfieldsave = dbChannelField(chan);
+                prec = dbChannelRecord(chan);
+                prset->get_array_info(&chan->addr, &pLog->no_elements, &pLog->offset);
+                /* don't make a copy yet, just reference the field value */
+                pLog->u.r.field = dbChannelField(chan);
+                dbChannelField(chan) = pfieldsave;
+            }
+            else {
+                /* don't make a copy yet, just reference the field value */
+                pLog->u.r.field = dbChannelField(chan);
+            }
+            /* indicate field value still owned by record */
+            pLog->u.r.dtor = NULL;
+            /* no private data yet, may be set by a filter */
+            pLog->u.r.pvt = NULL;
         }
     }
     return pLog;
 }
 
 /*
+ *  DB_CREATE_EVENT_LOG()
+ *
+ *  NOTE: This assumes that the db scan lock is already applied
+ *        (as it calls rset->get_array_info)
+ */
+db_field_log* db_create_event_log (struct evSubscrip *pevent)
+{
+    db_field_log *pLog = db_create_field_log(pevent->chan, pevent->useValque);
+    if (pLog) {
+        pLog->ctx  = dbfl_context_event;
+    }
+    return pLog;
+}
+
+/*
  *  DB_CREATE_READ_LOG()
  *
  */
 db_field_log* db_create_read_log (struct dbChannel *chan)
 {
-    db_field_log *pLog = (db_field_log *) freeListCalloc(dbevFieldLogFreeList);
-
+    db_field_log *pLog = db_create_field_log(chan,
+        dbChannelElements(chan) == 1 &&
+        dbChannelSpecial(chan) != SPC_DBADDR &&
+        dbChannelFieldSize(chan) <= sizeof(union native_value));
     if (pLog) {
         pLog->ctx  = dbfl_context_read;
-        pLog->type = dbfl_type_rec;
     }
     return pLog;
 }
@@ -724,20 +771,6 @@ static void db_queue_event_log (evSubscrip *pevent, db_field_log *pLog)
     LOCKEVQUE (ev_que);
 
     /*
-     * if we have an event on the queue and both the last
-     * event on the queue and the current event are emtpy
-     * (i.e. of type dbfl_type_rec), simply ignore duplicate
-     * events (saving empty events serves no purpose)
-     */
-    if (pevent->npend > 0u &&
-        (*pevent->pLastLog)->type == dbfl_type_rec &&
-        pLog->type == dbfl_type_rec) {
-        db_delete_field_log(pLog);
-        UNLOCKEVQUE (ev_que);
-        return;
-    }
-
-    /*
      * add to task local event que
      */
 
diff --git a/modules/database/src/ioc/db/dbEvent.h b/modules/database/src/ioc/db/dbEvent.h
index 374e849..175b0e4 100644
--- a/modules/database/src/ioc/db/dbEvent.h
+++ b/modules/database/src/ioc/db/dbEvent.h
@@ -50,6 +50,7 @@ epicsShareFunc int db_post_events (
 typedef void * dbEventCtx;
 
 typedef void EXTRALABORFUNC (void *extralabor_arg);
+void db_init_event_freelists (void);
 epicsShareFunc dbEventCtx db_init_events (void);
 epicsShareFunc int db_start_events (
     dbEventCtx ctx, const char *taskname, void (*init_func)(void *),
diff --git a/modules/database/src/ioc/db/dbExtractArray.c b/modules/database/src/ioc/db/dbExtractArray.c
index e16ab4c..f0ab281 100644
--- a/modules/database/src/ioc/db/dbExtractArray.c
+++ b/modules/database/src/ioc/db/dbExtractArray.c
@@ -13,11 +13,12 @@
 /*
  *  Author: Ralph Lange <Ralph.Lange at bessy.de>
  *
- *  based on dbConvert.c
+ *  based on dbConvert.c, see copyNoConvert
  *  written by: Bob Dalesio, Marty Kraimer
  */
 
 #include <string.h>
+#include <assert.h>
 
 #include "epicsTypes.h"
 
@@ -25,61 +26,31 @@
 #include "dbAddr.h"
 #include "dbExtractArray.h"
 
-void dbExtractArrayFromRec(const dbAddr *paddr, void *pto,
-                           long nRequest, long no_elements, long offset, long increment)
+void dbExtractArray(const void *pfrom, void *pto,
+    short field_size, short field_type,
+    long nRequest, long no_elements, long offset, long increment)
 {
     char *pdst = (char *) pto;
-    char *psrc = (char *) paddr->pfield;
-    long nUpperPart;
-    int i;
-    short srcSize = paddr->field_size;
-    short dstSize = srcSize;
-    char isString = (paddr->field_type == DBF_STRING);
+    const char *psrc = (char *) pfrom;
 
-    if (nRequest > no_elements) nRequest = no_elements;
-    if (isString && srcSize > MAX_STRING_SIZE) dstSize = MAX_STRING_SIZE;
+    /* assert preconditions */
+    assert(nRequest >= 0);
+    assert(no_elements >= 0);
+    assert(increment > 0);
+    assert(0 <= offset);
+    assert(offset < no_elements);
 
-    if (increment == 1 && dstSize == srcSize) {
-        nUpperPart = nRequest < no_elements - offset ? nRequest : no_elements - offset;
-        memcpy(pdst, &psrc[offset * srcSize], dstSize * nUpperPart);
+    if (increment == 1) {
+        long nUpperPart =
+            nRequest < no_elements - offset ? nRequest : no_elements - offset;
+        memcpy(pdst, psrc + (offset * field_size), field_size * nUpperPart);
         if (nRequest > nUpperPart)
-            memcpy(&pdst[dstSize * nUpperPart], psrc, dstSize * (nRequest - nUpperPart));
-        if (isString)
-            for (i = 1; i <= nRequest; i++)
-                pdst[dstSize*i-1] = '\0';
+            memcpy(pdst + (field_size * nUpperPart), psrc,
+                field_size * (nRequest - nUpperPart));
     } else {
-        for (; nRequest > 0; nRequest--, pdst += dstSize, offset += increment) {
+        for (; nRequest > 0; nRequest--, pdst += field_size, offset += increment) {
             offset %= no_elements;
-            memcpy(pdst, &psrc[offset*srcSize], dstSize);
-            if (isString) pdst[dstSize-1] = '\0';
-        }
-    }
-}
-
-void dbExtractArrayFromBuf(const void *pfrom, void *pto,
-                           short field_size, short field_type,
-                           long nRequest, long no_elements, long offset, long increment)
-{
-    char *pdst = (char *) pto;
-    char *psrc = (char *) pfrom;
-    int i;
-    short srcSize = field_size;
-    short dstSize = srcSize;
-    char isString = (field_type == DBF_STRING);
-
-    if (nRequest > no_elements) nRequest = no_elements;
-    if (offset > no_elements - 1) offset = no_elements - 1;
-    if (isString && dstSize >= MAX_STRING_SIZE) dstSize = MAX_STRING_SIZE - 1;
-
-    if (increment == 1) {
-        memcpy(pdst, &psrc[offset * srcSize], dstSize * nRequest);
-        if (isString)
-            for (i = 1; i <= nRequest; i++)
-                pdst[dstSize*i] = '\0';
-    } else {
-        for (; nRequest > 0; nRequest--, pdst += srcSize, offset += increment) {
-            memcpy(pdst, &psrc[offset*srcSize], dstSize);
-            if (isString) pdst[dstSize] = '\0';
+            memcpy(pdst, psrc + (offset * field_size), field_size);
         }
     }
 }
diff --git a/modules/database/src/ioc/db/dbExtractArray.h b/modules/database/src/ioc/db/dbExtractArray.h
index 7ed3584..9680660 100644
--- a/modules/database/src/ioc/db/dbExtractArray.h
+++ b/modules/database/src/ioc/db/dbExtractArray.h
@@ -21,11 +21,22 @@
 extern "C" {
 #endif
 
-epicsShareFunc void dbExtractArrayFromRec(const DBADDR *paddr, void *pto,
-                                          long nRequest, long no_elements, long offset, long increment);
-epicsShareFunc void dbExtractArrayFromBuf(const void *pfrom, void *pto,
-                                          short field_size, short field_type,
-                                          long nRequest, long no_elements, long offset, long increment);
+/*
+ * This function does not do any conversion. This means we don't have to
+ * truncate the field_size to MAX_STRING_SIZE or add extra null terminators
+ * for string values. All of this is done by the dbConvert routines which
+ * will be called whether or not a filter is active.
+ *
+ * Checked preconditions:
+ * - nRequest >= 0, no_elements >= 0, increment > 0
+ * - 0 <= offset < no_elements
+ * Unchecked preconditions:
+ * - pto points to a buffer with at least field_size*nRequest bytes
+ * - pfrom points to a buffer with at least field_size*no_elements bytes
+ */
+epicsShareFunc void dbExtractArray(const void *pfrom, void *pto,
+    short field_size, short field_type,
+    long nRequest, long no_elements, long offset, long increment);
 
 #ifdef __cplusplus
 }
diff --git a/modules/database/src/ioc/db/dbLink.c b/modules/database/src/ioc/db/dbLink.c
index 7c37058..aa72dc8 100644
--- a/modules/database/src/ioc/db/dbLink.c
+++ b/modules/database/src/ioc/db/dbLink.c
@@ -143,7 +143,7 @@ void dbInitLink(struct link *plink, short dbfType)
 }
 
 void dbAddLink(struct dbLocker *locker, struct link *plink, short dbfType,
-    DBADDR *ptarget)
+    dbChannel *ptarget)
 {
     struct dbCommon *precord = plink->precord;
 
diff --git a/modules/database/src/ioc/db/dbLink.h b/modules/database/src/ioc/db/dbLink.h
index 94e1f32..4255a56 100644
--- a/modules/database/src/ioc/db/dbLink.h
+++ b/modules/database/src/ioc/db/dbLink.h
@@ -20,6 +20,7 @@
 #include "epicsTypes.h"
 #include "epicsTime.h"
 #include "dbAddr.h"
+#include "dbChannel.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -368,7 +369,7 @@ epicsShareFunc const char * dbLinkFieldName(const struct link *plink);
 
 epicsShareFunc void dbInitLink(struct link *plink, short dbfType);
 epicsShareFunc void dbAddLink(struct dbLocker *locker, struct link *plink,
-        short dbfType, DBADDR *ptarget);
+        short dbfType, dbChannel *ptarget);
 
 epicsShareFunc void dbLinkOpen(struct link *plink);
 epicsShareFunc void dbRemoveLink(struct dbLocker *locker, struct link *plink);
diff --git a/modules/database/src/ioc/db/dbLock.c b/modules/database/src/ioc/db/dbLock.c
index 8df755b..19cb1c0 100644
--- a/modules/database/src/ioc/db/dbLock.c
+++ b/modules/database/src/ioc/db/dbLock.c
@@ -743,14 +743,14 @@ void dbLockSetSplit(dbLocker *locker, dbCommon *pfirst, dbCommon *psecond)
             for(i=0; i<rtype->no_links; i++) {
                 dbFldDes *pdesc = rtype->papFldDes[rtype->link_ind[i]];
                 DBLINK *plink = (DBLINK*)((char*)prec + pdesc->offset);
-                DBADDR *ptarget;
+                dbChannel *chan;
                 lockRecord *lr;
 
                 if(plink->type!=DB_LINK)
                     continue;
 
-                ptarget = plink->value.pv_link.pvt;
-                lr = ptarget->precord->lset;
+                chan = plink->value.pv_link.pvt;
+                lr = dbChannelRecord(chan)->lset;
                 assert(lr);
 
                 if(lr->precord==pfirst) {
diff --git a/modules/database/src/ioc/db/dbTest.c b/modules/database/src/ioc/db/dbTest.c
index 1193954..206d6c9 100644
--- a/modules/database/src/ioc/db/dbTest.c
+++ b/modules/database/src/ioc/db/dbTest.c
@@ -341,14 +341,14 @@ long dbgf(const char *pname)
     no_elements = MIN(addr.no_elements, sizeof(buffer)/addr.field_size);
     if (addr.dbr_field_type == DBR_ENUM) {
         long status = dbGetField(&addr, DBR_STRING, pbuffer,
-            &options, &no_elements, NULL);
+            &options, &no_elements);
 
         printBuffer(status, DBR_STRING, pbuffer, 0L, 0L,
             no_elements, &msg_Buff, 10);
     }
     else {
         long status = dbGetField(&addr, addr.dbr_field_type, pbuffer,
-            &options, &no_elements, NULL);
+            &options, &no_elements);
 
         printBuffer(status, addr.dbr_field_type, pbuffer, 0L, 0L,
             no_elements, &msg_Buff, 10);
@@ -487,7 +487,7 @@ long dbtgf(const char *pname)
     ret_options = req_options;
     no_elements = 0;
     status = dbGetField(&addr, addr.dbr_field_type, pbuffer,
-        &ret_options, &no_elements, NULL);
+        &ret_options, &no_elements);
     printBuffer(status, addr.dbr_field_type, pbuffer,
         req_options, ret_options, no_elements, pMsgBuff, tab_size);
 
@@ -496,62 +496,62 @@ long dbtgf(const char *pname)
 
     dbr_type = DBR_STRING;
     no_elements = MIN(addr.no_elements,((sizeof(buffer))/MAX_STRING_SIZE));
-    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
+    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements);
     printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);
 
     dbr_type = DBR_CHAR;
     no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsInt8)));
-    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
+    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements);
     printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);
 
     dbr_type = DBR_UCHAR;
     no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsUInt8)));
-    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
+    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements);
     printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);
 
     dbr_type = DBR_SHORT;
     no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsInt16)));
-    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
+    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements);
     printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);
 
     dbr_type = DBR_USHORT;
     no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsUInt16)));
-    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
+    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements);
     printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);
 
     dbr_type = DBR_LONG;
     no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsInt32)));
-    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
+    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements);
     printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);
 
     dbr_type = DBR_ULONG;
     no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsUInt32)));
-    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
+    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements);
     printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);
 
     dbr_type = DBR_INT64;
     no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsInt64)));
-    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
+    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements);
     printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);
 
     dbr_type = DBR_UINT64;
     no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsUInt64)));
-    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
+    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements);
     printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);
 
     dbr_type = DBR_FLOAT;
     no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsFloat32)));
-    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
+    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements);
     printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);
 
     dbr_type = DBR_DOUBLE;
     no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsFloat64)));
-    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
+    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements);
     printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);
 
     dbr_type = DBR_ENUM;
     no_elements = MIN(addr.no_elements,((sizeof(buffer))/sizeof(epicsEnum16)));
-    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements,NULL);
+    status = dbGetField(&addr,dbr_type,pbuffer,&ret_options,&no_elements);
     printBuffer(status,dbr_type,pbuffer,0L,0L,no_elements,pMsgBuff,tab_size);
 
     pmsg[0] = '\0';
@@ -651,7 +651,7 @@ long dbtpf(const char *pname, const char *pvalue)
 
                 printf("Put as DBR_%-6s Ok, result as ", dbr[put_type]);
                 status = dbGetField(&addr, addr.dbr_field_type, pbuffer,
-                    &options, &no_elements, NULL);
+                    &options, &no_elements);
                 printBuffer(status, addr.dbr_field_type, pbuffer, 0L, 0L,
                     no_elements, pMsgBuff, tab_size);
             }
diff --git a/modules/database/src/ioc/db/dbUnitTest.c b/modules/database/src/ioc/db/dbUnitTest.c
index 6846ef5..458a28b 100644
--- a/modules/database/src/ioc/db/dbUnitTest.c
+++ b/modules/database/src/ioc/db/dbUnitTest.c
@@ -197,7 +197,7 @@ void testdbVGetFieldEqual(const char* pv, short dbrType, va_list ap)
         return;
     }
 
-    status = dbGetField(&addr, dbrType, pod.bytes, NULL, &nReq, NULL);
+    status = dbGetField(&addr, dbrType, pod.bytes, NULL, &nReq);
     if (status) {
         testFail("dbGetField(\"%s\", %d, ...) -> %#lx (%s)", pv, dbrType, status, errSymMsg(status));
         return;
@@ -270,7 +270,7 @@ void testdbGetArrFieldEqual(const char* pv, short dbfType, long nRequest, unsign
         return;
     }
 
-    status = dbGetField(&addr, dbfType, gbuf, NULL, &nRequest, NULL);
+    status = dbGetField(&addr, dbfType, gbuf, NULL, &nRequest);
     if (status) {
         testFail("dbGetField(\"%s\", %d, ...) -> %#lx", pv, dbfType, status);
 
diff --git a/modules/database/src/ioc/db/db_field_log.h b/modules/database/src/ioc/db/db_field_log.h
index 1534517..4b3b82d 100644
--- a/modules/database/src/ioc/db/db_field_log.h
+++ b/modules/database/src/ioc/db/db_field_log.h
@@ -56,20 +56,31 @@ union native_value {
 struct db_field_log;
 typedef void (dbfl_freeFunc)(struct db_field_log *pfl);
 
-/* Types of db_field_log: rec = use record, val = val inside, ref = reference inside */
+/*
+ * A db_field_log has one of two types:
+ *
+ * dbfl_type_ref - Reference to value
+ *  Used for variable size (array) data types.  Meta-data
+ *  is stored in the field log, but value data is stored externally.
+ *  Only the dbfl_ref side of the data union is valid.
+ *
+ * dbfl_type_val - Internal value
+ *  Used to store small scalar data.  Meta-data and value are
+ *  present in this structure and no external references are used.
+ *  Only the dbfl_val side of the data union is valid.
+ */
 typedef enum dbfl_type {
-    dbfl_type_rec = 0,
     dbfl_type_val,
     dbfl_type_ref
 } dbfl_type;
 
 /* Context of db_field_log: event = subscription update, read = read reply */
 typedef enum dbfl_context {
-    dbfl_context_read = 0,
+    dbfl_context_read,
     dbfl_context_event
 } dbfl_context;
 
-#define dbflTypeStr(t) (t==dbfl_type_val?"val":t==dbfl_type_rec?"rec":"ref")
+#define dbflTypeStr(t) (t==dbfl_type_val?"val":"ref")
 
 struct dbfl_val {
     union native_value field; /* Field value */
@@ -81,6 +92,8 @@ struct dbfl_val {
  * db_delete_field_log().  Any code which changes a dbfl_type_ref
  * field log to another type, or to reference different data,
  * must explicitly call the dtor function.
+ * If the dtor is NULL, then this means the array data is still owned
+ * by a record.
  */
 struct dbfl_ref {
     dbfl_freeFunc     *dtor;  /* Callback to free filter-allocated resources */
@@ -88,8 +101,17 @@ struct dbfl_ref {
     void              *field; /* Field value */
 };
 
+/*
+ * Note: The offset member is understood to apply an implicit index mapping
+ *
+ *      i' = (i + offset) % no_elements
+ *
+ * of request index i. The resulting i' is used to to index into u.r.field.
+ *
+ * Also note that field_size may be larger than MAX_STRING_SIZE.
+ */
 typedef struct db_field_log {
-    unsigned int     type:2;  /* type (union) selector */
+    unsigned int     type:1;  /* type (union) selector */
     /* ctx is used for all types */
     unsigned int      ctx:1;  /* context (operation type) */
     /* the following are used for value and reference types */
@@ -97,37 +119,15 @@ typedef struct db_field_log {
     unsigned short     stat;  /* Alarm Status */
     unsigned short     sevr;  /* Alarm Severity */
     short        field_type;  /* DBF type of data */
-    short        field_size;  /* Data size */
-    long        no_elements;  /* No of array elements */
+    short        field_size;  /* Size of a single element */
+    long        no_elements;  /* No of valid array elements */
+    long             offset;  /* See above */
     union {
         struct dbfl_val v;
         struct dbfl_ref r;
     } u;
 } db_field_log;
 
-/*
- * A db_field_log will in one of three types:
- *
- * dbfl_type_rec - Reference to record
- *  The field log stores no data itself.  Data must instead be taken
- *  via the dbChannel* which must always be provided when along
- *  with the field log.
- *  For this type only the 'type' and 'ctx' members are used.
- *
- * dbfl_type_ref - Reference to outside value
- *  Used for variable size (array) data types.  Meta-data
- *  is stored in the field log, but value data is stored externally
- *  (see struct dbfl_ref).
- *  For this type all meta-data members are used.  The dbfl_ref side of the
- *  data union is used.
- *
- * dbfl_type_val - Internal value
- *  Used to store small scalar data.  Meta-data and value are
- *  present in this structure and no external references are used.
- *  For this type all meta-data members are used.  The dbfl_val side of the
- *  data union is used.
- */
-
 #ifdef __cplusplus
 }
 #endif
diff --git a/modules/database/src/std/dev/devAiSoft.c b/modules/database/src/std/dev/devAiSoft.c
index 0ecc1b1..5f9923e 100644
--- a/modules/database/src/std/dev/devAiSoft.c
+++ b/modules/database/src/std/dev/devAiSoft.c
@@ -96,9 +96,10 @@ static long read_ai(aiRecord *prec)
 
         prec->udf = FALSE;
         prec->dpvt = &devAiSoft;        /* Any non-zero value */
+        return 2;
     }
     else
         prec->dpvt = NULL;
 
-    return 2;
+    return status;
 }
diff --git a/modules/database/src/std/filters/arr.c b/modules/database/src/std/filters/arr.c
index f91708a..b90335e 100644
--- a/modules/database/src/std/filters/arr.c
+++ b/modules/database/src/std/filters/arr.c
@@ -12,16 +12,13 @@
 
 #include <stdio.h>
 
-#include <freeList.h>
-#include <dbAccess.h>
-#include <dbExtractArray.h>
-#include <db_field_log.h>
-#include <dbLock.h>
-#include <recSup.h>
-#include <epicsExit.h>
-#include <special.h>
-#include <chfPlugin.h>
-#include <epicsExport.h>
+#include "chfPlugin.h"
+#include "dbExtractArray.h"
+#include "db_field_log.h"
+#include "dbLock.h"
+#include "epicsExit.h"
+#include "freeList.h"
+#include "epicsExport.h"
 
 typedef struct myStruct {
     epicsInt32 start;
@@ -45,6 +42,8 @@ static void * allocPvt(void)
     myStruct *my = (myStruct*) freeListCalloc(myStructFreeList);
     if (!my) return NULL;
 
+    /* defaults */
+    my->start = 0;
     my->incr = 1;
     my->end = -1;
     return (void *) my;
@@ -93,78 +92,61 @@ static long wrapArrayIndices(long *start, const long increment, long *end,
 static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl)
 {
     myStruct *my = (myStruct*) pvt;
-    struct dbCommon *prec;
-    rset *prset;
+    int must_lock;
     long start = my->start;
     long end = my->end;
-    long nTarget = 0;
-    long offset = 0;
-    long nSource = dbChannelElements(chan);
-    long capacity = nSource;
-    void *pdst;
+    long nTarget;
+    void *pTarget;
+    /* initial values for the source array */
+    long offset = pfl->offset;
+    long nSource = pfl->no_elements;
 
     switch (pfl->type) {
     case dbfl_type_val:
-        /* Only filter arrays */
+        /* TODO Treat scalars as arrays with 1 element */
         break;
 
-    case dbfl_type_rec:
-        /* Extract from record */
-        if (dbChannelSpecial(chan) == SPC_DBADDR &&
-            nSource > 1 &&
-            (prset = dbGetRset(&chan->addr)) &&
-            prset->get_array_info)
-        {
-            void *pfieldsave = dbChannelField(chan);
-            prec = dbChannelRecord(chan);
-            dbScanLock(prec);
-            prset->get_array_info(&chan->addr, &nSource, &offset);
-            nTarget = wrapArrayIndices(&start, my->incr, &end, nSource);
-            pfl->type = dbfl_type_ref;
-            pfl->stat = prec->stat;
-            pfl->sevr = prec->sevr;
-            pfl->time = prec->time;
-            pfl->field_type = dbChannelFieldType(chan);
-            pfl->field_size = dbChannelFieldSize(chan);
-            pfl->no_elements = nTarget;
-            if (nTarget) {
-                pdst = freeListCalloc(my->arrayFreeList);
-                if (pdst) {
-                    pfl->u.r.dtor = freeArray;
-                    pfl->u.r.pvt = my->arrayFreeList;
-                    offset = (offset + start) % dbChannelElements(chan);
-                    dbExtractArrayFromRec(&chan->addr, pdst, nTarget, capacity,
-                        offset, my->incr);
-                    pfl->u.r.field = pdst;
-                }
-            }
-            dbScanUnlock(prec);
-            dbChannelField(chan) = pfieldsave;
-        }
-        break;
-
-    /* Extract from buffer */
     case dbfl_type_ref:
-        pdst = NULL;
-        nSource = pfl->no_elements;
+        must_lock = !pfl->u.r.dtor;
+        if (must_lock)
+            dbScanLock(dbChannelRecord(chan));
         nTarget = wrapArrayIndices(&start, my->incr, &end, nSource);
-        pfl->no_elements = nTarget;
-        if (nTarget) {
-            /* Copy the data out */
-            void *psrc = pfl->u.r.field;
-
-            pdst = freeListCalloc(my->arrayFreeList);
-            if (!pdst) break;
-            offset = start;
-            dbExtractArrayFromBuf(psrc, pdst, pfl->field_size, pfl->field_type,
-                nTarget, nSource, offset, my->incr);
-        }
-        if (pfl->u.r.dtor) pfl->u.r.dtor(pfl);
-        if (nTarget) {
+        /*
+         * Side note: it would be nice if we could avoid the copying in
+         * the case of my->incr==1. This is currently not possible due to
+         * the way the offset member is interpreted (namely as shifting the
+         * array in a ring-buffer style).
+         */
+        if (nTarget > 0) {
+            /* copy the data */
+            void *pSource = pfl->u.r.field;
+            pTarget = freeListCalloc(my->arrayFreeList);
+            if (!pTarget) break;
+            offset = (offset + start) % nSource;
+            dbExtractArray(pSource, pTarget, pfl->field_size,
+                pfl->field_type, nTarget, nSource, offset, my->incr);
+            if (pfl->u.r.dtor) pfl->u.r.dtor(pfl);
+            pfl->u.r.field = pTarget;
             pfl->u.r.dtor = freeArray;
             pfl->u.r.pvt = my->arrayFreeList;
-            pfl->u.r.field = pdst;
         }
+        /* adjust offset and no_elements to refer to the new pTarget */
+        pfl->offset = 0;
+        /*
+         * Setting pfl->no_elements outside of the "if" clause above is
+         * done to make requests fail if nTarget is zero, that is, if all
+         * elements selected by the filter are outside the array bounds.
+         * TODO:
+         * It would be possible to lift this restriction by interpreting
+         * a request with *no* number of elements (NULL pointer) as scalar
+         * (meaning: fail if we get less than one element); in contrast,
+         * a request that explicitly specifies one element would be
+         * interpreted as an array request, for which zero elements would
+         * be a normal expected result.
+         */
+        pfl->no_elements = nTarget;
+        if (must_lock)
+            dbScanUnlock(dbChannelRecord(chan));
         break;
     }
     return pfl;
diff --git a/modules/database/src/std/filters/ts.c b/modules/database/src/std/filters/ts.c
index 5925b0b..56c9f5b 100644
--- a/modules/database/src/std/filters/ts.c
+++ b/modules/database/src/std/filters/ts.c
@@ -11,21 +11,39 @@
  */
 
 #include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
 
-#include <chfPlugin.h>
-#include <dbLock.h>
-#include <db_field_log.h>
-#include <epicsExport.h>
+#include "chfPlugin.h"
+#include "db_field_log.h"
+#include "dbLock.h"
+#include "epicsExport.h"
+
+/*
+ * The size of the data is different for each channel, and can even
+ * change at runtime, so a freeList doesn't make much sense here.
+ */
+static void freeArray(db_field_log *pfl) {
+    free(pfl->u.r.field);
+}
 
 static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl) {
     epicsTimeStamp now;
     epicsTimeGetCurrent(&now);
 
-    /* If string or array, must make a copy (to ensure coherence between time and data) */
-    if (pfl->type == dbfl_type_rec) {
-        dbScanLock(dbChannelRecord(chan));
-        dbChannelMakeArrayCopy(pvt, pfl, chan);
-        dbScanUnlock(dbChannelRecord(chan));
+    /* If reference and not already copied,
+       must make a copy (to ensure coherence between time and data) */
+    if (pfl->type == dbfl_type_ref && !pfl->u.r.dtor) {
+        void *pTarget = calloc(pfl->no_elements, pfl->field_size);
+        void *pSource = pfl->u.r.field;
+        if (pTarget) {
+            dbScanLock(dbChannelRecord(chan));
+            memcpy(pTarget, pSource, pfl->field_size * pfl->no_elements);
+            pfl->u.r.field = pTarget;
+            pfl->u.r.dtor = freeArray;
+            pfl->u.r.pvt = pvt;
+            dbScanUnlock(dbChannelRecord(chan));
+        }
     }
 
     pfl->time = now;
diff --git a/modules/database/test/ioc/db/dbChArrTest.cpp b/modules/database/test/ioc/db/dbChArrTest.cpp
index 8255fdc..ff74e01 100644
--- a/modules/database/test/ioc/db/dbChArrTest.cpp
+++ b/modules/database/test/ioc/db/dbChArrTest.cpp
@@ -130,7 +130,7 @@ static void check(short dbr_type) {
     memset(buf, 0, sizeof(buf)); \
     (void) dbPutField(&offaddr, DBR_LONG, &off, 1); \
     pfl = db_create_read_log(pch); \
-    testOk(pfl && pfl->type == dbfl_type_rec, "Valid pfl, type = rec"); \
+    testOk(pfl && pfl->type == dbfl_type_ref, "Valid pfl, type = ref"); \
     testOk(!dbChannelGetField(pch, DBR_LONG, buf, NULL, &req, pfl), "Got Field value"); \
     testOk(req == Size, "Got %ld elements (expected %d)", req, Size); \
     if (!testOk(!memcmp(buf, Expected, sizeof(Expected)), "Data correct")) \
@@ -178,6 +178,7 @@ static void check(short dbr_type) {
     pfl->field_type = DBF_CHAR; \
     pfl->field_size = 1; \
     pfl->no_elements = 26; \
+    pfl->offset = 0; \
     pfl->u.r.dtor = freeArray; \
     pfl->u.r.field = epicsStrDup("abcdefghijklmnopqrsstuvwxyz"); \
     testOk(!dbChannelGetField(pch, DBR_LONG, buf, NULL, &req, pfl), "Got Field value"); \
diff --git a/modules/database/test/std/filters/arrTest.cpp b/modules/database/test/std/filters/arrTest.cpp
index 1ec16b3..08ba07b 100644
--- a/modules/database/test/std/filters/arrTest.cpp
+++ b/modules/database/test/std/filters/arrTest.cpp
@@ -56,25 +56,26 @@ const char *server_port = CA_SERVER_PORT;
 
 static int fl_equals_array(short type, const db_field_log *pfl1, void *p2) {
     for (int i = 0; i < pfl1->no_elements; i++) {
+        int j = (i + pfl1->offset) % pfl1->no_elements;
         switch (type) {
         case DBR_DOUBLE:
-            if (((epicsFloat64*)pfl1->u.r.field)[i] != ((epicsInt32*)p2)[i]) {
+            if (((epicsFloat64*)pfl1->u.r.field)[j] != ((epicsInt32*)p2)[i]) {
                 testDiag("at index=%d: field log has %g, should be %d",
-                         i, ((epicsFloat64*)pfl1->u.r.field)[i], ((epicsInt32*)p2)[i]);
+                         i, ((epicsFloat64*)pfl1->u.r.field)[j], ((epicsInt32*)p2)[i]);
                 return 0;
             }
             break;
         case DBR_LONG:
-            if (((epicsInt32*)pfl1->u.r.field)[i] != ((epicsInt32*)p2)[i]) {
+            if (((epicsInt32*)pfl1->u.r.field)[j] != ((epicsInt32*)p2)[i]) {
                 testDiag("at index=%d: field log has %d, should be %d",
-                         i, ((epicsInt32*)pfl1->u.r.field)[i], ((epicsInt32*)p2)[i]);
+                         i, ((epicsInt32*)pfl1->u.r.field)[j], ((epicsInt32*)p2)[i]);
                 return 0;
             }
             break;
         case DBR_STRING:
-            if (strtol(&((const char*)pfl1->u.r.field)[i*MAX_STRING_SIZE], NULL, 0) != ((epicsInt32*)p2)[i]) {
+            if (strtol(&((const char*)pfl1->u.r.field)[j*pfl1->field_size], NULL, 0) != ((epicsInt32*)p2)[i]) {
                 testDiag("at index=%d: field log has '%s', should be '%d'",
-                         i, &((const char*)pfl1->u.r.field)[i*MAX_STRING_SIZE], ((epicsInt32*)p2)[i]);
+                         i, &((const char*)pfl1->u.r.field)[j*pfl1->field_size], ((epicsInt32*)p2)[i]);
                 return 0;
             }
             break;
@@ -119,7 +120,7 @@ static void testHead (const char *title, const char *typ = "") {
     off = Offset; \
     (void) dbPutField(&offaddr, DBR_LONG, &off, 1); \
     pfl = db_create_read_log(pch); \
-    testOk(pfl->type == dbfl_type_rec, "original field log has type rec"); \
+    testOk(pfl->type == dbfl_type_ref, "original field log has type ref"); \
     pfl2 = dbChannelRunPostChain(pch, pfl); \
     testOk(pfl2 == pfl, "call does not drop or replace field_log"); \
     testOk(pfl->type == dbfl_type_ref, "filtered field log has type ref"); \
diff --git a/modules/database/test/std/filters/dbndTest.c b/modules/database/test/std/filters/dbndTest.c
index 4d70f83..fd4a472 100644
--- a/modules/database/test/std/filters/dbndTest.c
+++ b/modules/database/test/std/filters/dbndTest.c
@@ -129,7 +129,7 @@ MAIN(dbndTest)
     dbEventCtx evtctx;
     int logsFree, logsFinal;
 
-    testPlan(77);
+    testPlan(72);
 
     testdbPrepare();
 
@@ -170,12 +170,9 @@ MAIN(dbndTest)
            "dbnd has one filter with argument in pre chain");
     testOk((ellCount(&pch->post_chain) == 0), "dbnd has no filter in post chain");
 
-    /* Field logs of type ref and rec: pass any update */
-
-    testHead("Field logs of type ref and rec");
-    fl1.type = dbfl_type_rec;
-    mustPassTwice(pch, &fl1, "abs field_log=rec", 0., 0);
+    /* Field logs of type ref: pass any update */
 
+    testHead("Field logs of type ref");
     fl1.type = dbfl_type_ref;
     mustPassTwice(pch, &fl1, "abs field_log=ref", 0., 0);
 
diff --git a/modules/database/test/std/rec/Makefile b/modules/database/test/std/rec/Makefile
index 8c087b3..3d7cff9 100644
--- a/modules/database/test/std/rec/Makefile
+++ b/modules/database/test/std/rec/Makefile
@@ -156,6 +156,13 @@ asyncproctest_SRCS += asyncproctest_registerRecordDeviceDriver.cpp
 TESTFILES += $(COMMON_DIR)/asyncproctest.dbd ../asyncproctest.db
 TESTS += asyncproctest
 
+TESTPROD_HOST += linkFilterTest
+linkFilterTest_SRCS += linkFilterTest.c
+linkFilterTest_SRCS += recTestIoc_registerRecordDeviceDriver.cpp
+testHarness_SRCS += linkFilterTest.c
+TESTFILES += ../linkFilterTest.db
+TESTS += linkFilterTest
+
 # dbHeader* is only a compile test
 # no need to actually run
 TESTPROD += dbHeaderTest
diff --git a/modules/database/test/std/rec/linkFilterTest.c b/modules/database/test/std/rec/linkFilterTest.c
new file mode 100644
index 0000000..6f38d24
--- /dev/null
+++ b/modules/database/test/std/rec/linkFilterTest.c
@@ -0,0 +1,157 @@
+/*************************************************************************\
+* Copyright (c) 2020 Dirk Zimoch
+* EPICS BASE is distributed subject to a Software License Agreement found
+* in file LICENSE that is included with this distribution.
+\*************************************************************************/
+
+#include <string.h>
+
+#include "dbAccess.h"
+#include "devSup.h"
+#include "alarm.h"
+#include "dbUnitTest.h"
+#include "errlog.h"
+#include "epicsThread.h"
+
+#include "longinRecord.h"
+
+#include "testMain.h"
+
+void recTestIoc_registerRecordDeviceDriver(struct dbBase *);
+
+static void startTestIoc(const char *dbfile)
+{
+    testdbPrepare();
+    testdbReadDatabase("recTestIoc.dbd", NULL, NULL);
+    recTestIoc_registerRecordDeviceDriver(pdbbase);
+    testdbReadDatabase(dbfile, NULL, NULL);
+
+    eltc(0);
+    testIocInitOk();
+    eltc(1);
+}
+
+static void expectProcSuccess(const char *rec)
+{
+    char fieldname[20];
+    testDiag("expecting success from %s", rec);
+    sprintf(fieldname, "%s.PROC", rec);
+    testdbPutFieldOk(fieldname, DBF_LONG, 1);
+    sprintf(fieldname, "%s.SEVR", rec);
+    testdbGetFieldEqual(fieldname, DBF_LONG, NO_ALARM);
+    sprintf(fieldname, "%s.STAT", rec);
+    testdbGetFieldEqual(fieldname, DBF_LONG, NO_ALARM);
+}
+
+static void expectProcFailure(const char *rec)
+{
+    char fieldname[20];
+    testDiag("expecting failure S_db_badField %#x from %s", S_db_badField, rec);
+    sprintf(fieldname, "%s.PROC", rec);
+    testdbPutFieldFail(S_db_badField, fieldname, DBF_LONG, 1);
+    sprintf(fieldname, "%s.SEVR", rec);
+    testdbGetFieldEqual(fieldname, DBF_LONG, INVALID_ALARM);
+    sprintf(fieldname, "%s.STAT", rec);
+    testdbGetFieldEqual(fieldname, DBF_LONG, LINK_ALARM);
+}
+
+static void changeRange(long start, long stop, long step)
+{
+    char linkstring[60];
+    if (step)
+        sprintf(linkstring, "src.[%ld:%ld:%ld]", start, step, stop);
+    else if (stop)
+        sprintf(linkstring, "src.[%ld:%ld]", start, stop);
+    else
+        sprintf(linkstring, "src.[%ld]", start);
+    testDiag("modifying link: %s", linkstring);
+    testdbPutFieldOk("ai.INP", DBF_STRING, linkstring);
+    testdbPutFieldOk("wf.INP", DBF_STRING, linkstring);
+}
+
+static double buf[] = {1, 2, 3, 4, 5, 6, 7, 8, 0, 0, 2, 4, 6};
+
+static void expectRange(long start, long end)
+{
+    long n = end-start+1;
+    testdbGetFieldEqual("ai.VAL", DBF_DOUBLE, buf[start]);
+    testdbGetFieldEqual("wf.NORD", DBF_LONG, n);
+    testdbGetArrFieldEqual("wf.VAL", DBF_DOUBLE, n+2, n, buf+start);
+}
+
+#if 0
+static void expectEmptyArray(void)
+{
+    /* empty arrays are now allowed at the moment */
+    testDiag("expecting empty array");
+    testdbGetFieldEqual("wf.NORD", DBF_LONG, 0);
+}
+#endif
+
+MAIN(linkFilterTest)
+{
+    testPlan(98);
+    startTestIoc("linkFilterTest.db");
+
+    testDiag("PINI");
+    expectRange(2,4);
+
+    testDiag("modify range");
+    changeRange(3,6,0);
+    expectProcSuccess("ai");
+    expectProcSuccess("wf");
+    expectRange(3,6);
+
+    testDiag("backward range");
+    changeRange(5,3,0);
+    expectProcFailure("ai");
+    expectProcFailure("wf");
+
+    testDiag("step 2");
+    changeRange(1,6,2);
+    expectProcSuccess("ai");
+    expectProcSuccess("wf");
+    expectRange(10,12);
+
+    testDiag("range start beyond src.NORD");
+    changeRange(8,9,0);
+    expectProcFailure("ai");
+    expectProcFailure("wf");
+
+    testDiag("range end beyond src.NORD");
+    changeRange(3,9,0);
+    expectProcSuccess("ai");
+    expectProcSuccess("wf");
+    expectRange(3,7); /* clipped range */
+
+    testDiag("range start beyond src.NELM");
+    changeRange(11,12,0);
+    expectProcFailure("ai");
+    expectProcFailure("wf");
+
+    testDiag("range end beyond src.NELM");
+    changeRange(4,12,0);
+    expectProcSuccess("ai");
+    expectProcSuccess("wf");
+    expectRange(4,7); /* clipped range */
+
+    testDiag("single value beyond src.NORD");
+    changeRange(8,0,0);
+    expectProcFailure("ai");
+    expectProcFailure("wf");
+
+    testDiag("single value");
+    changeRange(5,0,0);
+    expectProcSuccess("ai");
+    expectProcSuccess("wf");
+    expectRange(5,5);
+
+    testDiag("single beyond rec.NELM");
+    changeRange(12,0,0);
+    expectProcFailure("ai");
+    expectProcFailure("wf");
+
+    testIocShutdownOk();
+    testdbCleanup();
+    return testDone();
+}
diff --git a/modules/database/test/std/rec/linkFilterTest.db b/modules/database/test/std/rec/linkFilterTest.db
new file mode 100644
index 0000000..5ee371e
--- /dev/null
+++ b/modules/database/test/std/rec/linkFilterTest.db
@@ -0,0 +1,16 @@
+record(waveform, "src") {
+    field(NELM, "10")
+    field(FTVL, "SHORT")
+    field(INP, [1, 2, 3, 4, 5, 6, 7, 8])
+}
+record(ai, "ai") {
+    field(INP, "src.[2]") # expect 3
+    field(PINI, "YES")
+}
+record(waveform, "wf") {
+    field(NELM, "5")
+    field(FTVL, "DOUBLE")
+    field(INP,  "src.[2:4]") # expect 3,4,5
+    field(PINI, "YES")
+}
+
diff --git a/modules/database/test/std/rec/regressTest.c b/modules/database/test/std/rec/regressTest.c
index 6614639..fd4f0a8 100644
--- a/modules/database/test/std/rec/regressTest.c
+++ b/modules/database/test/std/rec/regressTest.c
@@ -132,7 +132,7 @@ void testCADisconn(void)
 
     startRegressTestIoc("badCaLink.db");
 
-    testdbPutFieldOk("ai:disconn.PROC", DBF_LONG, 1);
+    testdbPutFieldFail(-1, "ai:disconn.PROC", DBF_LONG, 1);
     testdbGetFieldEqual("ai:disconn.SEVR", DBF_LONG, INVALID_ALARM);
     testdbGetFieldEqual("ai:disconn.STAT", DBF_LONG, LINK_ALARM);
 }

Replies:
Re: [Merge] ~bfrk/epics-base:write-filters into epics-base:7.0 Ben Franksen via Core-talk
[Merge] ~bfrk/epics-base:write-filters into epics-base:7.0 Ben Franksen via Core-talk

Navigate by Date:
Prev: Re: write to a single element of an array field Johnson, Andrew N. via Core-talk
Next: Re: [Merge] ~bfrk/epics-base:write-filters into epics-base:7.0 Ben Franksen via Core-talk
Index: 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  <2020
Navigate by Thread:
Prev: [Bug 1868680] Re: Access Security file reload (asInit) fails Andrew Johnson via Core-talk
Next: Re: [Merge] ~bfrk/epics-base:write-filters into epics-base:7.0 Ben Franksen via Core-talk
Index: 2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  <2020
ANJ, 28 May 2020 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· Search · EPICS V4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·