-------------------------- With the Properties in the %MV.Adaptor class
Property TimeSpanCode As %String(MVITYPERTN = "I26^|""ADEV""|MVI.1",
MVPROJECTED = 0, MVTYPE = "I") [ Calculated, SqlComputeCode = {
Set {TimeSpanCode}=##class(O.TimeSpan).calcTimeSpanCode({%%ID},{%RECORD})},
SqlComputed ];
-------------------------- In XData MVAdditionalDictItems
<DictItem Name="TimeSpanCode">
<Attr>I</Attr>
<Attr>FIELD(@ID,'*',2)
</Attr>
<Attr>.</Attr>
<Attr>TimeSpanCode</Attr>
<Attr>40L</Attr>
<Attr>S</Attr>
</DictItem>
-------------------------- With the class methods
ClassMethod calcTimeSpanCode(
ItemID As %String,
Item As %String) As %Integer [ Language = cache ]
{
Set $mvv(16)=ItemID,$mvv(17)=Item Quit $$I26^|"ADEV"|MVI.1
}
Previously we had it coded as below, but then it did not work in SQL
or Cache' classes. Hopefully we can code them more like below in the
future (crossing fingers).
//Property TimeSpanCode As %String(MVITYPE = "FIELD(@ID,'*',2)",
MVITYPERTN = "", MVTYPE = "I", MVWIDTH = 20) [ Calculated ];
Obviously I'm hoping this approach to sourcing I-descs will be fixed
(I had indicated to folks that I was pretty sure this would be fixed
in 2008.2, but, alas...) but short of any future enhancement in that
regard at this point I at least need to figure out how to get the
hard-coded account name ("ADEV" in the above example) out of the
class.
If the namespace name is required (and I couldn't get it to work
without it, but only gave it one shot a few months back, so maybe this
is easy enough), is there a way it could be read from a file or
handled across all classes as a constant, even if that constant needs
to be changed for each account/namespace?
Thanks for any suggestions. --dawn
--
Dawn M. Wolthuis
Take and give some delight today
I still need a means of not automating the configuration management,
preferable eliminating the hard-coded namespace from source code,
otherwise isolating it to a single class somehow. The same code
should run in any namespace, right? Are there other ares where this
is required? (I have one other instance where we have it hard-coded,
but I'm not certain it is required, need to test that yet).
I'm hoping that you can generate at least that part of the code in the
future based on our "spec" in a persistent class (hopefully all of the
"I-desc hand-coded code that looks more like generated code that is
junking up our source metadata" can be generated in the future, but
that isn't the problem I'm addressing here). Then you would have what
you need somewhere in the code, but not in any code we are
maintaining, so this issue of hard-coded accounts in the code would go
away.
Is it possible to put a variable in place of the "ADEV" or do
something to make this transition from one namespace to another not
require changing source code or isolate such changes to a single
class? Thanks a bunch! --dawn
Class O.TimeSpan Extends (%Persistent, %MV.Adaptor)
{
Property TimeSpanCode As %String(
MVITYPE = """O.TimeSpan""->calcTimeSpanCode(@ID)",
MVPROJECTED = 1,
MVTYPE = "I")
[ Calculated, SqlComputeCode = {
Set {TimeSpanCode}=##class(O.TimeSpan).calcTimeSpanCode({%%ID})
}, SqlComputed ];
ClassMethod calcTimeSpanCode(ItemID As %String) As %Integer [ Language =
mvbasic ] {
RETURN FIELD(ItemID,'*',2)
}
}
You don't need to have both a property and an XDATA block for TimeSpanCode.
The only awkwardness here is that you need to define the same method call in
both the MVITYPE parameter and in the SqlComputeCode, one in mvbasic and one
in COS. The reason for the duplication is that the I-type needs to have some
code to compile and store on it's own, because CMQL doesn't use the class at
all to get the data (often, there may not be a class at all). But it's not
so bad because both are just calling a method.
There's no need to have the MVITYPERTN in the class at all, anywhere. It
only needs to be in the DICT i-type entry, and gets put there by the i-type
compiler (which runs automatically on uncompiled i-types when needed). It's
subject to arbitrary change whenever the itype changes, so you don't want it
in the class at all.
The problem here (based on John's previous post) is that the Itype is
calling a class method. If you use the Itype through a q-pointer from
another account, you need to make sure the class is mapped into that
account. The alternative is duplicating the code from calcTimeSpanCode into
the MVITYPE parameter instead of having MVITYPE call the method directly.
But it's probably easier to maintain package mappings than to maintain
redundant code (or more redundancy then necessary, since we already have
that redundancy between MVITYPE and SqlComputeCode.)
I do have a question -- one of the issues on my list is that the
properties all have to be in one line of code, but you have split it
out into separate lines, so I'm excited about that too. When I break
it into multiple lines, it compiles, but is placed back onto a single
line. Is there a setting you have that permits these multi-line
properties? (I know it works after "SqlComputeCode = {" to go to
another line).
Thanks. --dawn
Cache doesn't store classes as text. They are in globals with lots nodes and
subnodes. This isn't exact (not my area--ultracrepidarian warning), but
there's probably a node for the class, and under that a node for each
property, and under those a node for each parameter, and a node for each
modifier and so forth. The format is designed for efficiency in the class
compiler, and also for runtime speed. Studio rebuilds the text every time it
displays the class, but doesn't have any info on where you might have had
line breaks. At this point, it looks like you're free to put in line
breaks, but most of them aren't stored, so will be lost when the class is
redisplayed. The places where line breaks are remembered are probably inside
of code blocks--anything between {braces} is probably stored literally. I
don't know if that's something identified for enhancement or not. Studio
used to reorder all your properties in alphabetical order, but now remembers
the order.
Ah, OK, I misunderstood, thanks.
> CMQL is doing a simple
> execution of that routine, and needs to know the namespace to work through a
> q-pointer,
If we do not use q-pointers, can we get away without this somehow?
> unless you've done routine mapping, which most "traditional"
> multivalue users will probably never touch. So the routine name and
> namespace need to be in the dict, but you don't need to have them in the
> class.
If we keep those out of the class, will everything work until the
point we create a q-pointer?
> Cache doesn't store classes as text. They are in globals with lots nodes and
> subnodes. This isn't exact (not my area--ultracrepidarian warning), but
> there's probably a node for the class, and under that a node for each
> property, and under those a node for each parameter, and a node for each
> modifier and so forth. The format is designed for efficiency in the class
> compiler, and also for runtime speed. Studio rebuilds the text every time it
> displays the class, but doesn't have any info on where you might have had
> line breaks. At this point, it looks like you're free to put in line
> breaks, but most of them aren't stored, so will be lost when the class is
> redisplayed.
Yup, that is what happens. One of the first impressions that a new
developer (and even an old one) has in this environment is that the
classes are way too hard to read, with the long lines (sometimes a
single property line is the length of a typical java class) being a
good part of that. This is one of those things that can suck some joy
out of coding for a subset of developers, just FYI. I was pretty much
told that my request for an enhancement in this area would be closed
without putting it in any queue whatsoever (if I understood
correctly).
> The places where line breaks are remembered are probably inside
> of code blocks--anything between {braces} is probably stored literally. I
> don't know if that's something identified for enhancement or not.
I don't think so. My WRC request was closed and not passed as a
prodlog. I considered submitting a follow-up asking that studio have
a setting to show properties, splitting out indented lines at commans,
but decided that there are folks who think my requests of this nature
to be trivial and there were bigger fish to fry. But if you guys
would like to lobby a little on this, I sure wouldn't mind. It seems
like a big bang for the buck enhancement which can be isolated to the
display of the code, rather than any change to the compiler, for
example.
> Studio
> used to reorder all your properties in alphabetical order, but now remembers
> the order.
Thank goodness! Thanks. --dawn
> -----Original Message-----
> From: InterSy...@googlegroups.com
> [mailto:InterSy...@googlegroups.com] On Behalf Of Dawn Wolthuis
> Sent: Wednesday, May 28, 2008 3:39 PM
> To: InterSy...@googlegroups.com
> Subject: [InterSystems-MV] Re: Removing account-specific code
> for I-descs
>
>
>
> On Wed, May 28, 2008 at 2:12 PM, Ed Clark
> <ecl...@intersystems.com> wrote:
> > CMQL is doing a simple
> > execution of that routine, and needs to know the namespace to work
> > through a q-pointer,
>
> If we do not use q-pointers, can we get away without this somehow?
It's not something you need to worry about. When the I-type gets compiled
(either explicitly with ICOMP or implicitly the first time it's used), it
puts the namespace-qualified routine name (the MVITYPERTN) into attribute 13
of the dictionary. So CMQL will run. You don't need the MVITYPERTN in the
class, and in fact you want it NOT to be there, because when you change the
itype in the class, and compile the class overwriting the dict entry, you
want there to be nothing in attribute 13 so that the i-type will recompile
on the next use.
Next year at Devcon, ambush the studio developers :)
If you want to keep close to what you're already doing, you could refine
your current process. Instead of:
> Property TotalSnuppies As %String(MVITYPERTN =
> "I8^|""ADEV""|MVI.1", MVPROJECTED = 0, MVTYPE = "I") [
> Calculated, SqlComputeCode = {
> Set {TotalSnuppies}=##class(S.Snuppies).calcTotalSnuppies({%%ID},
> {%RECORD})}, SqlComputed ];
>
> XData MVAdditionalDictItems
> {
> <DictItems>
> <DictItem Name="TotalSnuppies">
> <Attr>I</Attr>
> <Attr>SignUpSnuppies + InviteSnuppies + RegistrarSnuppies +
> SupportSnuppies </Attr> <Attr>.</Attr> <Attr>Total</Attr>
> <Attr>5R</Attr> <Attr>S</Attr> </DictItem> </DictItems> }
>
> ClassMethod calcTotalSnuppies(
> ItemID As %String,
> Item As %String) As %Integer [ Language = cache ]
> {
> Set $mvv(16)=ItemID,$mvv(17)=Item Quit $$I8^|"ADEV"|MVI.1
> }
You could use:
Property TotalSnuppies As %String(MVHEADING = "Total",
MVITYPE = "SignUpSnuppies + InviteSnuppies + RegistrarSnuppies +
SupportSnuppies",
MVJUSTIFICATION = "R",
MVTYPE = "I",
MVWIDTH = 5,
MVPROJECTED=1 )
[ Calculated,
SqlComputeCode = {
Set $mvv(16)={%%ID},$mvv(17)={%RECORD}
Set {TotalSnuppies}=$$I8^|"ADEV"|MVI.1
},
SqlComputed
];
MVITYPERTN doesn't need to be specified--it gets put into the dictionary
item when it's compiled. You don't need the xml data block--there are
parameters for all the parts of the i-type, and since you aren't specifying
MVITYPERTN anymore, you can project the i-type now. You don't need a
separate method to do the sql computation because you are just calling the
method compiled from the itype.
If you always run the application from a single account (no q-pointers) then
you don't need the namespace in the routine, so:
Set {TotalSnuppies}=$$I8^|"ADEV"|MVI.1
Can be
Set {TotalSnuppies}=$$I8^MVI.1
I think that should work, though you still need to compile the i-types, then
write the routine name back into the class. You could actually work around
that--continue to have the sqlcompute call a class method that's written in
mvbasic and uses the ITYPE function. But that would be more runtime overhead
for the sql compute.
There is no "I-desc specification language", at least not on cache. On
unidata and universe, the code you use in an i-descriptor is distinct from
mvbasic, and there are functions you can use in a basic program that don't
work in an i-type (very frustrating), and things that go into an i-type that
don't work in mvbasic. On Cache, what you put into an i-descriptor is
mvbasic, just with a compressed syntax so that it can go on one line. When
you compile an i-type it gets converted into normal mvbasic code (though in
an MVI type routine instead of the MVB routines that you are normally used
to working with). For example:
1. create a new account and logto it (this is to simplify finding the
compiled code later)
2. enter an i-type into DICT VOC that looks like:
TEST1
0001 I
0002 FIELD(@RECORD<1>,'-',1,3); INDEX(@RECORD<2>,'*',1); @2 * "20";
@1:':':@3
3. Compile the i-type with ICOMP, or use it in a list to get it compiled.
4. Look at the i-type attribute 13. It looks something like:
0013 I1^|"TEST"|MVI.1
5. Attribute 13 in my example tells us that the expanded code for the i-type
went into a routine named MVI.1.MVI as function I1. Open studio and open
MVI.1.MVI (or whatever yours says. Note that because this is an MVI routine
instead of an MVB, it isn't in a file--it's right at the top level of
studio's file view). It looks like:
$OPTIONS ITYPE VEC.MATH
DIM %MVTOTALD(),%MVTOTALI(),%MVTOTALE()
FUNCTION I1()
MV$ITYPE$1=FIELD(@RECORD<1>,'-',1,3)
MV$ITYPE$2=INDEX(@RECORD<2>,'*',1)
MV$ITYPE$3=MV$ITYPE$2 * "20"
RETURN MV$ITYPE$1:':':MV$ITYPE$3
END
Function I1() is there. It's normal mvbasic. When you use the i-type, it's
running this routine.
If you wanted, you could run this routine too, using the cos DO command:
DO I1^MVI.1
If you are running it from another account, you need to include the
namespace:
DO I1^|"TEST"|MVI.1
Which is exactly what's in the i-type attribute 13. The namespace name needs
to be in there so that the i-type will work if called through a q-pointer
from another account.
Now, enter another i-type that references the first one. Something like:
0001 I
0002 '<':TEST1:'>'
My MVI.1.MVI now looks like:
$OPTIONS ITYPE VEC.MATH
DIM %MVTOTALD(),%MVTOTALI(),%MVTOTALE()
FUNCTION I1()
MV$ITYPE$1=FIELD(@RECORD<1>,'-',1,3)
MV$ITYPE$2=INDEX(@RECORD<2>,'*',1)
MV$ITYPE$3=MV$ITYPE$2 * "20"
RETURN MV$ITYPE$1:':':MV$ITYPE$3
END
FUNCTION I2() ; RETURN '<':$FUNCTION('I1^|"TEST"|MVI.1'):'>' ; END
Function I2 will be run when i-type TEST2 gets used. The $FUNCTION function
is a function that allows you to call a cos routine directly from mvbasic.
Notice that to get the value of TEST1, function I1 is being called using
it's full namespace qualified name. If you were coding methods in a class,
that qualification wouldn't need to be there because you would know that the
class was visible.
1) Must be able to specify virtual fields using the language used to
specify virtual fields in the PI family of PICK products (maybe in
others too, I simply don't know them as well). We have been able to
do this successfully, even if somewhat tediously, thereby
single-sourcing derived data for our code -- this derived data can
then be used in Zen pages, SQL queries, and MV selects (and lists at
the colon prompt for dev purposes). That is very cool -- super!
[BTW, I am hopeful that the performance on these is not poorer than
performance in other MV products, but a little bit cautious so I hope
to keep these from writing too many I-desc until we have a better
understanding of that.]
however...
2) Must NOT have to hard-code the account name in our source code. I
would think this requirement to be obvious, but if, for some reason,
we cannot meet requirement 1 without hard-coding the account name,
then at the very least we must be able to (alternate requirement)
specify it one time for the entire account and use a variable (a
"constant variable") in our source code.
How can we accomplish 2, either the way one would like to or with a
parameter (in a file or a class, whereever) that can be specified one
time and not in every virtual field?
Thanks. --dawn
From what I understand, the core of your application is going to use SQL and
ZEN. Those get their data by using the sql compute method defined for the
property in the class. The way you are doing it now, ZEN calls a sql compute
method, which then calls a routine compiled from an itype. It would be
marginally more efficient, and more readable, if the sql compute method did
the actual work itself instead of calling another routine. When you use cmql
"at the colon prompt for dev purposes" the i-type will be calling a class
method, which might be marginally slower, but will be much better
documented.
The way you are doing it now--having the i-type routine do the work--traps
you in a 3-step method. Every time you change the i-type code (or when you
first create it) you need to compile the class, compile the i-type, and then
copy the routine name into the class and compile again. The class compiler
can't compile the i-type for you, because itypes may depend on other itypes
in other files or accounts, but even if it did you would still have to go
back and put the itype routine into the method. You could probably use a
generated method to fill in the routine name, but you would still have to
compile the class twice. You could use a program to do the compilations
using the %SYSTEM.OBJ class methods, but it seeems like a lot of work for a
workaround.
As I mentioned before (below) you don't need to put the namespace name as
part of the routine:
Quit $$I8^|"ADEV"|MVI.1
Could be simply
Quit $$I8^MVI.1
Provided that your application is running all from one namespace, or that
you're never going to call the method from a different namespace. I suspect
that's probably the case, so you don't need the namespace qualifier there.
I'm not understanding why you want to "specify virtual fields using the
language used to specify virtual fields in the PI family of PICK".
That
"language" is terse and difficult to maintain (for anything that requires
multiple statements you need to manually keep count to use the @n result
variables) and isn't anything special--it converts trivially to normal
mvbasic.
That is a little disconcerting. I am on Cache for Windows (x86-32) 2008.2 (Build 526_0_7489) Tue Nov 11 2008 13:09:40 EST. I am running it under the "REALITY" emulation. When I compile my class, I get this in the dictionary:
Jim,
I'll be the first to admit that I'm not sure about the best way to go about this. Here is what I'm trying to accomplish and maybe there is a better way than what I'm trying to do.
We have set up a development environment where each developer has their own namespace and a sandbox of code checked out from source control. This works for us really well and we want to keep that setup. We are trying to develop a strategy for deploying dictionaries from the class because that seems like it will be the easiest way to deploy to multiple servers and namespaces.
The problem is that the code that is generated by PROTOCLASS is namespace dependent. I just found out last night that the storage code had put my namespace in the references so queries in another namespace was returning result sets from my namespace. We are trying to achieve namespace independence specifically for itypes. This thread had some good ideas and I've implemented many of the ideas that Ed suggested here for one test class and it seems to work ok.
I guess the big question is, is there a better way to deploy classes with itypes to different namespaces and have the generated code work? The most frustrating part is that the MVI.1 file that is generated for itypes is entirely dependent on the order and number of itypes previously generated.
I did find your document and read through it. The biggest problem that
we had was getting the MVI.1.mvi routine into source control.
Fortunately, we don't have the same constraints that you do and I am
allowed to try all sorts of different options. The option that Ed
outlined seems to work ok for us. The key seems to be leaving out or
removing the MVITYPERN parameter. We are just running up against a
really ugly dictionary for a couple of files. It looks like previous
developers were into creating dictionaries for the sake of saving a
couple of key strokes (a dictionary named "-" is a good case in point).
Our entire deployment procedure is based on our source control. To
deploy code, we check it out from the repository and then import it and
compile it. This has actually worked very well in Cache and that is the
driving force behind a lot of this discussion. We want an easy way to
deploy dictionaries to our various servers and namespaces.
Unfortunately, like you encountered, moving my code from my namespace to
our staging or other developer namespaces has caused headaches.
Jason
Group,
I’ve recently done some playing around trying o hook SSRS (SQL Server Reporting Services) up to my dev Cache server. Unfortunately SSRS is pretty picky about what sort of datasource it would hook up too and even though I’ve added Cache as an ODBC datasource I didn’t have any luck getting a connection.
Has anyone ever successfully done this, and what was the trick to making the connection.
Thanks,
James
The ODBC connection works fine, it just does not appear when trying to select it when creating a datasource in SSRS from Visual Studio 2005.
<BR