Merge ~dirk.zimoch/epics-base:FilterForInfoFields into epics-base:7.0

Proposed by Dirk Zimoch
Status: Needs review
Proposed branch: ~dirk.zimoch/epics-base:FilterForInfoFields
Merge into: epics-base:7.0
Diff against target: 735 lines (+448/-55)
7 files modified
documentation/RELEASE_NOTES.md (+9/-0)
modules/database/src/std/filters/Makefile (+1/-0)
modules/database/src/std/filters/filters.dbd.pod (+108/-55)
modules/database/src/std/filters/info.c (+189/-0)
modules/database/test/std/filters/Makefile (+7/-0)
modules/database/test/std/filters/epicsRunFilterTests.c (+2/-0)
modules/database/test/std/filters/infoTest.cpp (+132/-0)
Reviewer Review Type Date Requested Status
Andrew Johnson Approve
Dirk Zimoch (community) Needs Information
Review via email: mp+399414@code.launchpad.net

Commit message

Add filter to read info fields

To post a comment you must log in.
Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Works with R7.0.5 but stopped working after the merge with Ben's dbChannel refactoring). I am investigating why. It seems commit 85822f broke it.

review: Needs Fixing
Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

The breaking change is in dbAccess.c line 918.

73fcbb5... by Dirk Zimoch

fail early for info field names that are empty or over the implementation limit of 50 chars

a461993... by Simon

Initial test

Revision history for this message
Ben Franksen (bfrk) wrote :

In case you haven't solved it yet: what exactly breaks?

Revision history for this message
Ben Franksen (bfrk) wrote :

Your branch compiles and tests fine on my machine.

Revision history for this message
Ben Franksen (bfrk) wrote :

Hm, I think my copy of your branch doesn't have the latest changes. I am on your branch but when I
git pull https://git.launchpad.net/~dirk.zimoch/epics-base FilterForInfoFields
it wants me to add a merge commit. I don't want to merge I want your branch as is. How do I do that?

Revision history for this message
Ben Franksen (bfrk) wrote :

And when I checkout the latest commit 663e6442e47ea7329217f60b6ca3e5c6d636620d on your branch it tells me I am not on any branch. Obviously I am having a bad git moment.

cfd0ea6... by Simon

Added more tests

bffda44... by Simon

Minor cleanup

1abb9c3... by Simon

Added one more type check test

74a3fa6... by Simon

Slight cleanup

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

I am at commit 663e6442e47ea7329217f60b6ca3e5c6d636620d which is based on 3ba778c08bfbf6364bdbefa41f16022b4c0f41d5, before your refactory was merged in.
I have not pushed the re-base on top of f571c5950bfe476ef79d4c143f50e2f9e0d5edc7.

What fails: I get garbage instead of the correct data. Don't know yet where it came from. I suspect it has to do with the new dbfl_has_copy() logic.

I could fix my code by adding a dummy pfl->u.r.dtor in order to claim "ownership" of the data for the logic in dbAccess.c. The desctructor does nothing, as the data does not need to be destroyed, but otherwise dbAccess.c takes a few wrong branches, it seems.

I have not analyzed the exact path of execution before and after. I have checked which your commits (cherry-picked onto my branch) broke my code and then which modified line. I got the first problem with commit 85822f in dbAccess.c line 918. Reverting that line restored my functionality. But later commits broke it again. So I checked what changed there.

I found out that adding a dtor seems to fix everything. I will commit than now and rebase this branch.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

With the current 7.0 head, the function dbGet() in dbAccess.c branches 3 times on a test of dbfl_has_copy().

With a dummy dtor, the program flow it takes the else path all three times.
Without a dtor, it takes the first else path but the other two times it takes the if path.

In the old version of dbAccess.c, it took the else path all three times.

c5fe434... by Dirk Zimoch

fix for recent changes in dbGet()

Revision history for this message
Ben Franksen (bfrk) wrote :

I see the problem. What you are doing was not anticipated. You do not make a copy, the data is owned by the record all the time, so dbfl_has_copy correctly returns false. But then the code in dbAccess.c assumes that it has to ask the record for things like the actual number of elements etc, so it calls get_array_info for the dbAddr. For actual record fields this is precisely what we want. In your case I guess this is where things start to go wrong. Indeed you have set no_elements and field_type in the pfl. I am not sure how to fix this properly. Adding a dummy dtor means we lie to dbAccess, telling it we have made a copy in order to make it use the meta data in the pfl instead of trying to query the record about it. While this works I find it a bit obscure...

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Yes, it is unexpected as I completely throw away the original data and replace it with a constant string. I am not happy to use such a lie, but it probably does no harm. It basically says: "It is my data, I care about the memory." Once we have code that modifies info fields at run time, I probably need a copy and a real descructor. And locks.

Revision history for this message
Ben Franksen (bfrk) wrote :

Here is what I think we should do. We add another member (one bit, no extra storage needed) to allow the filter to override the ownership decision made by dbfl_has_copy. We should also rename dbfl_has_copy to dbfl_is_owner to make this clearer. Then, instead of defining a dummy dtor, you can set pfl->is_owner = 1 and all is well. Like this:

