There is a short answer to this and a long explanation. The short answer is that the warning is accurate and you might experience problems if you use the same name for two class members. When defining classes myself, I keep method names and property names distinct, mostly by using nouns and verbs. For indices, I prefix the name with a letter indicating the index type.

The reason why you should avoid non-unique names is simple. Most member types inherit methods from what I refer to as the member super tree. The member super tree has an abstract top node with two subnodes, the member super class and the declared type class. The member super class for properties is often called the "property class" and the declared type class is the class the author defines as the type. Methods inherited from the member super tree are implemented in the resolved member runtime as a composite name formed from the member name and the method name. For a property named 'Foo', you might see methods in the class runtime named 'FooSet' and 'FooGet'.

If two members share a name it is possible that a composite method name will conflict between the two member types. We try to avoid this when implementing our member super classes and our type classes but it is still possible to encounter runtime method name conflicts.

This method call is not part of any release but, as others have mentioned, you can use %Net.Http to submit a request and receive a response. I can help you write that request. How does this look?

 

USER>set matrix = ##class(<your classname goes here>).GetJSON("https://maps.googleapis.com/maps/api/distancematrix/json?units=imperial&origins=40.6655101,-73.89188969999998&destinations=40.6905615%2C-73.9976592%7C40.6905615%2C-73.9976592%7C40.6905615%2C-73.9976592%7C40.6905615%2C-73.9976592%7C40.6905615%2C-73.9976592%7C40.6905615%2C-73.9976592%7C40.659569%2C-73.933783%7C40.729029%2C-73.851524%7C40.6860072%2C-73.6334271%7C40.598566%2C-73.7527626%7C40.659569%2C-73.933783%7C40.729029%2C-73.851524%7C40.6860072%2C-73.6334271%7C40.598566%2C-73.7527626&key=<you api key goes here>",{"SSLConfiguration":"<your SSL Configuration goes here>"})

USER>write matrix.%ToJSON()

{"httpStatus":"200","message":"HTTP/1.1 200 OK","content":{"destination_addresses":["67-89 Pacific St, Brooklyn, NY 11201, USA","67-89 Pacific St, Brooklyn, NY 11201, USA","67-89 Pacific St, Brooklyn, NY 11201, USA","67-89 Pacific St, Brooklyn, NY 11201, USA","67-89 Pacific St, Brooklyn, NY 11201, USA","67-89 Pacific St, Brooklyn, NY 11201, USA","585 Schenectady Ave, Brooklyn, NY 11203, USA","66-0-66-26 103rd St, Rego Park, NY 11374, USA","931-947 N Village Ave, Rockville Centre, NY 11570, USA","300-448 Beach 19th St, Far Rockaway, NY 11691, USA","585 Schenectady Ave, Brooklyn, NY 11203, USA","66-0-66-26 103rd St, Rego Park, NY 11374, USA","931-947 N Village Ave, Rockville Centre, NY 11570, USA","300-448 Beach 19th St, Far Rockaway, NY 11691, USA"],"origin_addresses":["566 Vermont St, Brooklyn, NY 11207, USA"],"rows":[{"elements":[{"distance":{"text":"6.7 mi","value":10754},"duration":{"text":"37 mins","value":2201},"status":"OK"},{"distance":{"text":"6.7 mi","value":10754},"duration":{"text":"37 mins","value":2201},"status":"OK"},{"distance":{"text":"6.7 mi","value":10754},"duration":{"text":"37 mins","value":2201},"status":"OK"},{"distance":{"text":"6.7 mi","value":10754},"duration":{"text":"37 mins","value":2201},"status":"OK"},{"distance":{"text":"6.7 mi","value":10754},"duration":{"text":"37 mins","value":2201},"status":"OK"},{"distance":{"text":"6.7 mi","value":10754},"duration":{"text":"37 mins","value":2201},"status":"OK"},{"distance":{"text":"3.4 mi","value":5426},"duration":{"text":"19 mins","value":1136},"status":"OK"},{"distance":{"text":"8.5 mi","value":13740},"duration":{"text":"23 mins","value":1399},"status":"OK"},{"distance":{"text":"15.9 mi","value":25546},"duration":{"text":"29 mins","value":1755},"status":"OK"},{"distance":{"text":"13.2 mi","value":21296},"duration":{"text":"34 mins","value":2040},"status":"OK"},{"distance":{"text":"3.4 mi","value":5426},"duration":{"text":"19 mins","value":1136},"status":"OK"},{"distance":{"text":"8.5 mi","value":13740},"duration":{"text":"23 mins","value":1399},"status":"OK"},{"distance":{"text":"15.9 mi","value":25546},"duration":{"text":"29 mins","value":1755},"status":"OK"},{"distance":{"text":"13.2 mi","value":21296},"duration":{"text":"34 mins","value":2040},"status":"OK"}]}],"status":"OK"}}

 

