Stephen Wilson · Aug 18, 2017 go to post

Could you share your straightforward code? For searching all routines in a namespace in Cache ObjectScript, I have used Find in Files within Cache Studio (class is %SYS.%Studio.Project) but it looks like you are looking to programmatically replace what is returned as a result. The basic 'Find and Replace' only seems to only apply to the currently active window. I do not know if Atelier has more advanced Find in Files and & Replace feature to search the entire namespace but from the documentation I've read, I suspect not.

Other search facilities I have used include %GCHANGE routine and %RFIND. %GCHANGE has an option for changing the contents of a global - useful if you have XECUTE and indirection (@) statements that call executable code from a global reference. This practice was more common when the size of routines had to be very small and executing statements from within a global reference was one way getting round this limitation. If you have no executable code in globals then %GCHANGE probably doesn't apply.

Check out the ObjectScript function $TEXT to return source code from a routine. You could use ZINSERT and ZREMOVE commands to programmatically alter the contents of source code.  

UPDATE: If I was trying to solve this problem. I would do a find in files to get the routine references. Verify the output and copy the contents to a plain text file. Clean up the file if necessary removing data that is not required. Read the contents of the file to obtain the routine references. Use ZINSERT and ZREMOVE to replace the source code contents. You will probably need to re-compile for the changes to take effect.

Stephen Wilson · Aug 16, 2017 go to post

What about using more complex subcommand prefixes such as

ZZMYCOMMAND /MYSWITCH:OPT1:"some_param"

Could you code that using %ZLANG ?

Stephen Wilson · Aug 16, 2017 go to post

Note the variable 'waittoend' and the zenPage.GetAuditLogs(RowId) method. This process builds the global that the secondary tablePane is built from. Without the return value, it is possible for the table to refresh before the global is built for the newly selected row.

There is additional documentation here .

Stephen Wilson · Aug 3, 2017 go to post

The I+3 is a hard-coded expression. At first it appears arbitrary or random. I have just taken +3 as an example. We have real-world examples of expressions like 

When some condition is met 
LINE(I-8)="some value"
When another condition is met
LINE(I-17)="some value"

The hard-coded offset does not come from a variable. A lack of comments in the code makes it very difficult to understand and debug.

Stephen Wilson · Aug 3, 2017 go to post

One possible answer is the following:

ZBREAK *LINE:"T"::"ZWRITE LINE(I)"

This will write the value to the TRACE device, which is a log file in my case. Not sure how to handle arbitrary subscript calculations like ZWRITE LINE(I+3) but this is close to what I'm looking for. Any other suggestions/comments are welcome.

Stephen Wilson · Jun 26, 2017 go to post

Thanks Nikita Savchenko for the mention. Excellent detail in the article, I really appreciate putting this article out to the community for comments. Interesting note about libssh2.dll/.so. I would love to see an early proof-of-concept of libssh2 working with WebTerminal. I would also like the option to use Telnet or SSH rather than being forced into using one or the other. 

Stephen Wilson · May 26, 2017 go to post

Same problem occurred in Cache 2017.1.

What if you simply changed your dataGrid page size to 12?

If you do that then the scroll bars do not really come into play.

Stephen Wilson · May 24, 2017 go to post

The link in MappingExample4.zip appears to be missing.

I have attached my own version mappingexample4_community_v1.zip   including the full ^ParentChild global. This version does not contain example usage of 'Next Code' and 'Invalid Conditions' as referenced in the article. 

As a example of using invalid conditions, if you want to skip all the 3rd subscripts in that have "DEL" as the subscript value use {L3}="DEL" as your invalid condition.

Stephen Wilson · Apr 6, 2017 go to post

Thanks Tom, we have implemented something very similar using the $System.License object.

Stephen Wilson · Mar 29, 2017 go to post

Sadly, the %CSP.REST class does not exist in Caché 2013.1.6 and the Class Explorer UML tool only works in Caché 2014.1 or later. We really struggle to keep pace with upgrades to Caché as there is always a fear that something will break after an upgrade is applied. Testing to confirm everything is working as expected is a huge undertaking and requires a lot of organisation. The next opportunity to upgrade Caché will probably be around Sept-Oct 2017.

