Rubens Silva · Jul 28, 2017 go to post

I agree with @Amir Samary. Even though by using inheritance you're allowed to use method generators, it's still against against the SIngle Responsibility Principle, since the code is binding directly to the persistence (which it's SRP should be to work as a storage representation).
Binding another responsibility to it could mean  that sometime in the future the persistence would need to be rewritten to match the newer requirements, thus risking to break current applications and elevating technical debt.
So my golden rule is: unless you're 120% sure that the result generated by the binded class won't change, or you're 120% sure that it still submits to the SRP, only then you can "adapt" the persistent class.
But before that, I would recommend (priority order):
1 - Use an utility method that takes the binding class name and returns the JSON or vice-versa. Can take an optional configuration object or flags to overwrite and determine the serialization behavior for the current case.

 

2 - Create a mirror class that extends exclusively from the Adapter.

 

This keeps the persistent class clean and still allows it to be serialized.
Also, there's a method for doing what I suggest on 1, I think it's been discussed here already.

Rubens Silva · Jul 27, 2017 go to post

I think that the default convention for REST usage has been exploited too much already, to the point that most would prefer to have an extra feature than follow it strictly. Even Google and Facebook do it: though they use REST most of their services, they also allow to pass query parameters.
I think it's due to their performance or readability policies.
EDIT:
By allowing query parameters, you can also prevent another rule break: 
Let's say I need to pass a lot of parameters to FETCH data from a resource. As REST demand, you need to use GET verb to fetch data from the resource if you won't change it.
However if you can't use query parameters, you need to use POST or PUT to provide the payload (that modifies data) and this would break the CRUD rule as well.
This is where I would balance the rule breaks, that is, I can't really disregard breaking a rule that actually prevents me from breaking another that's even more explicit.

Rubens Silva · Jul 27, 2017 go to post

Yeah, I read that a few days ago. But I noticed it doesn't mention how to handle "?".
Hmm, I see... so according to your idea,  I basically have to fetch the parameters by using %request.Get.

Rubens Silva · Jul 24, 2017 go to post

You're right, %Dictionary.CompiledClass actually overwrites the current %Persistent implementation for %Exists (which is called by %ExistsId).

Rubens Silva · Jul 24, 2017 go to post

That is because the  CLS Package is used internally.
InterSystems uses it to verify if the method is defined or not without throwing exception.
This is probably faster than %ExistsId, because %ExistsId tries to open the instance beforehand instead.

Rubens Silva · Jul 24, 2017 go to post

Though it works, it's not literal: another programmer could ask himself why you tested the method %New, thus requiring to add a comment for explaining it. However if you use ExistsId, you're literally saying about testing if the class exists.
I know, it's just a detail, but still contributes for clean code directives.

Rubens Silva · Jul 20, 2017 go to post

Can you post the code that's generating this error?
I noticed that the error is coming from a method called File inside your class User.zKQRest.

Rubens Silva · Jul 20, 2017 go to post

Yes, you must use ... to inform the method that you're are using the format compatible with variable arity.

When using . instead, the compiler assumes it should index from the subscript that you provided. It's not a bug, but a unexpected behavior caused by your input as the compiler modifies the way it treats the parameter if you put ... on the declaration.


So yep, you should always use ... for passing and receiving parameters with variable arity, unless you know exactly which parameters and their passing index, you can express it using  a more "literal" format: as you have noticed, right from start I defined a param variable to hold all my values, this is really useful for populating the arity from a loop.
But what if already know what to pass? You could also call the method as:
$classmethod($this, "Sum", 1,2,3,4,5,6) // up to 255 parameters.

Also, yes, it works for %ResultSet.Execute, by the way here is the method signature:
Method Execute(args...) As %Status [ PublicList = (qHandle, args) ]

Rubens Silva · Jul 20, 2017 go to post

Are you sure?

 

ClassMethod TestAritySum()
{
  set args = 3
  set args(1) = 10
  set args(2) = 20
  set args(3) = 30
  
  quit $classmethod($this, "Sum", args...)
}
ClassMethod Sum(n... As %Integer)
{
  set sum = 0
  for i=1:1:{
    set sum = sum + n(i)
  }
  quit sum
}

Rubens Silva · Jul 19, 2017 go to post

Not exactly, this following syntax is weird, but works:
try typing instance."my_property", you can even create properties and methods with "".
Extremely useful if you are working with snake_case.
Here's the proof:
USER>zn "samples"
 
SAMPLES>set r = ##class(%SQL.Statement).%ExecDirect(,"SELECT TOP 1 ID AS ""my_id"" FROM SAMPLE.PERSON")
 
SAMPLES>w r.%Next()
1
SAMPLES>w r."my_id"
1

Rubens Silva · Jul 18, 2017 go to post

Now I can assume that you merged your "returnParams" subscript into a variable called filterParams.

 

Doesn't look wrong for me, just wondering about that SIZE. Try removing it and see if it affects your results.
Also check if you modified the display mode. Along with that status variable to see if some error happened.
P.S.: It's becoming hard to type here :x

Rubens Silva · Jul 18, 2017 go to post

Uhmm... what could be causing this issue is that embedded list. Your %INLIST reads one list, but the result is another list.
Or maybe I'm still missing some piece of code to understand better.
Anyway according to the documentation:
 

SET states=$LISTBUILD("VT","NH","ME")
SET myquery = "SELECT Name,Home_State FROM Sample.Person WHERE Home_State %INLIST ?"
SET tStatement = ##class(%SQL.Statement).%New()
SET qStatus = tStatement.%Prepare(myquery)
IF qStatus'=1 {WRITE "%Prepare failed:" DO $System.Status.DisplayError(qStatus) QUIT}
SET rset = tStatement.%Execute(states)

 

I assume it should be:
set params=N

set params(1)=$lb(1,2,3)

set params(2)=hypothetical param2

...

set params(N)=paramN
I can see that your returnParams is a list instead of the format that allows variable arity.

But like I said, I might be missing some piece of code to understand better.
However, since my suggestion is not a surefire tip and you're in a hurry I think you should opt-in for the fatest working solution and investigate the error cause when you have some time, just like you said.

Rubens Silva · Jul 18, 2017 go to post

Ahh, that's because you're using IN instead of %INLIST. IN doesn't expect a list, so throws an exception.

Rubens Silva · Jul 18, 2017 go to post

I'll assume that your 'where' variable is already your SQL query.

 

set params=1

set params(1)=$lb(,1,2,3,4,5,6,7)
"Set where(16)=InstancePrefix_"PayerName IN (?)""
 

do statement.%Prepare(.where)

set rows = s.%Execute(params...)

 

Remember that you still can concatenate SQL parts, because they aren't editable by the end-user, I just don't recommend concatenating values.

Rubens Silva · Jul 18, 2017 go to post

If you can't release memory by ajusting your own source code, then what you need is to do is expand it's size by using $zstorage or the configuration.
Also, is that a recursive call?

Rubens Silva · Jul 18, 2017 go to post

Makes sense, I think it's because OUTER JOIN lifts equivalence constraints when joining. 

Rubens Silva · Jul 18, 2017 go to post

If you mean testing using the SSL configuration in the Portal, when you click Test, it asks you for your server name and port. This follows the same pattern as %Net.HttpRequest, which means: don't provide the protocol http:// or https://, only your address: fantasyfootballnerd.com
For a use case, we have service hosted on AWS using HTTPS and it connects succesfully, but only if I strip the protocol from the address. Here's a feedback from our service when using the Test button (it's portuguese though ...)
Conexão SSL bem sucedida <- Indicates success on connecting.
Protocolo: TLSv1/SSLv3
Ciphersuite:ECDHE-RSA-AES128-GCM-SHA256
Peer:  <- No peer certification defined, so it's empty...
Requisição: GET / HTTP/1.0 <- Request done using HTTP 1.0 protocol 
Resposta: <- Response is an empty body.