And with a little work, this can also be invoked as an SQL function:

 

Row count: 1 Performance: 0.398 seconds  54 global references 2349 lines executed 0 disk read latency (ms)  Cached Query: %sqlcq.pSYS.cls4  Last update: 2018-01-30 07:09:30.073

Print

 
Expression_1
[{"httpStatus":"200","message":"HTTP/1.1 200 OK","content":{"destination_addresses":["67-89 Pacific St, Brooklyn, NY 11201, USA","67-89 Pacific St, Brooklyn, NY 11201, USA","67-89 Pacific St, Brooklyn, NY 11201, USA","67-89 Pacific St, Brooklyn, NY 11201, USA","67-89 Pacific St, Brooklyn, NY 11201, USA","67-89 Pacific St, Brooklyn, NY 11201, USA","585 Schenectady Ave, Brooklyn, NY 11203, USA","66-0-66-26 103rd St, Rego Park, NY 11374, USA","931-947 N Village Ave, Rockville Centre, NY 11570, USA","300-448 Beach 19th St, Far Rockaway, NY 11691, USA","585 Schenectady Ave, Brooklyn, NY 11203, USA","66-0-66-26 103rd St, Rego Park, NY 11374, USA","931-947 N Village Ave, Rockville Centre, NY 11570, USA","300-448 Beach 19th St, Far Rockaway, NY 11691, USA"],"origin_addresses":["566 Vermont St, Brooklyn, NY 11207, USA"],"rows":[{"elements":[{"distance":{"text":"6.7 mi","value":10754},"duration":{"text":"37 mins","value":2201},"status":"OK"},{"distance":{"text":"6.7 mi","value":10754},"duration":{"text":"37 mins","value":2201},"status":"OK"},{"distance":{"text":"6.7 mi","value":10754},"duration":{"text":"37 mins","value":2201},"status":"OK"},{"distance":{"text":"6.7 mi","value":10754},"duration":{"text":"37 mins","value":2201},"status":"OK"},{"distance":{"text":"6.7 mi","value":10754},"duration":{"text":"37 mins","value":2201},"status":"OK"},{"distance":{"text":"6.7 mi","value":10754},"duration":{"text":"37 mins","value":2201},"status":"OK"},{"distance":{"text":"3.4 mi","value":5426},"duration":{"text":"19 mins","value":1136},"status":"OK"},{"distance":{"text":"8.5 mi","value":13740},"duration":{"text":"23 mins","value":1399},"status":"OK"},{"distance":{"text":"15.9 mi","value":25546},"duration":{"text":"29 mins","value":1755},"status":"OK"},{"distance":{"text":"13.2 mi","value":21296},"duration":{"text":"34 mins","value":2040},"status":"OK"},{"distance":{"text":"3.4 mi","value":5426},"duration":{"text":"19 mins","value":1136},"status":"OK"},{"distance":{"text":"8.5 mi","value":13740},"duration":{"text":"23 mins","value":1399},"status":"OK"},{"distance":{"text":"15.9 mi","value":25546},"duration":{"text":"29 mins","value":1755},"status":"OK"},{"distance":{"text":"13.2 mi","value":21296},"duration":{"text":"34 mins","value":2040},"status":"OK"}]}],"status":"OK"}}]

1 row(s) affected