Stephen Wilson · Mar 29, 2017 go to post

Personally, I would use the Visual Basic language styling on a white background. The linked COS UDL styling pack leaves much to be desired. The lime green for strings in the COS UDL won't show up very well on a white background so you can change the background via the Settings > Style Configurator. Post-conditional expressions and Method parameters are not recognised  :(

Stephen Wilson · Mar 24, 2017 go to post

1.  Good question but I'm not fully sure I understand the design-time and runtime concepts so I'll try to explain it in different terms. If you open two Caché  objects in memory using something like ##class(MyProjectName.MyClass1).%OpenId(id1) and ##class(MyProjectName.MyClass2).%OpenId(id2) can you create an arbitrary parent-child relationship object that links the two open objects within the executing ClassMethod? The implication is that if you were then to delete the parent object, you would also delete it's children.

2. Exporting individual classes from one server to another and getting compile errors due to missing dependencies and child dependencies. The classes have been provided by another programmer but the relationships are not necessary for what I am doing. I would question why so many relationships were created but I suspect the programmer was trying to ensure that all the data and all the levels of a single global were mapped to a class property.

3. I'll initially try to keep this high-level and theoretical rather than immediately delve into working code examples. I can tell you that the classes have been created from a Global using a CacheSQLStorage strategy.

Stephen Wilson · Mar 16, 2017 go to post

We don't have any plans to introduce new namespaces but I can see how this would be a useful feature for some people. We have 'training' and 'live' namespaces that have a one-to-one mapping with a database. It allows you to test software in a "logically partitioned" area of the production server. This setup has its quirks. When you copy a live routine into a training database you have to run a routine that essentially converts references to live namespaces and replaces them with training namespaces. Training namespaces always end in 'T'. If you forget to run this routine, you could accidentally modify the live data...

Stephen Wilson · Mar 16, 2017 go to post

You can also get this kind of information from the cache.cpf and CSP/bin/CSP.ini files. The Write $ZV function will get your version information. You may also view the system start-up time from the cconsole.log

Stephen Wilson · Mar 16, 2017 go to post

Maybe is obvious to people but the downside to encryption is that if you lose your encryption keys, you lose all of your encrypted data. That would be ironic if the reason you encrypted your data was to prevent data loss and theft. This fear of data loss has been the key driver for not using key-based encryption within our applications - even though there are good business reasons for doing so.

Stephen Wilson · Mar 16, 2017 go to post

No more certificate errors. I believe the issue with the certificate is now resolved.

Stephen Wilson · Mar 15, 2017 go to post

Hi Marco,

I think your problem might be to do with having declared 'TotalMatrix' as a transient property, it does not exist in the 'Relations' table. In my example, I referenced a table class called 'ZenTutorial.PhoneNumber' but my SQL statement did NOT refer to a 'TotalPhones' column.

Consider this

colExpression="(select count(*) from ZenTutorial.PhoneNumber where Contact=ZenTutorial.Contact.ID)"

So the result of the SQL goes into my TotalPhones column on my tablePane but the ZenTutorial.PhoneNumber class does not recognise it as a SQL field. Transient properties are useful when opening %Persistent objects and dynamically setting the transient property at the %OnOpen() event based on a set of conditions. As TotalPhones is marked as transient, it is not stored in the Global Mapping for %Library.CacheStorage. You cannot reference it in your SQL statement.

What if you tried something like

SELECT COUNT(*) AS TotalMatrix FROM Relations

You will need all those relationships in there though for it to work. If you have a <column> section defined in your tablePane be sure to set colName="TotalMatrix"

I hope that helps.

Stephen Wilson · Mar 15, 2017 go to post

To summarise, the answer is twofold:

1) Use the OnCreateResultSet event of the tablePane to get your filter value and pass it into your custom SQL. I have appended any filter values onto the end of my WHERE clause in each SQL fragment.

2) Use the 'onrefresh' event of the tablePane to call JavaScript to hide other ZEN components when the table is updated.

