go to post Rubens Silva · May 26, 2017 As far as I know, %OnBeforeSave is used to prevent (abort) the save operation and to customize %Save status.You can't change any properties on that phase as they won't be reflected, that is because the payload is already queuedto be saved.%OnAddToSaveSet is executed before before the queueing phase, which allows you to overwrite property values.Unless you want to do something that deals with complex business rules, you should indeed use InitialExpression as Fabian suggested, even use ReadOnly to prevent the property's value to be edited.
go to post Rubens Silva · May 26, 2017 I don't recommend opening %ResultSet instances recursively.It's more performatic if you open a single %SQL.Statement and reuse that.This will also solve you quite some bytes being allocated for the process as the depth keeps growing.
go to post Rubens Silva · May 26, 2017 Yup, but if you read the source code, it's limited to 32000 kb.
go to post Rubens Silva · May 25, 2017 If you want something more regular you could pipe the output via OS:This also outputs more than 32000 kb. set errorLogDir = ##class(%File).TempFilename() set outputLogDir = ##class(%File).TempFilename() set command = "dir /A-D /B /S ""%1"" 2> ""%2"" > ""%3""" quit $zf(-1, $$$FormatText(command, "C:\InterSystems\Cache", errorLogDir, outputLogDir))Now simply open and read the logs.
go to post Rubens Silva · May 24, 2017 Unless you're asking for maximum performance you could create a method that receives or creates a %SQL.Statement for the %File:FileSet query and detect if "Type" is "D" to call it recursively, passing that statement instance.Here's a use case that I applied that pattern: Method SearchExtraneousEntries(statement As %SQL.Statement = "",path As %String,ByRef files As %List = ""){ if statement = "" { set statement = ##class(%SQL.Statement).%New() $$$QuitOnError(statement.%PrepareClassQuery("%File", "FileSet")) } set dir = ##class(%File).NormalizeDirectory(path) set row = statement.%Execute(dir) set sc = $$$OK while row.%Next(.sc) { if $$$ISERR(sc) quit set type = row.%Get("Type") set fullPath = row.%Get("Name") if ..IsIgnored(fullPath) continue if type = "D" { set sc = ..SearchExtraneousEntries(statement, fullPath, .files) if $$$ISERR(sc) quit } if '..PathDependencies.IsDefined(fullPath) { set length = $case(files, "": 1, : $listlength(files)+1) set $list(files, length) = $listbuild(fullPath, type) } } quit sc}
go to post Rubens Silva · May 24, 2017 I see that as an integrity viewpoint.What if the user customized the storage? If you don't export it with his definition, it would map to another global.This is even more fearsome if you map a class to a global since they're usually based on user-made globals. Removing Schema definition also means introducing a checkpoint to verify if the class is a %Persistent or not, since you can also have global mapped classes. That could also reduce the peformance.And the fundamental question is:Why you would versionate something different than what you actually intended?
go to post Rubens Silva · May 23, 2017 This indeed made the communication much easier!Now I only have to create a strategy for message batching.It seems that I can't simply use WaitForComplete since it puts the caller process into sleep.My next step is do an experiment with the IPQ variation.Anyway, here's my experiment using the WorkMgr. Class Log.Test2 [ Abstract ]{ClassMethod Log(){ set buf = "" set msg = "This is a test #" for i=1:1:1046000 { set composedMessage = msg_i_$c(13, 10) set expectedSize = $length(buf) + $length(composedMessage) if expectedSize > $$$MaxStringLength { write buf set buf = "" } else { set buf = buf_composedMessage } } if buf '= "" write buf quit $$$OK}ClassMethod Start(){ write "This is from "_$job, !! set queue = $System.WorkMgr.Initialize("d", .sc, .sc) set sc = queue.Queue("##class(Log.Test2).Log") if $$$ISERR(sc) do $System.OBJ.DisplayError(sc) quit sc set sc = queue.WaitForComplete() if $$$ISERR(sc) do $System.OBJ.DisplayError(sc) quit sc quit $$$OK}}
go to post Rubens Silva · May 22, 2017 Just like a CSP file.You have two ways of working with that: 1 - If you modify the file using the Studio, that file is exported automatically when you save it. 2 - If you modify the file outside the Studio you can import the file again to the project and it'll be overwritten with the new version (as long as it's newer).Anything inside the web folder is mirrored inside the csp application. Which means:If you have a file with the path C:\CacheProjects\yourproject\web\css\index.css It's name will be resolved to:csp/namespace/index.cssAnd will be mirrored like this:C:\InterSystems\Cache\CSP\namespace\css\index.cssIf you remove that file from the project, save it and export, then this file will be eliminated from the repository since it doesn't belong to the project anymore.The same rule is applied for each type. Not only the folder "web".
go to post Rubens Silva · May 22, 2017 If you want a proof-of-concept or a showcase, then this link shows how the class package hierarchy is followed.You can disable the use of .txt using two ways: 1 - By running ##class(Port.SourceControl.Config).SetSourceExtension("")2 - By running the wizard with ##class(Port.SourceControl.Wizard).Start() and navigating to the relevant option.Note that this will ONLY AFFECT the appended extension, not the type itself. Example:##class(Port.SourceControl.Config).SetSourceExtension("") = cls/Port/SourceControl/Hooks.cls##class(port.SourceControl.Config).SetSourceExtension("txt") = cls/Port/SourceControl/Hooks.cls.txtAll the same for following formats: INC, INT, MAC, DFI, MVB, MVI, BAS. Except if the file is inside the web (CSP) path.I could do a small screencast, but the usage is pretty straightforward. Anything more advanced than that is covered by the Wizard.How to use:1 - First time only: import the port.xml.2 - Run ##class(Port.SourceControl.Installer).Install().3 - Restart the Studio.4 - Done! Now whenever you save a file related to the project, it's structure is generated and the file is exported everytime you save it.5 - If you want to export all items regarding the current project you can use Source Control->Export Current Project.EDIT: Oh I forgot mentioning about tests.Unit testing with Port:As long as you have classes prefixed with the package "UnitTest" (which is also configurable). You can export these classes to XML and run their tests atomically.1 - SourceControl->Export Test Suites to XML.2 - Context SourceControl menu->Run Tests Associated with this item. By default when you install Port using the installer, it sets a parameter to run unit tests as you keep compiling the classes.You can disable that as well.If you notice any bugs, please fill a issue.If you still need a demonstration, please ask again.
go to post Rubens Silva · May 19, 2017 Wow, it worked! But I don't want it to send the data back to the other process for each iteration, so I tried buffering it using a variable. Now, If you run this code you will notice the following error: [CRLF] class '%Studio.General', method 'Execute': <WRITE> 41 zExecute+26^%Studio.General.1 The routine might according to the principal device, of course.There're two points I noticed here:1 - The caller process must wait for the job for it's first write. Or a <READ> error will happen.2 - The caller process must know when stop iterating if the job is finished.3 - Sometime the line break works, sometimes it doesn't.That's why I tried introducing the Event API, however it doesn't seems to be working, since the Signal is being ignored by that WaitMsg. Is there anything I'm missing here? Class Log.Test [ Abstract ]{ClassMethod Write(device As %String,start As %Integer = 1) As %Boolean{ set startEventTriggered = 0 open device:("127.0.0.1":33568:"M"):3 quit:'$test use device set buf = "" set msg = "This is a test #" for i=start:1:10460 { set composedMessage = msg_i_$c(13, 10)_" [CRLF] " set expectedSize = $length(buf) + $length(composedMessage) if expectedSize > $$$MaxStringLength { //set offset = expectedSize - $$$MaxStringLength //set subbuff = $e if 'startEventTriggered { set startEventTriggered = 1 use 0 do $System.Event.Signal($zparent, "1") use device } write buf set buf = "" } else { set buf = buf_composedMessage } } if buf '= "" write buf write $c(0) close device}ClassMethod Start(){ set server = "|TCP|1" set client = "|TCP|2" job ..Write(server)::3 do $System.Event.WaitMsg("", 3) open client:(:33568:"M"):3 // Moved open outside the loop quit:'$test while 1 { use "|TCP|2" read x:3 if x '= "" { use 0 write x } quit:x=$c(0) } close client}}
go to post Rubens Silva · May 19, 2017 That's one hell of a name, but I suppose it's:#define YourMacro $$zt^%zu2f%ztAnd if there's any parameter:#define YourMacro(%yourparam) $$zt^%zu2f%zt(%yourparam)
go to post Rubens Silva · May 19, 2017 Both plus globals.NoSQL: set obj = ##class(Sample.Person).%New() set obj.Name = "John" set obj.Spouse = #class(Sample.Person).%OpenId(1) // Let's assume it's a woman. set sc = obj.%Save() // Use sc to know if any error happened. SQL: set s = ##class(%SQL.Statement).%New() set sc = s.%Prepare("INSERT INTO Package.PersistentClass (property) VALUES (propertyValue)") set sc = s.%Execute() // If everything is OK sc is 1, otherwise you might want to check s.%Message and s.%SQLCODE as well. You can also use embedded SQL, search for "&sql" on the link I provided.GlobalsWARNING: I don't recommend any manipulation using globals directly if you 're working with classes.Normally class globals are stored following the pattern below:^Package.ClassNameI for index^Package.ClassNameD for values^Package.ClassNameS for streamsCheck the class's Storage XML discover it's global structure.
go to post Rubens Silva · May 17, 2017 I tried making a simple example, no Event or IJC for now, just to understand how devices works. I understand the concept, but there are many types of devices and types of configuration that always lead me to a dead end.I'll escalate the example (using Events and IJC) as I keep understanding the concepts.So, what am I doing wrong here? read x:3 always times out, regardless of my doings. Note: Just to make myself clear, I'm planning on using it along with this project. Well, use it with big projects and you'll understand.Maybe I could even create an utility that makes it easier to print a job's outputs as long as it's possible.Class Log.Test [ Abstract ]{ClassMethod Write(device As %String,start As %Integer = 1) As %Boolean{ open device:(:33568::"\n"):3 quit:'$test use device for i=start:1:1000000000 { write "This is a test #"_i_$c(13,10) if i#1000 = 0 { write "\n" } }}ClassMethod Start(){ set device = "|TCP|1" job ..Write(device)::3 while 1 { open "|TCP|2":(:33568):3 quit:'$test use "|TCP|2" read x:3 close "|TCP|2" if x '= "" { use 0 write x } }}}
go to post Rubens Silva · May 17, 2017 Oh, sorry for my misleading words.I said "parent process" or "main process" due to the $zparent, $zchild and $job. But I didn't mean something close to process forks, since as you said, they haven't a parent-child relationship, but more liking siblings as they can run independently.Which situtation would be interesting to change the principal device? This method's description is quite 'dry' to understand what is it's usage.I noticed that devices are my weak spot, maybe my questions are looking dumb for some people here. :PThanks for your help so far.
go to post Rubens Silva · May 12, 2017 Hmm, about I'll have to do something more elaborated then. I had done a quick draft before. Calling $ZUTIL(132) with no extra arguments makes the current device the principal device. And how does this affects the parent process? (if it does affect). Often more useful is the former $ZUTIL(82,12,bool), now ##CLASS(%Device).ReDirectIO(bool) which lets you redirect I/O through routines that can filter, redirect, record, though routines. I remember reading about that here and here
go to post Rubens Silva · May 12, 2017 1. Set temporary global: I though about using that, but I'm targeting the fastest batched feedback as possible. About $System.Event, I had the code, but switched to IJC, so I'll have to re-implement it. No problem though, I'll redo that asap.