That phrase is not a reference to a special type of class, instead it refers to the reason the class is added to the set of classes to be compiled. For example, if you compile Sample.Employee it becomes a "direct class" in the set of classes to be compiled. The compiler evalutes each class in the set of classes to produce an "expanded set of classes". In this case, Sample.Person would be included as an "expanded class". HTH.

Renaming a persistent class is complicated a few things:

  • Renaming the globals used by the extent of the class. There is no requirement that you change the global names. If you do not rename the globals and the extent is of type %Library.CacheStorage and MANAGEDEXTENT is true then we will have registered the globals as being "used by" the old class. An attempt to compile the new class could produce an error in that case. This is easily avoided - refer to ##class(%ExtentMgr.Util).DeleteExtentDefinition().
  • Subextents and %%CLASSNAME, if your class is not final and subclasses do exist then instances of the subclasses will have a non-null %%CLASSNAME value containing references to old class names. This value is not only in the data global but it will also be present in index globals. Even if you do not rename the globals you will have to update these values. The simplest way to deal with the values in the index globals is to rebuild the indices of the new class.
  • The structure of the global nodes as defined by the DATA members in the STORAGE definition. If you retain the previous DATA members from the old class then you will not have to alter the global nodes and the merge command can be used.
  • If PARENT/CHILDREN relationships exist and hierarchical storage is used then the children classes will have to be refactored as well.
  • All properties whose type class is a renamed class will have to be updated. If OID format values are used then these OID values will have to be updated as well.
  • Streams are referenced by the container object and can be stored in that container as a simple ID, an OID or as a SOID (includes storage location). Any stream reference stored as an SOID (CLASSNAME=2) will have to be refactored. The default is CLASSNAME = 0. If CLASSNAME = 0 then a simple merge will work for the stream global, assuming no other classes that are not also refactored share the same stream global.

-Dan

To set a property whose type class is a serializable class (extends %Library.SwizzleObject - streams, serial classes, persistent classes) you need to call the property's SetObject or SetObjectId method if you wish to set the value to a serialized value (id value often). I think that in your example, tvar1 is holds an ID value of the referenced ZenCrm.Relationtypes class? If so, then this should work:

do RelationMatrix.RelationAIDSetObjectId(tvar)

The ID is assigned by %SaveData() and %OnBeforeSave is invoked before %SaveData is called. For a new object the ID value will not be reliable until after %SaveData has returned. I do not know whether or not this is documented explicitly.

