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

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

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

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

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

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

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

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 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 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 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

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 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 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

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

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 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 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 28, 2017 go to post

set number = "1234"
write number?.N // 1, so it's integer.
set number = "44.2d3"
write number?.N // 0, so it's float, double or whatever data type with precison.
You can read more about Pattern Matching here.

Rubens Silva · Aug 1, 2017 go to post

Yeah, you can't upload photos directly to this forum and it is somewhat bad, even though there's a lot of image related configs that most won't even think about using...

The only way is hosting in the cloud.

Rubens Silva · Aug 2, 2017 go to post

It seems they have changed it to an image for newer releases. (Even Caché is now an image instead of pure CSS).
Well, I do agree with crediting IS, however it should be something like:
"Powered by InterSystems ENSEMBLE" like a single message, since the focus is to demonstrate your work, not theirs.

Rubens Silva · Aug 2, 2017 go to post

Updated to use %Stream.GlobalCharacter instead, thank you.
Using eol also didn't resolved I guess. Here's the feedback:

Port: Executing hook extension OnAfterSave from the class Port.HookTest ...
Port (OnAfterSave):
Port (OnAfterSave): First lineSecond line
Port (OnAfterSave): Third line
Port (OnAfterSave):
Port: OnAfterSave returned without errors.
ClassMethod OnAfterSave(
sourceControl As %Studio.Extension.Base = "",
InternalName,
Location,
Object) As %Status
{
  // ! skips the first line leaving it empty, actual message is pushed down to the second line.
  // should not even print it.
  write !, "First line"
  
  // First line is here, printed along with the second.
  write "Second line", !
  
  // Third is still third line. But ! writes another empty line.
  write "Third line", !
  return $$$OK
}
As you suggested, I changed it to use eol:
 if $isobject(content) {
    do content.OutputToDevice()
    do content.MoveTo(content.Size - 1)
    do content.ReadLine(, sc, .isEOL)
  }
     
  if 'isEOL write !

Rubens Silva · Aug 2, 2017 go to post

%Status always returned $$$OK and putting -3 instead -1 provided me the same feedback. The last line is still empty.
Maybe it's something regarding the redirection routines. I can't really see that:
wnl() do output($char(13,10)_$$$FormatText("Port (%1): ", hookName)) quit
As correct, since wnl doesn't even take a parameter  where I could decide what to do.

Rubens Silva · Aug 2, 2017 go to post

Indeed, those redirection routines aren't called anywhere. But they're redefined extensively around the Caché Class API.
As you said it's cryptic and powerful enough to dictate the buffer workflow even though you don't use it explicitly.
From my experiments, what I notice is:
wstr handles every write

wnl handles every !

wchr handles every single char

wtab handles tab usage
wff  handles every form feed
As used here.