Stephen Wilson · Mar 15, 2017 go to post

Nope, certificate error still remains. Our web security policy blocks access to sites with certificate errors.

Error details are as follows

VERIFY DENY: depth=0, CommonName "*.influitive.com" does not match URL "globalmasters.intersystems.com"
Stephen Wilson · Mar 14, 2017 go to post

This is excellent! Probably the best answer on this question. I would add that sometimes the process is the login session of an individual user and what the user does within that session needs to be carefully managed!

You can also use set $ZS=1234567 to set the max-limit memory for your process. Useful if the default memory is inadequate for a particular process but is fine for everything else.

Stephen Wilson · Mar 14, 2017 go to post

Thanks Wolf Koelling.  I should have made it more explicit that %vars are shared across NAMESPACES not processes. When a user logs into our system their process remains active as long as they are logged in. During their login session they can call any number of COS routines to perform a wide range of different functions. Variables not explicitly killed off still reside in memory and this was the problem I had to solve. 

I had to be sure that

a) The %ZLOG COS routine would not crash out because of missing variables the routine expects.

b) All the calls or entry points into %ZLOG would still work as normal otherwise a system-wide crash would occur across all our databases.

c) Be able to identify that if my background process had called %ZLOG then use the PPG created before the call to %ZLOG. No other code in our system uses PPGs whereas there is a plethora of globals, %variables and non-percent variables. 

I could have used a scratch or temporary global such as ^CacheTempUser.DataExtract($JOB). This type of global is killed off when the instance is brought down for our daily backup job. A PPG was very easy to implement.

I have placed a strikethrough on the erroneous statement in my answer.

Stephen Wilson · Mar 10, 2017 go to post

Lets say I have a *.INT routine called %ZLOG. This routine can be called from any namespace and when it is called,  some variables are already defined. I want to check for the existence of a specific PPG before overwriting a collection of variables set by something further up the call stack. If the PPG I created exists then I can safely assume that it was my process that set it.

eg. Before calling %ZLOG, set a PPG in your process then at the start of %ZLOG check for the existence of the PPG.

// Assume variables are set by something else if PPG not present in %ZLOG
Set var1=$GET(var1)
Set var2=$GET(var2)
Set: $DATA(^||ExtractProcess("var1")) var1=^||ExtractProcess("var1") 
Set: $DATA(^||ExtractProcess("var2")) var2=^||ExtractProcess("var2")

PPGs are cleared when the process is terminated so you don't have to kill them off. So if you plan to run something as a background job or have some kind of unique transaction taking place for an individual user's process, PPGs are good to use. In this example, I have opted to use a PPG rather than using the NEW keyword. NEW is only useful if you know what variables to NEW and what not to NEW. At least with a unique PPG I know for a fact that it was my ExtractProcess that created the PPG. %VAR variables are accessible for any namespace but are not specific to a particular process.

Stephen Wilson · Nov 25, 2016 go to post

Ok, I *think* I have it now.

ClientMethod onSelectRow() [ Language = javascript ]
{
	var resultsPane=zenPage.getComponentById('resultsPaneId');
	var isHidden=resultsPane.getHidden();

	if (!isHidden)
	{
		var table=zenPage.getComponentById('workBioTable');
		var dataRow=table.getRowData(table.selectedIndex);


	if (dataRow)
	{
		var rowID = zenPage.GetSelectedRowId(dataRow.RequestDate,dataRow.Worksheet,dataRow.SpecId);

		if (rowID)
		{
			this.populatePIDForm(dataRow);
			var waittoend = zenPage.GetAuditLogs(rowID);
			this.populateAuditEvents();
		}
	}


} // end-if
}

Note the variable 'waittoend' and the zenPage.GetAuditLogs(RowId) method. This process builds the global that the secondary tablePane is built from. Without the return value, it is possible for the table to refresh before the global is built for the newly selected row.

Testing this has been difficult because in the most cases, the table was displayed correctly but several dozen clicks later you gasp, "Did I just see that!?" when you notice that on this one occasion it did not update correctly.