go to post Eduard Lebedyuk · Jun 3, 2019 Great points, Sean.However there are, I think, areas where macros are needed:Compile-time execution (if it's possible to resolve stuff at compile time, do it)Constants/EnumsTrivial wrappers over system functions (i.e. I use $$$ts instead of $zdt($h, 3, 1, 3) because it's everywhere)But I definitely agree that overabundance of custom macros makes code unreadable.
go to post Eduard Lebedyuk · Jun 3, 2019 Let's say you created class Point: Class try.Point Extends %Persistent [DDLAllowed] { Property X; Property Y; } You can also create it via DDL: CREATE Table try.Point ... It would create the same class. After compilation our new class would have autogenerated Storage structure which is a mapping of global to data to columns and properties: Storage Default { <Data name="PointDefaultData"> <Value name="1"> <Value>%%CLASSNAME</Value> </Value> <Value name="2"> <Value>X</Value> </Value> <Value name="3"> <Value>Y</Value> </Value> </Data> <DataLocation>^try.PointD</DataLocation> <DefaultData>PointDefaultData</DefaultData> <IdLocation>^try.PointD</IdLocation> <IndexLocation>^try.PointI</IndexLocation> <StreamLocation>^try.PointS</StreamLocation> <Type>%Library.CacheStorage</Type> } What is going on here? From the bottom and up (bolded are important, ignore the rest): Type: type of generated storage, in our case the default storage for persistent objectsStreamLocation - name of global where we store streamsIndexLocation - name of global for indicesIdLocation - name of global where we store ID autoincremental counterDefaultData - name of storage XML element which maps global value to columns/propertiesDataLocation - name of global to store data Now our default data is PointDefaultData so let's see it. Essentially it says that global node has this structure: 1 - %%CLASSNAME2 - X3 - Y So we might expect our global to look like this: ^try.PointD(id) = %%CLASSNAME, X, Y But if we output our global it would be empty because we didn't add any data: zw ^try.PointD Let's add one object: set p = ##class(try.Point).%New() set p.X = 1 set p.Y = 2 write p.%Save() And here's our global zw ^try.PointD ^try.PointD=1 ^try.PointD(1)=$lb("",1,2) As you see our expected structure %%CLASSNAME, X, Y is set with $lb("",1,2) which corresponds to X and Y properties of our object (%%CLASSNAME is system property, ignore it). We can also add a row via SQL: INSERT INTO try.Point (X, Y) VALUES (3,4) Now our global looks like this: zw ^try.PointD ^try.PointD=2 ^try.PointD(1)=$lb("",1,2) ^try.PointD(2)=$lb("",3,4) So the data we add via objects or sql is stored in globals according to storage definitions (you can modify manually storage definition by replacing X and Y in PointDefaultData - check what happens to new data!) Now, what happens when we want to execute SQL query? SELECT * FROM try.Point It is translated into ObjectScript code that iterates over ^try.PointD global and populates columns based on storage definition - PointDefaultData part of it precisely. Now for modifications. Let's delete all the data from the table DELETE FROM try.Point And let's see our global after it: zw ^try.PointD ^try.PointD=2 Note that only ID counter is left, so new object/row would have an ID=3. Also our class and table continue to exist. But what happens on: DROP TABLE try.Point It would destroy the table, class and delete the global. zw ^try.PointD
go to post Eduard Lebedyuk · Jun 3, 2019 Classes and tables are mappings that are read "on the fly". That's exactly it.
go to post Eduard Lebedyuk · Jun 3, 2019 Stores data on disk? Meaning, that if we create these persistent classes, the data is stored twice? Once in the global node and in some other format as defined (or not defined by the class)?The data is stored in globals and only globals. Globals themselves are physically written on disk.But just to reiterate, how does the DDL interpret a read definition into a write definition? The data stored in the global node and the definition do line up exactly (in our case, almost not at all).Please expand your question. Do you mean how does SELECT or DROP or whatever SQL statement against table interacts with globals?
go to post Eduard Lebedyuk · Jun 3, 2019 Preface macro with /// to enable autocomplete: /// #define $$$In...
go to post Eduard Lebedyuk · Jun 3, 2019 Globals store data. That's the only way data can be stored inside Intersystems products.Tables and classes are projections of this data. In your created classes (at the end) there's a Storage element.It contains mappings of class properties (and table columns) to global nodes.So if you delete this representation it does not delete the underlying global data, because class is essentially just a description of how to read and write into global.
go to post Eduard Lebedyuk · Jun 1, 2019 Why do you want to do that?Can you describe what do you want to achieve?
go to post Eduard Lebedyuk · Jun 1, 2019 It should be started automatically, but you can configure one explicitly at: SMP - System Administration - Configuration - Zen Reports - Excel Servers.Set log file and try to start it from the same page. I think it would show the root problem.
go to post Eduard Lebedyuk · Jun 1, 2019 Have you checked that Java is in or not in path for cache service account?
go to post Eduard Lebedyuk · May 31, 2019 Please post:Your REST brokerExample of request and response from browser
go to post Eduard Lebedyuk · May 30, 2019 Added a dummy BS to production with OnInit: Class test.InitService Extends Ens.BusinessService { Parameter ADAPTER = "Ens.InboundAdapter"; Property Adapter As Ens.InboundAdapter; Method OnProcessInput(pInput As %RegisteredObject, Output pOutput As %RegisteredObject) As %Status [ CodeMode = expression ] { $$$OK } /// This user callback method is called via initConfig() from %OnNew() or in the case of SOAP Services from OnPreSOAP() Method OnInit() As %Status { $$$TRACE("INIT") set sc = ..SendRequestAsync(...) quit sc }
go to post Eduard Lebedyuk · May 27, 2019 What's the use case? I think it's better to keep independent tasks separate.Anyway:1. Create your own task class by extending %SYS.Task.Definition.2. Program your taskSpecify TaskName parameter to provide readable task nameAdd class properties if needed - they are task argumentsOverride OnTask method to program your logic3. Create a new task, choosing your task class (by TaskName).
go to post Eduard Lebedyuk · May 24, 2019 1. Execute this query: SELECT ID FROM %SYS.ProcessQuery WHERE LastGlobalReference [ '1119102928' 2. Iterate over result set. ID is process ID. 3. Kill process by id: do $system.Process.Terminate(processId)
go to post Eduard Lebedyuk · May 21, 2019 It's better to not create lists, especially lists which you need to filter later.Write a SQL query, iterate over it results and do stuff you need to do right there.SQL Query can be a separate class element for readability purposes.
go to post Eduard Lebedyuk · May 21, 2019 1. I'd like to add to @Nicole Aaron answer, that moving code is done in two separate steps:On developer machine the code is created, written to files and commited into source control systemCI/CD server (in your case TFS) is triggered on repository push and executes CI script - which is OS-level commands in our particular case and would be the same between GitLab or TFS.
go to post Eduard Lebedyuk · May 21, 2019 Interesting questions.There are several ways to achieve clean and pseudo-clean builds:Containers. Clean builds every time. Next articles in the series explore how containers can be used for CI/CD.Hooks. Curently I implemented one-time and every-time hooks before and after build. They can be used to do deletion, configuration, etc.Recreate. Add action to delete before build:DBsNamespacesRolesWebAppsAnything else you createdI agree with @Ben Spead here. System default settings are the way to go. If you're working outside of Ensemble architecture, you can create a small settings class which gets the data from global/table and use that. Example.