Rubens Silva · Jul 18, 2017 go to post

HTTP Status 301 means Moved Permanently, which happens when you request a resource and the server redirects you to another (usually equivalent), since you said it's asking to use HTTPS, I suppose you haven't configured a SSL configuration on the Caché side.
Create a new SSL Configuration mark Enabled, select Type to Client, Peer certificate verification level to None, and Protocols to TLS.
Or simply fill the Name and Description, anything else is default... click Test to see if it works and Save it.
Now after instantiating the %Net.HttpRequest, you pass your instance.SSLConfiguration = "Your SSL Configuration Name" and instance.Https = 1.
This is the minimal you usually need to do to have a working SSL aware client.

Rubens Silva · Jul 18, 2017 go to post

%Execute and Execute as well support a variable arity of arguments. This can be reproduced as:
set args=2
set args(1)="first_value"

set args(2)="second_value"

 

do statement.%Execute(args...)
Notice that the variable itself contains the total of arguments while each subscript is sequential, this defines their position as arguments.
Methods that support a variable arity imply on using three rules:
1 - Argument variable must contain the arity size.

2 - Argument index starts from 1.
3 - Index must be sequential.
Method with a variable arity can also pass through their arguments to the next method like this:
ClassMethod First(params... As %String)
{
   do ..Second(params...)
}

Rubens Silva · Jul 18, 2017 go to post

I didn't know that Caché allowed to use JOINs without specifying FROM. That's really useful to know indeed.

Rubens Silva · Jul 18, 2017 go to post

Actually, the amount of placeholders inside the SQL is what dictates how many parameters should be read. SQL engines or ORMs that support queries with placeholders usually escape the input value already to prevent attacks like SQL injection. And that is why you should never use _concatenation_ with SQL strings. Because the engine cannot sanitize what is not an explicit value.
As you said the parameter is a %List instead of a multidimensional you don't need to do anything regarding the input, just pass it and read it using %INLIST.
And surely, if you can provide a fixed number of parameters, this should always be your first option.

Rubens Silva · Jul 18, 2017 go to post

The main downside of using "?" instead of :param is that if you have multiple ocurrences from the same column you have to repeat the parameter. That's why I would discard using dynamic queries if the SQL already covers all conditions to display the results and create it as class query (possibly your first example).

Rubens Silva · Jul 17, 2017 go to post

You can use $query as Fabian suggested along with indirection this will allow you to traverse the global without the need of creating a recursive call.
You can use $qlength along with $query to discover how deep (how many subscripts) you are in the global.
You can use $qsubscript along with an index to fetch it's name.
There's a sample within the $query documentation.
Also, $order uses the lastest subscript you provided to find the next one. So, no, you don't need to know how many subscripts you have. But you need to know when to stop.
I suggest you to check [@Sergey Kamenev]'s articles for a deep understanding about globals.