diff --git a/modules/database/src/ioc/db/db_field_log.h b/modules/database/src/ioc/db/db_field_log.h
index e517d529f..68e725ed6 100644
--- a/modules/database/src/ioc/db/db_field_log.h
+++ b/modules/database/src/ioc/db/db_field_log.h
@@ -107,6 +107,7 @@ struct dbfl_ref {
  */
 typedef struct db_field_log {
     unsigned int type:1; /* type (union) selector */
+ unsigned int is_owner:1; /* whether we own the data */
     /* ctx is used for all types */
     unsigned int ctx:1; /* context (operation type) */
     /* the following are used for value and reference types */
@@ -131,8 +132,9 @@ typedef struct db_field_log {
  * this case, making a copy is redundant, so we have no dtor. But conceptually
  * the db_field_log still owns the (empty) data.
  */
-#define dbfl_has_copy(p)\
- ((p) && ((p)->type==dbfl_type_val || (p)->u.r.dtor || (p)->no_elements==0))
+#define dbfl_is_owner(p)\
+ ((p) && ((p)->type==dbfl_type_val || (p)->u.r.dtor || (p)->no_elements==0\
+ || (p)->is_owner))

 #define dbfl_pfield(p)\
  ((p)->type==dbfl_type_val ? &p->u.v.field : p->u.r.field)

Revision history for this message
Ben Franksen (bfrk) wrote :

Sorry, I sent this before I saw your reply. If you anticipate that you may need an actual dtor at some later point this may not be needed. Still, I like the idea to allow a filter to explicitly claim ownership, independent of the presence of a dtor. And renaming dbfl_has_copy to dbfl_is_owner is also probably a good idea.

Revision history for this message
Ben Franksen (bfrk) wrote :
Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Adding another member to db_field_log seems like the right approach. I'm wondering whether 'is_owner' is the right distinction to draw. While reviewing, I gave some consideration to adding 'must_lock' to indicate when dbScanLock is needed to for access. This avoids overloading the meaning of '!dtor'.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Your change looks backward compatible. Anyway, I have modified my code and re-based it.
I will force-push now...

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

As I see it, there previously were three states for a db_field_log to be in:

1. Simple value type==dbfl_type_val
2. Owned value type==dbfl_type_ref
3. Borrowed value type==dbfl_type_rec

I think these would now need to be represented as:

1. type==dbfl_type_val && !dtor
2. type==dbfl_type_ref && dtor
3. type==dbfl_type_ref && dtor

Where 2 and 3 are ambiguous, and handling #2 would unnecessarily require dbScanLock()

Do I have this correct?

b7d2a43... by Simon

Refactor to reduce DRY

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

I had type==dbfl_type_ref && !dtor and own the value.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Ben wrote inline:
> + if (pfl->type == dbfl_type_ref && pfl->u.r.dtor)
> + pfl->u.r.dtor(pfl);
>
> These two lines seem to be unnecessary, since you always have type and dtor set to fixed values

What if two filters with dtors are chained?

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Maybe the assumption is that a value borrowed from a record will never need cleanup? Then:

1. type==dbfl_type_val && !dtor
2. type==dbfl_type_ref && dtor
3. type==dbfl_type_ref && !dtor

I'm not sure this is necessarily always true. I've been toying with the idea of involving db_field_log in reference counting where a record and and some db_field_log would share ownership. I haven't completely worked this through.

Revision history for this message
Ben Franksen (bfrk) wrote :

Am 10.03.21 um 17:49 schrieb Dirk Zimoch via Core-talk:
> Ben wrote inline:
>> + if (pfl->type == dbfl_type_ref && pfl->u.r.dtor)
>> + pfl->u.r.dtor(pfl);
>>
>> These two lines seem to be unnecessary, since you always have type and dtor set to fixed values
>
> What if two filters with dtors are chained?

Do they share the same pfl in this case? How would that work?

--
I would rather have questions that cannot be answered, than answers that
cannot be questioned. -- Richard Feynman

Revision history for this message
Ben Franksen (bfrk) wrote :

I am not sure I understand what you mean with "borrowed". IIUC there is nothing like that going on. You can have the value as a copy (dbfl_type_val) or as a reference (dbfl_type_ref). As I see it, the reference can be

1. owned by the record
2. owned the db_field_log

Originally I thought that 2 coincides with db_field_log having made a copy and thus having a dtor. For normal fields this is true. But info items are immutable, so there is no need to make a copy. There is also no need to to lock the record. And there is no need to query teh record for things like the current number of elements.

If there were guaranteed immutable record fields, the same would hold.

Revision history for this message
Ben Franksen (bfrk) wrote :

What happens when the record owns a reference and we lock it and then access the data can be called "borrowing". This is a temporary state and not a property of the db_field_log.

Revision history for this message
Ben Franksen (bfrk) wrote :

> I'm not sure this is necessarily always true. I've been toying with the idea of involving db_field_log in reference counting where a record and and some db_field_log would share ownership. I haven't completely worked this through.

I think I agree. This is why I proposed to allow the filter to override the conditional with an explicit "this is mine, keep off" bit.

Revision history for this message
Ben Franksen (bfrk) wrote :

Last remark for the moment. What we have with the info items here is that there is no actual owner. The decisions made in dbAccess when it calls dbfl_has_copy is about whether the meta data in the db_field_log is valid. Until now this happened to coincide with the data being owned by the db_field_log. It is clear that for a mutable array value the owner of the data is also the owner of the meta data (size, type, etc). But for immutable data that has no proper owner this is not obvious. So an alternative view would be to say that dbfl_has_copy == dbfl_is_owner stays as is and we add another property dbfl_has_meta_data that explicitly answers that particular question.

You alluded to possible shared ownership using reference counting. Let's see if I understand what you mean. So the actual data is immutable, but may be deallocated when the ref count goes to zero. The producer (normally the record) would create a fresh array but promise not to change the data after "publishing" it i.e. making it available as a field, nor the meta data. Then one could reference that data w/o locking. And since the db_field_log already has room for the meta data, the record (producer) could immediately create immutable reference counted db_field_logs. Was that the idea?

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> ... What if two filters with dtors are chained?

Since the value is being replaced, the original dtor needs to be called. So the basic process would be something like:

1. inspect previous value. eg. copy/convert etc.
2. dtor previous value
3. copy in new value/pointer and install new dtor

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> What happens when the record owns a reference and we lock it and then access the data can be called "borrowing". This is a temporary state and not a property of the db_field_log.

By "borrowed" I was meaning that a db_field_log which references a buffer which may only be accessed with a dbScanLock() of the associated record. Indeed struct db_field_log does not current encode this associate fully.

I think that you and I (Ben) probably have the same idea with only a sign flip in the name. Would you agree that "is_owner==!must_lock"? In your mind, does "is_owner" have a wider meaning than record locking? eg. does "is_owner" imply anything about the presence or absence of a dtor?

Revision history for this message
Ben Franksen (bfrk) wrote :

I don't think we disagree. I am fine with your definition "is_owner==!must_lock". But whether or not to lock (dbScanLock) is not the only question relevant here.

Immutable data does not have an owner: there is no need for any locking. Nevertheless (in the absence of garbage collection or ref counting) we must assign responsibility for deallocating the memory (unless the data remains life forever). This is signalled by a non-NULL dtor.

The other question is who has the relevant meta data? Again, for mutable data this is clearly the owner. For immutable data it could be the record or the field_log.

My refactor conflated all three notions: presence of a dtor == db_field_log is owner == db_field_log has the meta data. This is okay for mutable data but not for immutable.

The problem at hand, filters that allow to access info items, has these features: the data is immutable, so no owner; it has infinite life time, so there is no dtor; yet the meta data is held by the field_log, not the record.

This means we need another "degree of freedom": at least one extra bit, perhaps two. The design question is: precisely what meaning shall these bits have?

c99ede6... by Dirk Zimoch <email address hidden>

Merge pull request #1 from dirk-zimoch/FIFtest

Fi ftest

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

> > ... What if two filters with dtors are chained?
>
> Since the value is being replaced, the original dtor needs to be called. So
> the basic process would be something like:
>
> 1. inspect previous value. eg. copy/convert etc.
> 2. dtor previous value
> 3. copy in new value/pointer and install new dtor

Ok. That is what I'm doing:
1. Ignore the previous value
2. dtor it
3. copy new pointer and install new dtor.

That means those two line in question implement item 2 and need to stay.

6beea52... by Simon

'fixed' failing test

Revision history for this message
Ben Franksen (bfrk) wrote :

> 1. Ignore the previous value
> 2. dtor it
> 3. copy new pointer and install new dtor.
>
> That means those two line in question implement item 2 and need to stay.

I see. So this guards against a configuration error: someone applies the info item filter on top of some other filter. Such a configuration doesn't make sense, but we have to do something and I guess your solution is acceptable.

I would want to get a warning in this case, but certainly not every time the filter gets processed, rather only once when the filter is set up. Do you think that could be done?

b224fcf... by Simon

Using dbGet to fetch value

8a92bef... by Simon

Cleaned up source code and comments

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Even though it makes no sense, it is totally leagal to do so. The two lines normally cost nothing because in the typical case dtor is NULL. Actually type is typically not even ref. But they prevent a memory leak in case someone does stack info ontop of another filter.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Simon's tests added.

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

> ... I would want to get a warning in this case ...

I might agree if such a warning would be seen by a remote client. However, there isn't a mechanism for this. So rather, the IOC console would be cluttered up with messages saying the equivalent of "someone did this wrong". This doesn't seem so useful to me.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

You mean like those:
dbChannelCreate: parse error: premature EOF
                                       {info:{n:"longinfo",l:1}
                     (right here) ------^

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

Precisely. They are annoying, but in the case of errors acceptable as there is no alternative. I don't like the idea of extending this to warnings, where from the client prospective everything is working just fine.

Revision history for this message
Andrew Johnson (anj) wrote :

Some comments and questions inline; this also needs an entry in the documentation/ReleaseNotes.md file so people know the new filter exists.

review: Needs Fixing
Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Simon, please comment on the compiler flags.

5ef70f7... by Dirk Zimoch

revert link fixes

0d4063f... by Dirk Zimoch

info filter described in RELEASE_NOTES

f4b4a36... by Dirk Zimoch

debugging flags removed

50c9d70... by Dirk Zimoch

cleanup unneeded includes

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

I would like to have asyntax like TEST.{info:"infotag"} instead of TEST.{info:{name:"infotag"}} but unfortunately the filter plugin does not accept it.

Revision history for this message
Ben Franksen (bfrk) wrote :

> Precisely. They are annoying, but in the case of errors acceptable as there
> is no alternative. I don't like the idea of extending this to warnings, where
> from the client prospective everything is working just fine.

Hm, yes. A warning that's not seen by the one who causes it is perhaps not worth the hassle. So we have to be defensive here even though I don't like it.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Best I can achieve to have a long string "shortcut" is at the moment:
a) {info$:{n:"infotag"}}
b) {info:{n$:"infotag"}}
c) {info:{$:"infotag"}}

Revision history for this message
mdavidsaver (mdavidsaver) wrote :

The chfPlugin code would need to be taught about shortcuts. Maybe indicated for one parameter with a new bit flag in chfPluginArgDef?

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

First thing I changed in chfPlugin is to support arbitratily long string arguments, so that I do not need to limit the length of the tag name.
Should that change go to a different merge request?
Should it completely replace the fixed length "chfString" implementation? (currently only used in the "sync" filter)

Revision history for this message
Andrew Johnson (anj) wrote :

Can we keep any chfPlugin enhancements on a separate branch and as a separate PR please. If you mix them up you would be delaying the merge of the new filter and also complicating the review process, so separate is better. It would be okay to include modifications to streamline the existing filters in the PR that enhances the chfPlugin code.

You could include that suggested shortcut to the filter params here though, it looks much better than having the separate longstr parameter. I would also maybe use the shortcut in the Release Notes example, and even drop the quotes there so users who see it there start to recognize that the quotes aren't needed.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

OK, I will create a separate branch for chfPlugin improvements.

Adding the shortcuts now and removeing the quotes from all examples.

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

Which shortcurt would you prefer for long string? {info:{name$:'name'}} or {info$:{name:'name'}} ?
As I am working on a change in chfPlugin to allow the even shorter {info:'name'}, info$ would probably be the better way to signal long string.

Should I drop the 'auto' mode?

Revision history for this message
Ralph Lange (ralph-lange) wrote :

+1 for info$

Which closely follows the initial idea behind the $ shortcut (cf. programming language Basic):
"Adding a $ makes it a string."

31b88eb... by Dirk Zimoch

cleanup quotes according to new JSON5 parser

ece87ca... by Dirk Zimoch

implement several shortcut alternatives

Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

chfPlugin changes taken out and moved then to merge request 399636.

I fixed the usage of quotes in the documentation. I now suggest to use single quotes for string arguments and double quotes around the whole expression. (Basically because that allows to use shell features like $macros to be used within the double quotes).

I have added implementations for info$:{name:...} as well as for info:{name$:...}.
For me info$ looks nicer than name$ but name$ is simpler to implement.
But if I can implement the chfPlugin shortcut for 1 argument, then using info$ makes more sense.

Revision history for this message
Dirk Zimoch (dirk.zimoch) :
review: Needs Information
Revision history for this message
Dirk Zimoch (dirk.zimoch) wrote :

If https://code.launchpad.net/~dirk.zimoch/epics-base/+git/epics-base/+merge/399636 gets accepted, I would like to rebase this on top of that.
In that case, {info:'name'} for STRING and {info$:'name'} for CHAR array makes sense (no auto switching, no switching options).
Opinions?

Revision history for this message
Andrew Johnson (anj) wrote :

Core Group 8/11: Approve to be merged (without the chfPlugin changes which need more review).

review: Approve

Unmerged commits

ece87ca... by Dirk Zimoch

implement several shortcut alternatives

31b88eb... by Dirk Zimoch

cleanup quotes according to new JSON5 parser

50c9d70... by Dirk Zimoch

cleanup unneeded includes

f4b4a36... by Dirk Zimoch

debugging flags removed

0d4063f... by Dirk Zimoch

info filter described in RELEASE_NOTES

5ef70f7... by Dirk Zimoch

revert link fixes

8a92bef... by Simon

Cleaned up source code and comments

b224fcf... by Simon

Using dbGet to fetch value

6beea52... by Simon

'fixed' failing test

c99ede6... by Dirk Zimoch <email address hidden>

Merge pull request #1 from dirk-zimoch/FIFtest

Fi ftest

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/documentation/RELEASE_NOTES.md b/documentation/RELEASE_NOTES.md
2index 49cdd74..789a2ea 100644
3--- a/documentation/RELEASE_NOTES.md
4+++ b/documentation/RELEASE_NOTES.md
5@@ -17,6 +17,15 @@ should also be read to understand what has changed since earlier releases.
6
7 <!-- Insert new items immediately below here ... -->
8
9+### New channel filter for info fields
10+
11+A new filter `info` is available to access info fields of a record
12+using Channel Access or links.
13+The text value can be read as a `DBF_STRING` or an array of `DBF_CHAR`.
14+
15+```
16+caget 'recordname.{info:{name="infotag"}}'
17+```
18
19 -----
20
21diff --git a/modules/database/src/std/filters/Makefile b/modules/database/src/std/filters/Makefile
22index ee6a0ae..16e8171 100644
23--- a/modules/database/src/std/filters/Makefile
24+++ b/modules/database/src/std/filters/Makefile
25@@ -16,6 +16,7 @@ dbRecStd_SRCS += dbnd.c
26 dbRecStd_SRCS += arr.c
27 dbRecStd_SRCS += sync.c
28 dbRecStd_SRCS += decimate.c
29+dbRecStd_SRCS += info.c
30
31 HTMLS += filters.html
32
33diff --git a/modules/database/src/std/filters/filters.dbd.pod b/modules/database/src/std/filters/filters.dbd.pod
34index 27e356f..d171cda 100644
35--- a/modules/database/src/std/filters/filters.dbd.pod
36+++ b/modules/database/src/std/filters/filters.dbd.pod
37@@ -16,6 +16,8 @@ The following filters are available in this release:
38
39 =item * L<Decimation|/"Decimation Filter dec">
40
41+=item * L<Info Field|/"Info Field Filter info">
42+
43 =back
44
45 =head2 Using Filters
46@@ -27,13 +29,11 @@ to the resulting data-stream.
47 The filter specification must appear after the field name, or if the default
48 (VAL) field is used after a dot C<.> appended to the record name.
49 With the exception of the array short-hand which is described below, all filters
50-must appear inside a pair of braces C< {} > after the dot expressed as a JSON
51-(L<JavaScript Object Notation|http://www.json.org/>) object, which allows filter
52-parameters to be included as needed.
53+must appear inside a pair of braces C< {} > after the dot expressed as a JSON5
54+(L<JavaScript Object Notation|https://spec.json5.org/>) object, which allows
55+filter parameters to be included as needed.
56
57-Each filter is given as a name/value pair. The filter name (given in parentheses
58-in the titles below) is a string, and must be enclosed inside double-quotes C<">
59-characters as per the JSON specification.
60+Each filter is given as a name/value pair.
61 Parameters to that filter are provided as the value part of the name/value pair,
62 and will normally appear as a child JSON object consisting of name/value pairs
63 inside a nested pair of braces C< {} >.
64@@ -42,14 +42,18 @@ inside a nested pair of braces C< {} >.
65
66 Given a record called C<test:channel> the following would apply a filter C<f> to
67 the VAL field of that record, giving the filter two numeric parameters named
68-C<lo> and C<hi>:
69+C<lo> and C<hi> and a string parameter C<s>:
70+
71+ "test:channel.{f:{lo:0,hi:10,s:'X'}}"
72
73- test:channel.{"f":{"lo":0,"hi":10}}
74+Note that due to the required presence of quotes around string values, and
75+the way Unix shells expand braces, it is recommended to enclose a filtered name
76+within either double quotes C<< "..." >> or single quotes C<< '...' >>.
77+When using double quotes around the filter, use single quotes around string
78+parameters and vice versa.
79
80-Note that due to the required presence of the double-quote characters in the
81-JSON strings in the name string, it will usually be necessary to enclose a
82-filtered name within single-quotes C<< ' ... ' >> when typing it as an
83-argument to a Unix shell command.
84+Thanks to the new JSON5 parser, member names like the filter names and parameter
85+names do not require quotes any longer.
86
87 =head2 Filter Reference
88
89@@ -57,7 +61,7 @@ argument to a Unix shell command.
90
91 registrar(tsInitialize)
92
93-=head3 TimeStamp Filter C<"ts">
94+=head3 TimeStamp Filter C<ts>
95
96 This filter is used to set the timestamp of the value fetched through
97 the channel to the time the value was fetched (or an update was sent),
98@@ -71,16 +75,16 @@ None, use an empty pair of braces.
99
100 =head4 Example
101
102- Hal$ caget -a 'test:channel.{"ts":{}}'
103- test:channel.{"ts":{}} 2012-08-28 22:10:31.192547 0 UDF INVALID
104- Hal$ caget -a 'test:channel'
105+ Hal$ caget -a "test:channel.{ts:{}}"
106+ test:channel.{ts:{}} 2012-08-28 22:10:31.192547 0 UDF INVALID
107+ Hal$ caget -a "test:channel"
108 test:channel <undefined> 0 UDF INVALID
109
110 =cut
111
112 registrar(dbndInitialize)
113
114-=head3 Deadband Filter C<"dbnd">
115+=head3 Deadband Filter C<dbnd>
116
117 This filter implements a channel-specific monitor deadband, which is applied
118 after any deadbands implemented by the record itself (it can only drop updates
119@@ -93,21 +97,21 @@ percentage.
120
121 =over
122
123-=item Mode+Deadband C<"abs">/C<"rel"> (shorthand)
124+=item Mode+Deadband C<abs>/C<rel> (shorthand)
125
126 Mode and deadband can be specified in one definition (shorthand).
127-The desired mode is given as parameter name (C<"abs"> or C<"rel">), with the
128+The desired mode is given as parameter name (C<abs> or C<rel>), with the
129 numeric size of the deadband (absolute value or numeric percentage) as value.
130
131-=item Deadband C<"d">
132+=item Deadband C<d>
133
134 The size of the deadband to use.
135 Relative deadband values are given as a numeric percentage, but without any
136 trailing percent character.
137
138-=item Mode C<"m"> (optional)
139+=item Mode C<m> (optional)
140
141-A string (enclosed in double-quotes C<">), which should contain either
142+A string (enclosed in quotes), which should contain either
143 C<abs> or C<rel>.
144 The default mode is C<abs> if no mode parameter is included.
145
146@@ -115,7 +119,7 @@ The default mode is C<abs> if no mode parameter is included.
147
148 =head4 Example
149
150- Hal$ camonitor 'test:channel'
151+ Hal$ camonitor test:channel
152 test:channel 2012-09-01 22:10:19.600595 1 LOLO MAJOR
153 test:channel 2012-09-01 22:10:20.600661 2 LOLO MAJOR
154 test:channel 2012-09-01 22:10:21.600819 3 LOW MINOR
155@@ -123,19 +127,19 @@ The default mode is C<abs> if no mode parameter is included.
156 test:channel 2012-09-01 22:10:23.601023 5
157 test:channel 2012-09-01 22:10:24.601136 6 HIGH MINOR
158 ^C
159- Hal$ camonitor 'test:channel.{"dbnd":{"abs":1.5}}'
160- test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:49.613341 1 LOLO MAJOR
161- test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:51.613615 3 LOW MINOR
162- test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:53.613804 5
163- test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:55.614074 7 HIGH MINOR
164- test:channel.{"dbnd":{"d":1.5}} 2012-09-01 22:11:57.614305 9 HIHI MAJOR
165+ Hal$ camonitor "test:channel.{dbnd:{abs:1.5}}"
166+ test:channel.{dbnd:{d:1.5}} 2012-09-01 22:11:49.613341 1 LOLO MAJOR
167+ test:channel.{dbnd:{d:1.5}} 2012-09-01 22:11:51.613615 3 LOW MINOR
168+ test:channel.{dbnd:{d:1.5}} 2012-09-01 22:11:53.613804 5
169+ test:channel.{dbnd:{d:1.5}} 2012-09-01 22:11:55.614074 7 HIGH MINOR
170+ test:channel.{dbnd:{d:1.5}} 2012-09-01 22:11:57.614305 9 HIHI MAJOR
171 ^C
172
173 =cut
174
175 registrar(arrInitialize)
176
177-=head3 Array Filter C<"arr">
178+=head3 Array Filter C<arr>
179
180 This filter is used to retrieve parts of an array (subarrays and strided
181 subarrays).
182@@ -153,16 +157,16 @@ Any parameter may be omitted (keeping the colons) to use the default value.
183 If only one colon is included, this means C<[start:end]> with an increment of 1.
184 If only a single parameter is used C<[index]> the filter returns one element.
185
186-=item Start index C<"s">
187+=item Start index C<s>
188
189 Index of the first original array element to retrieve.
190
191-=item Increment C<"i">
192+=item Increment C<i>
193
194 Index increment between retrieved elements of the original array; must be
195 a positive number.
196
197-=item End index C<"e">
198+=item End index C<e>
199
200 Index of the last original array element to retrieve.
201
202@@ -174,9 +178,9 @@ C<s=0> (first element), C<i=1> (fetch all elements), C<e=-1>
203
204 =head4 Example
205
206- Hal$ caget test:channel 'test:channel.{"arr":{"s":2,"i":2,"e":8}}' test:channel.[3:5] test:channel.[3:2:-3]
207+ Hal$ caget test:channel "test:channel.{arr:{s:2,i:2,e:8}}" test:channel.[3:5] test:channel.[3:2:-3]
208 test:channel 10 0 1 2 3 4 5 6 7 8 9
209- test:channel.{"arr":{"s":2,"i":2,"e":8}} 4 2 4 6 8
210+ test:channel.{arr:{s:2,i:2,e:8}} 4 2 4 6 8
211 test:channel.[3:5] 3 3 4 5
212 test:channel.[3:2:-3] 3 3 5 7
213
214@@ -184,7 +188,7 @@ C<s=0> (first element), C<i=1> (fetch all elements), C<e=-1>
215
216 registrar(syncInitialize)
217
218-=head3 Synchronize Filter C<"sync">
219+=head3 Synchronize Filter C<sync>
220
221 This filter is used to dynamically enable or disable monitors according
222 to a condition and a state variable declared by the IOC.
223@@ -200,57 +204,57 @@ C<dbStateSet()>.
224 =item Mode+State
225
226 Mode and state can be specified in one definition (shorthand).
227-The desired mode is given as parameter name (C<"before"> / C<"first"> /
228-C<"while"> / C<"last"> / C<"after"> / C<"unless">), with the state name
229-(enclosed in double quotes C<">) as value.
230+The desired mode is given as parameter name (C<before> / C<first> /
231+C<while> / C<last> / C<after> / C<unless>), with the state name
232+(enclosed in quotes) as value.
233
234-=item Mode C<"m">
235+=item Mode C<m>
236
237-A single word from the list below, enclosed in double quotes C<">.
238+A single word from the list below, enclosed in quotes.
239 This controls how the state value should affect the monitor stream.
240
241 =over
242
243-=item C<"before"> E<mdash> only the last value received before the state
244+=item C<before> E<mdash> only the last value received before the state
245 changes from false to true is forwarded to the client.
246
247-=item C<"first"> E<mdash> only the first value received after the state
248+=item C<first> E<mdash> only the first value received after the state
249 changes from true to false is forwarded to the client.
250
251-=item C<"while"> E<mdash> values are forwarded to the client as long as
252+=item C<while> E<mdash> values are forwarded to the client as long as
253 the state is true.
254
255-=item C<"last"> E<mdash> only the last value received before the state
256+=item C<last> E<mdash> only the last value received before the state
257 changes from true to false is forwarded to the client.
258
259-=item C<"after"> E<mdash> only the first value received after the state
260+=item C<after> E<mdash> only the first value received after the state
261 changes from true to false is forwarded to the client.
262
263-=item C<"unless"> E<mdash> values are forwarded to the client as long
264+=item C<unless> E<mdash> values are forwarded to the client as long
265 as the state is false.
266
267 =back
268
269-=item State C<"s">
270+=item State C<s>
271
272-The name of a state variable, enclosed in double quotes C<">.
273+The name of a state variable, enclosed in quotes.
274
275 =back
276
277 =head4 Example
278
279-Assuming there is a system state called "blue", that is being controlled by
280+Assuming there is a system state called C<blue>, that is being controlled by
281 some other facility such as a timing system, updates could be restricted to
282-periods only when "blue" is true by using
283+periods only when C<blue> is true by using
284
285- Hal$ camonitor 'test:channel' 'test:channel.{"sync":{"while":"blue"}}'
286+ Hal$ camonitor test:channel "test:channel.{sync:{while:'blue'}}"
287 ...
288
289 =cut
290
291 registrar(decInitialize)
292
293-=head3 Decimation Filter C<"dec">
294+=head3 Decimation Filter C<dec>
295
296 This filter is used to reduce the number or rate of monitor updates from a
297 channel by an integer factor C<n> that is provided as a filter argument,
298@@ -262,7 +266,7 @@ update through.
299
300 =over
301
302-=item Number C<"n">
303+=item Number C<n>
304
305 The decimation factor, a positive integer. Giving n=1 is equivalent to a no-op
306 that allows all updates to be passed to the client.
307@@ -281,7 +285,56 @@ client connects.
308 To sample a 60Hz channel at 1Hz, a 10Hz channel every 6 seconds or a 1Hz channel
309 once every minute:
310
311- Hal$ camonitor 'test:channel' 'test:channel.{"dec":{"n":60}}'
312+ Hal$ camonitor test:channel "test:channel.{dec:{n:60}}"
313 ...
314
315 =cut
316+
317+registrar(infoInitialize)
318+
319+=head3 Info Field Filter C<info>
320+
321+This filter gives access to the info fields of a record.
322+
323+=head4 Parameters
324+
325+=over
326+
327+=item Name C<name> or C<n>
328+
329+The name of the info field.
330+Currently, there is an implementation limit of 50 chars
331+for the name length.
332+
333+=item Long String Flag C<longstr> or C<l>
334+
335+If this optional flag is C<'yes'>, C<'on'> or C<1>, the info field is
336+returned as an array of C<CHAR>.
337+If it is C<'no'>, C<'off'> or C<0>, the info field is returned as a
338+C<STRING>, truncated to 40 characters if necessary.
339+The default is C<'auto'>, which will use C<STRING> for sufficiently short values
340+and array of C<CHAR> for long values.
341+
342+Alternatively, long strings can be requested by adding a C<$> to the filter
343+name or the name parameter.
344+
345+=back
346+
347+=head4 Example
348+
349+Assuming the record C<test:channel> has:
350+
351+ info(infofieldname, "value of the info field")
352+
353+To read the value of the info field:
354+
355+ $ caget "test:channel.{info:{name:'infofieldname'}}"
356+ test:channel.{info:{name:'infofieldname'}} value of the info field
357+ $ caget -S "test:channel.{info:{name:'infofieldname',l:'yes'}}"
358+ test:channel.{info:{name:'infofieldname'}} value of the info field
359+ $ caget -S "test:channel.{info$:{name:'infofieldname'}}"
360+ test:channel.{info$:{name:'infofieldname'}} value of the info field
361+ $ caget -S "test:channel.{info:{n$:'infofieldname'}}"
362+ test:channel.{info:{n$:'infofieldname'}} value of the info field
363+
364+=cut
365diff --git a/modules/database/src/std/filters/info.c b/modules/database/src/std/filters/info.c
366new file mode 100644
367index 0000000..b14d7ed
368--- /dev/null
369+++ b/modules/database/src/std/filters/info.c
370@@ -0,0 +1,189 @@
371+/*************************************************************************\
372+* Copyright (c) 2021 Paul Scherrer Institute
373+* SPDX-License-Identifier: EPICS
374+* EPICS BASE is distributed subject to a Software License Agreement found
375+* in file LICENSE that is included with this distribution.
376+\*************************************************************************/
377+
378+/*
379+ * Author: Dirk Zimoch <dirk.zimoch@psi.ch>
380+ */
381+
382+#include <stdio.h>
383+#include <string.h>
384+
385+#include "chfPlugin.h"
386+#include "dbStaticLib.h"
387+#include "dbAccessDefs.h"
388+#include "epicsExit.h"
389+#include "freeList.h"
390+#include "epicsExport.h"
391+
392+typedef struct myStruct {
393+ char name[52]; /* arbitrary size, we better had dynamic strings */
394+ DBENTRY dbentry;
395+ int longstr;
396+} myStruct;
397+
398+static void *myStructFreeList;
399+
400+static const
401+chfPluginEnumType longstrEnum[] = {{"no",0}, {"off",0}, {"yes",1}, {"on",1}, {"auto",2}, {NULL, 0}};
402+
403+static const chfPluginArgDef opts[] = {
404+/* member, name, [tagmember, tagval,] required, convert, [enums] */
405+ chfString (myStruct, name, "name", 1, 0),
406+ chfString (myStruct, name, "n", 1, 0),
407+ chfTagString (myStruct, name, "name$", longstr, 1, 1, 0),
408+ chfTagString (myStruct, name, "n$", longstr, 1, 1, 0),
409+ chfTagString (myStruct, name, "$", longstr, 1, 1, 0),
410+ chfEnum (myStruct, longstr, "longstr", 0, 1, longstrEnum),
411+ chfEnum (myStruct, longstr, "l", 0, 1, longstrEnum),
412+ chfPluginArgEnd
413+};
414+
415+static void * allocPvt(void)
416+{
417+ myStruct *my = (myStruct*) freeListCalloc(myStructFreeList);
418+ if (!my) return NULL;
419+ my->longstr = 2;
420+ return (void *) my;
421+}
422+
423+static void freePvt(void *pvt)
424+{
425+ freeListFree(myStructFreeList, pvt);
426+}
427+
428+static int parse_ok(void *pvt)
429+{
430+ myStruct *my = (myStruct*) pvt;
431+ if (my->name[0] == 0) /* empty name */
432+ return -1;
433+ if (my->name[sizeof(my->name)-2] != 0) /* name buffer overrun */
434+ return -1;
435+ return 0;
436+}
437+
438+static int parse_ok_long(void *pvt)
439+{
440+ myStruct *my = (myStruct*) pvt;
441+ my->longstr = 1;
442+ return parse_ok(pvt);
443+}
444+
445+static void dbfl_free(struct db_field_log *pfl)
446+{
447+ /* dummy needed for dbGet() to work correctly */
448+}
449+
450+static db_field_log* filter(void* pvt, dbChannel *chan, db_field_log *pfl)
451+{
452+ myStruct *my = (myStruct*) pvt;
453+
454+ if (pfl->type == dbfl_type_ref && pfl->u.r.dtor)
455+ pfl->u.r.dtor(pfl);
456+ pfl->type = dbfl_type_ref;
457+ pfl->u.r.dtor = dbfl_free;
458+ pfl->u.r.field = (void*)dbGetInfoString(&my->dbentry);
459+
460+ if (my->longstr) {
461+ pfl->field_size = 1;
462+ pfl->field_type = DBF_CHAR;
463+ pfl->no_elements = strlen((char*)pfl->u.r.field)+1;
464+ } else {
465+ pfl->field_size = MAX_STRING_SIZE;
466+ pfl->field_type = DBF_STRING;
467+ pfl->no_elements = 1;
468+ }
469+ return pfl;
470+}
471+
472+static long channel_open(dbChannel *chan, void *pvt)
473+{
474+ myStruct *my = (myStruct*) pvt;
475+ DBENTRY* pdbe = &my->dbentry;
476+ int status;
477+
478+ dbInitEntryFromAddr(&chan->addr, pdbe);
479+ for (status = dbFirstInfo(pdbe); !status; status = dbNextInfo(pdbe))
480+ if (strcmp(dbGetInfoName(pdbe), my->name) == 0)
481+ return 0;
482+ return -1;
483+}
484+
485+static void channelRegisterPre(dbChannel *chan, void *pvt,
486+ chPostEventFunc **cb_out, void **arg_out, db_field_log *pfl)
487+{
488+ myStruct *my = (myStruct*) pvt;
489+ size_t len = strlen(dbGetInfoString(&my->dbentry)) + 1;
490+ if (my->longstr == 2) {
491+ my->longstr = len > MAX_STRING_SIZE;
492+ }
493+ if (my->longstr) {
494+ pfl->field_size = 1;
495+ pfl->field_type = DBF_CHAR;
496+ pfl->no_elements = len;
497+ } else {
498+ pfl->field_size = MAX_STRING_SIZE;
499+ pfl->field_type = DBF_STRING;
500+ pfl->no_elements = 1;
501+ }
502+ *cb_out = filter;
503+ *arg_out = pvt;
504+}
505+
506+static void channel_report(dbChannel *chan, void *pvt, int level,
507+ const unsigned short indent)
508+{
509+ myStruct *my = (myStruct*) pvt;
510+ printf("%*sInfo: name=%s\n", indent, "",
511+ my->name);
512+}
513+
514+static chfPluginIf pif = {
515+ allocPvt,
516+ freePvt,
517+
518+ NULL, /* parse_error, */
519+ parse_ok,
520+
521+ channel_open,
522+ channelRegisterPre,
523+ NULL, /* channelRegisterPost, */
524+ channel_report,
525+ NULL /* channel_close */
526+};
527+
528+static chfPluginIf piflong = {
529+ allocPvt,
530+ freePvt,
531+
532+ NULL, /* parse_error */
533+ parse_ok_long,
534+
535+ channel_open,
536+ channelRegisterPre,
537+ NULL, /* channelRegisterPost, */
538+ channel_report,
539+ NULL /* channel_close */
540+};
541+
542+static void infoShutdown(void* ignore)
543+{
544+ if(myStructFreeList)
545+ freeListCleanup(myStructFreeList);
546+ myStructFreeList = NULL;
547+}
548+
549+static void infoInitialize(void)
550+{
551+ if (!myStructFreeList)
552+ freeListInitPvt(&myStructFreeList, sizeof(myStruct), 64);
553+
554+ chfPluginRegister("info", &pif, opts);
555+ chfPluginRegister("info$", &piflong, opts);
556+ epicsAtExit(infoShutdown, NULL);
557+}
558+
559+epicsExportRegistrar(infoInitialize);
560diff --git a/modules/database/test/std/filters/Makefile b/modules/database/test/std/filters/Makefile
561index 2576ce4..190f406 100644
562--- a/modules/database/test/std/filters/Makefile
563+++ b/modules/database/test/std/filters/Makefile
564@@ -65,6 +65,13 @@ decTest_SRCS += filterTest_registerRecordDeviceDriver.cpp
565 testHarness_SRCS += decTest.c
566 TESTS += decTest
567
568+TESTPROD_HOST += infoTest
569+infoTest_SRCS += infoTest.cpp
570+infoTest_SRCS += filterTest_registerRecordDeviceDriver.cpp
571+testHarness_SRCS += infoTest.cpp
572+TESTFILES += ../infoTest.db
573+TESTS += infoTest
574+
575 # epicsRunFilterTests runs all the test programs in a known working order.
576 testHarness_SRCS += epicsRunFilterTests.c
577
578diff --git a/modules/database/test/std/filters/epicsRunFilterTests.c b/modules/database/test/std/filters/epicsRunFilterTests.c
579index 2b92b53..19952c1 100644
580--- a/modules/database/test/std/filters/epicsRunFilterTests.c
581+++ b/modules/database/test/std/filters/epicsRunFilterTests.c
582@@ -19,6 +19,7 @@ int dbndTest(void);
583 int syncTest(void);
584 int arrTest(void);
585 int decTest(void);
586+int infoTest(void);
587
588 void epicsRunFilterTests(void)
589 {
590@@ -29,6 +30,7 @@ void epicsRunFilterTests(void)
591 runTest(syncTest);
592 runTest(arrTest);
593 runTest(decTest);
594+ runTest(infoTest);
595
596 dbmfFreeChunks();
597
598diff --git a/modules/database/test/std/filters/infoTest.cpp b/modules/database/test/std/filters/infoTest.cpp
599new file mode 100644
600index 0000000..070d23d
601--- /dev/null
602+++ b/modules/database/test/std/filters/infoTest.cpp
603@@ -0,0 +1,132 @@
604+/*************************************************************************\
605+* Copyright (c) 2021 European Spallation Source (ERIC)
606+* SPDX-License-Identifier: EPICS
607+* EPICS BASE is distributed subject to the Software License Agreement
608+* found in the file LICENSE that is included with this distribution.
609+\*************************************************************************/
610+
611+/*
612+ * Author: Simon Rose <simon.rose@ess.eu>
613+ */
614+
615+#include <string.h>
616+
617+#include "epicsStdio.h"
618+#include "dbStaticLib.h"
619+#include "errlog.h"
620+#include "dbAccess.h"
621+#include "dbChannel.h"
622+#include "dbUnitTest.h"
623+#include "testMain.h"
624+
625+extern "C" {
626+ void filterTest_registerRecordDeviceDriver(struct dbBase *);
627+}
628+
629+#define DEBUG(fmt, x) printf("DEBUG (%d): %s: " fmt "\n", __LINE__, #x, x)
630+
631+static void runSingleTest(const char *json, dbfType dbf_type, const char *type, const char *expected) {
632+ dbChannel *pch;
633+ db_field_log fl;
634+ db_field_log *pfl;
635+ DBADDR addr;
636+ char data[80];
637+ long nreq;
638+ char name[80] = "x.";
639+
640+ strncat(name, json, sizeof(name)-strlen(name)-1);
641+
642+ testOk(!!(pch = dbChannelCreate(name)), "dbChannel with plugin %s successful", name);
643+ testOk((ellCount(&pch->filters) == 1), "Channel has one plugin");
644+
645+ testOk(!(dbChannelOpen(pch)), "dbChannel with plugin info opened");
646+ testOk(pch->final_type == dbf_type, "Channel type should be %s", type);
647+
648+ memset(&fl, 0, sizeof(fl));
649+ pfl = dbChannelRunPreChain(pch, &fl);
650+ testOk(pfl->field_type == dbf_type, "Field type should be %s", type);
651+
652+ dbNameToAddr(name, &addr);
653+ dbScanLock(addr.precord);
654+ nreq = strlen(expected) + 1;
655+ dbGet(&addr, dbf_type, &data, NULL, &nreq, pfl);
656+ dbScanUnlock(addr.precord);
657+
658+ testOk(strcmp(expected, data) == 0, "Info string matches expected '%s'", expected);
659+
660+ dbChannelDelete(pch);
661+}
662+
663+MAIN(arrTest)
664+{
665+ dbChannel *pch;
666+ const chFilterPlugin *plug;
667+ char info[] = "info";
668+ dbEventCtx evtctx;
669+ DBENTRY dbentry;
670+ const char test_info_str[] = "xxxxxxxxxxxx";
671+ const char test_info_str_long[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
672+ const char test_info_str_truncated[] = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
673+
674+ testPlan(30);
675+
676+ /* Prepare the IOC */
677+
678+ testdbPrepare();
679+
680+ testdbReadDatabase("filterTest.dbd", NULL, NULL);
681+
682+ filterTest_registerRecordDeviceDriver(pdbbase);
683+
684+ testdbReadDatabase("xRecord.db", NULL, NULL);
685+
686+ eltc(0);
687+ testIocInitOk();
688+ eltc(1);
689+
690+ /* Start the IOC */
691+
692+ evtctx = db_init_events();
693+
694+ testOk(!!(plug = dbFindFilter(info, strlen(info))), "plugin 'info' registered correctly");
695+
696+
697+ testDiag("Testing failing info tag reads");
698+ testOk(!dbChannelCreate("x.{info:{}}"), "dbChannel with plugin {info:{}} failed");
699+ testOk(!dbChannelCreate("x.{info:{name:\"\"}}"), "dbChannel with plugin {info:{name:\"\"}} failed");
700+ testOk(!dbChannelCreate("x.{info:{name:\"0123456789a123456789b123456789c123456789d123456789e\"}}"), "dbChannel with plugin {info:{name:\"<too long name>\"}} failed");
701+
702+
703+ testDiag("Testing non-existent tags");
704+ testOk(!!(pch = dbChannelCreate("x.{info:{name:\"non-existent\"}}")), "dbChannel with plugin {info:{name:\"non-existent\"}} created");
705+ testOk(!!(dbChannelOpen(pch)), "dbChannel with non-existent info tag did not open");
706+
707+ /*
708+ * Update the record to add the correct info record
709+ */
710+ dbInitEntry(pdbbase, &dbentry);
711+ dbFindRecord(&dbentry,"x");
712+ dbPutInfo(&dbentry, "a", test_info_str);
713+ dbPutInfo(&dbentry, "b", test_info_str_long);
714+
715+
716+ testDiag("Testing valid info tag");
717+ runSingleTest("{info:{name:\"a\"}}", DBF_STRING, "DBF_STRING", test_info_str);
718+
719+ testDiag("Testing long string");
720+ runSingleTest("{info:{name:\"a\", l:\"on\"}}", DBF_CHAR, "DBF_CHAR", test_info_str);
721+
722+ testDiag("Testing long string, full");
723+ runSingleTest("{info:{name:\"b\", l:\"auto\"}}", DBF_CHAR, "DBF_CHAR", test_info_str_long);
724+
725+ testDiag("Testing long string, truncated");
726+ runSingleTest("{info:{name:\"b\", l:\"off\"}}", DBF_STRING, "DBF_STRING", test_info_str_truncated);
727+
728+ db_close_events(evtctx);
729+
730+ testIocShutdownOk();
731+
732+ testdbCleanup();
733+
734+ return testDone();
735+}

Subscribers

People subscribed via source and target branches