I defined this simple class:

 Class Community.IdBeforeSave Extends (%Persistent, %Populate)
{

Parameter USEEXTENTSET = 1;

Parameter DEFAULTGLOBAL = "^C.I";

Property Name As %String;

Property DOB As %Date;

Property SSN As %String;

/// This callback method is invoked by the <METHOD>%Save</METHOD> method to 
/// provide notification that the object is being saved. It is called after 
/// the object's data has been successfully written to disk.
/// 
/// <P><VAR>insert</VAR> will be set to 1 if this object is being saved for the first time.
/// 
/// <P>If this method returns an error then the call to <METHOD>%Save</METHOD> will fail.
Method %OnAfterSave(insert As %Boolean) As %Status [ Private, ServerOnly = 1 ]
{
set id = ..%Id()
set ^C.S($increment(^C.S)) = "OnAfterSave, insert = "_$select(insert:"'true'",1:"'false'") _ ", ID = " _ $select(id'="":id,1:"<null>")
Quit $$$OK
}

/// This callback method is invoked by the <METHOD>%Save</METHOD> method to 
/// provide notification that the object is being saved. It is called before 
/// any data is written to disk.
/// 
/// <P><VAR>insert</VAR> will be set to 1 if this object is being saved for the first time.
/// 
/// <P>If this method returns an error then the call to <METHOD>%Save</METHOD> will fail.
Method %OnBeforeSave(insert As %Boolean) As %Status [ Private, ServerOnly = 1 ]
{
set id = ..%Id()
set ^C.S($increment(^C.S)) = "OnBeforeSave, insert = "_$select(insert:"'true'",1:"'false'") _ ", ID = " _ $select(id'="":id,1:"<null>")
Quit $$$OK
}

and then populated it with 5 objects. This is the result:

LATEST:USER>zw ^C.S

^C.S=10

^C.S(1)="OnBeforeSave, insert = 'true', ID = <null>"

^C.S(2)="OnAfterSave, insert = 'true', ID = 1"

^C.S(3)="OnBeforeSave, insert = 'true', ID = <null>"

^C.S(4)="OnAfterSave, insert = 'true', ID = 2"

^C.S(5)="OnBeforeSave, insert = 'true', ID = <null>"

^C.S(6)="OnAfterSave, insert = 'true', ID = 3"

^C.S(7)="OnBeforeSave, insert = 'true', ID = <null>"

^C.S(8)="OnAfterSave, insert = 'true', ID = 4"

^C.S(9)="OnBeforeSave, insert = 'true', ID = <null>"

^C.S(10)="OnAfterSave, insert = 'true', ID = 5"

HTH,

Dan

Yes, this should work but you are missing a step - you must execute the statement. Dynamic SQL allows for a statement to be prepared once and executed many times. A prepared statement is executed, returning a statement result. You can provide different parameter values for each execution.

set result = tStatement.%Execute()

Alternatively, you can do this in a single comment -

set result = $system.SQL.Execute("UPDATE table Set Status = 'Completed' WHERE ID in (1,2,3,4)")

-Dan

This should work. I assume the code you included resides in a routine named 'XJSON' and in a function named 'fromFile'? I can guess that the class referenced is %DynamicAbstractObject and that class should certainly exist in versions 2016.2 and later. In version 2016.1, this class was known as %Library.AbstractObject.

In 2016.1, the JSON syntax was much different. The correct expression for that version is:

 set obj = [].$fromJSON(stream)

Errors can be encountered during the normal execution of any code and the cause of those errors is not always known or predictable. Caché has three primary error reporting mechanisms two are passive, meaning the user code is responsible for checking some error indicator and one is active, meaning the error triggers a change in the code path. The passive error reporting, %Status and SQLCODE, both require the user code to check for error conditions after executing some code. The code author must know before hand that these error indicators can be changed by the code that is being executed.

The third mechanism is exceptions. ($ZE with ZTRAP, etc. exist of course). With exceptions, the user code only needs to know that an exception could be thrown and to properly structure the code to deal with that possibility. If Caché user code is not already properly structured then $ZTRAP errors can cause problems so structuring code to deal with exceptions should not be considered a hardship.

The advantage of exceptions over the two passive types is that all three types of errors - %Status, SQLCODE, and exception - can be integrated into a single error handler. The passive checks can be used to construct exceptions using exception class constructors - CreateFromStatus and CreateFromSQLCODE - which can then be thrown. The error handling code lives in the CATCH block and can use the exception instance without further need to parser of otherwise process the exception in order to identify the type of exception, the exact error code and name, error context values, and so on. There is no need to litter your code with a lot of error handling code, detecting different types of errors, formatting, parsing, and so on. Just do it all in one place.

There are also exception methods to cast an exception as %Status or SQLCODE error values should there be a contractual obligation to report errors using one of the passive methods.

Another advantage to using exceptions is that there is virtually no runtime overhead. Also, unless you must return a %Status value (it happens), your code is free to return a useful value from a function, improving code readability and efficiency.

Please keep in mind that you are not supposed to be checking the local variable, SQLCODE. You should be checking the %SQLCODE property of either the prepared statement or the %SQL.StatementResult instance returned by %Execute().

And, the undocumented but fully supported methods of prepare() and execute() do throw exceptions now - no status code unless you want a status code. Just catch any thrown exception and process it as you wish.

I see two primary advantages of using exceptions over other error handling mechanisms. First is that exceptions can integrate all of the other error reporting mechanisms, allowing for consolidation of error handling code as well as the ability to report errors reported by one mechanism as an error using a different mechanism - %Status reported as SQLCODE and so on. The second is performance. Exceptions, using try/catch, are basically zero-cost for success, overhead encountered only when an exception is actually thrown. 

An additional advantage that is more subjective is code "cleanliness". Code written using try/catch/throw doesn't have to continually check for errors unless there functions not using some other error protocol are referenced.

To your question, I do not always add try/catch to a method if the catch {} block simply re-throws the exception. I only catch an exception if I need to do something to the exception before re-throwing it (or not throwing it at all) to the caller.

-Dan

 

PS: 

There is a macro, $$$THROWONERROR, that helps clean up calls to functions that return %Status. This macro is a convenient way to replace this pattern:

    set status = ..StatusReturningMethod()
    ​if $$$ISERR(status) { throw ##class(%Exception.StatusException).CreateFromStatus(status) }

with

    $$$THROWONERROR(status,..StatusReturningMethod())

If the status is not returned by the code, perhaps it is returned as a by-reference parameter value, then there is another macro that can help with the throw:

     $$$ThrowStatus(status)

Here are two snippets. The first is as close as I can come to Amir's original example and the second is a more radical version that embraces try/catch and exceptions.

 

 /// Always return a %Status
ClassMethod SomeMethod2() As %Status
{
    Set tSC = $System.Status.OK()
    Try
    {
        Set oRS = ##class(%SQL.Statement).%New()
        Set tSC = oRS.%Prepare("Select Name,DOB,Home_City from Sample.Person where Name %STARTSWITH ?")
        Quit:$System.Status.IsError(tSC)
        Set result = oRS.%Execute("A")
        if result.%SQLCODE '< 0 {
            While result.%Next()
            {
                //Do something...
            }
else {
            throw ##class(%Exception.SQL).CreateFromSQLCODE(result.%SQLCODE,result.%Message)
        }
    }
    Catch (oException)
    {
        Set tSC = oException.AsStatus()
    }
    Quit tSC
}

Snippet #2 - embrace exceptions

 /// Always throw an exception, return something useful to the caller
ClassMethod SomeMethod3() as %String
{
    try {
        set oRS = ##class(%SQL.Statement).%New()
        do oRS.prepare("Select Name,DOB,Home_City from Sample.Person where Name %STARTSWITH ?")
        set result = oRS.execute("A")
        while result.%Next() {
            //Do something...
        }
         if result.%SQLCODE < 0 {
            throw ##class(%Exception.SQL).CreateFromSQLCODE(result.%SQLCODE,result.%Message)
        }
        set response = "something useful"
    } catch (oException) {
            // process the exception - perhaps eat it and return or re-throw
            // often, we can place error/exception logging code here
        throw oException
    }
    return response
}

Of course, it would be nice if there were a "next()" method that throws an exception. Even the prepare() and execute() are not "officially" documented.

 

HTH,

Dan

%Library.ResultSet combines the statement (prepared) and the result into a single instance. You must consume the entire result - or discard it - before you can execute the prepared statement again. %Library.ResultSet also copies the data from the underlying embedded SQL memory into the result set object - perhaps more than once. %SQL.Statement is the prepared statement and it can be executed as many times as you wish, each producing an independent result. The SQL statement result shares memory with SQL SELECT statements so data does not have to be copied in most cases.

%SQL.Statement and %SQL.StatementResult do provide status values where appropriate. However, the convention of "always return a %Status value" destroys our ability to implement true functions that return a usable return value. With the convention of always returning a %Status value, the error case is the primary check. With exceptions, errors become, well, the exception and code can be written in a more direct manner. Most modern programming languages use try/catch.

Since you like macros, you might investigate $$$THROWONERROR. This macro allows the COS programmer to combine %Status values and exceptions using try/catch, writing the error handling only once - in a CATCH block.

For many simple statements and common operations, they will perform nearly the same. Both employ a generated code container with an embedded SQL statement that is compiled using the same SQL query processor. It is the interface to the compiled embedded SQL query where we see differences. The new(er - implemented several years ago) dynamic SQL uses objects to scope versions, you can have multiple instances of the same query open at the same time, you can return multiple result sets from a stored procedure using CALL, you can retrieve output-directed parameters from a CALL-ed procedure, you can execute DDL statements, and so on. With %ObjectSelectMode active, you can retrieve columns that are directly swizzled, enabling access to in-memory versions of objects that may not have been saved to disk. There are many reasons to use %SQL.Statement.