That's perfect to diagnosticate which infos I've available. I didn't know about that %Display method on metadata.
But ultimately I need this info for internal management.
- Log in to post comments
That's perfect to diagnosticate which infos I've available. I didn't know about that %Display method on metadata.
But ultimately I need this info for internal management.
Nice approach.
I got almost everything I need, except the property name. When the column is not aliased I can take it's name normally from label and colName.
However it breaks when SQL column is aliased, both colName and label refers to the same aliased column.
By the way, you can get it using:
resultset.%GetMetadata().columns.GetAt(index).colName // and label.
Notice that columns must be an instance of %ResultSet.MD.Column.
Dammit, I would downvote myself if I could. XD
Greetings Skip,
Just so you know. Unless you aren't using a version that already supports JSON, I can say that %DynamicAbstractObject's %FromJSON and %ToJSON is from far the fastest parsing/serializing implementation. It's about to 4x ~ 8x faster than my lib.
Otherwise I see no problem on open sourcing this library. But before I do that, I need to remove business related implementations.
Check my project. This might give you some help planning how to mimic package-to-folder exportation.
https://github.com/rfns/port
Now this is just an idea: I think you should make an API for serializing the JSON available. I mean, not only embed the serializer by extending the target class but also providing a way to serialize without extensions.
There might have cases where the user might need to use the same class for other JSON structures and with the current implementation he could be locked to it since he can't change parameter values on-the-fly.
However that implies on implementing a selector system to apply the same effect like each parameter does. So I don't think it's something for now, but you might want to consider it.
That's what I though, but it seemed slow.
Isn't there anyway to move the stream cursor back without erasing the existing data?
And if not, couldn't that be done using a device directly?
I develop using a mix of Caché Studio with Visual Studio Code.
I use Visual Studio Code for dealing with front-end code, while using Caché Studio for back-end.
I don't use Caché Studio to edit static files.
I'm actually doing experiments using my Port library for managing export/import operations.
About how I keep the server code close to it's client counterpart is quite simple. By default Port exports project following the template /CacheProjects/{NAMESPACE}/{PROJECT}, so instead of depending on it, I overwrite that path to /my-repo/server.
From this point exported files will follow:
/my-repo/server/cls/My/Class.cls
/my-repo/server/cls/Another/Deep/Package/Whatever.cls
/my-repo/server/int/myroutine.int
/my-repo/server/mac/myroutine.mac
/my-repo/server/dfi/mydef.dfi
/my-repo/server/int/myinclude.inc
And so on, for every recognized Caché file format.
Now notice that I didn't said anything about static files. That's where a module bundler like Webpack is used to orchestrate the client-side workflow.
Now Caché only needs to send readable data back to the SPA (preferably JSON using %CSP.REST).
When the project repo reaches a milestone. I build a release to actually export the files to the path, like this:
/my-repo/server/web/app/bundle-[chunkhash].js
/my-repo/server/web/app/bundle-[chunkhash].css
Since [chunkhash] is unique per build, the consumer shouldn't have any issues with browser cache.
Now there's an issue: the bundled files still aren't inside the CSP folder, so I need to import the project back to Studio using Port.
Files are imported using UDL instead of XML. But Port always keep a project XML up-to-date along with the UDL code.
As you can see I can work with Caché code AND Client-code, alternating between both editors, thus keeping their own development scope, even though their code remain inside the same repo.
Is your class located within the User package? Ex: User.Location?
If you REALLY want to use _ then at least wrap the alias inside double quotes.
Here is the procedure sample:
http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY…
ClassMethod CalcAvgScore(firstname As %String,lastname As %String) [sqlproc]
{
New SQLCODE,%ROWID
&sql(UPDATE students SET avgscore =
(SELECT AVG(sc.score)
FROM scores sc, students st
WHERE sc.student_id=st.student_id
AND st.lastname=:lastname
AND st.firstname=:firstname)
WHERE students.lastname=:lastname
AND students.firstname=:firstname)
IF ($GET(%sqlcontext)'= "") {
SET %sqlcontext.%SQLCODE = SQLCODE
SET %sqlcontext.%ROWCOUNT = %ROWCOUNT
}
QUIT
}
Instead of using SQL to define PROCEDURES, even though you can, it's easier to create one using your own class. Just declare it as [ SqlProc] and it'll be available to use inside SQL. You can use that way to define a SQL function as well.
You can't use positive numbers because most of them are already defined inside %occErrors.inc and there's no way to inform which domain should the Error() use.
Yeah. I actually prefer using $$$GeneralError than creating my own error and having it display with <domain> before it's message. I think that" <domain>-errorcode: Message" pollutes the message for the end-user.
I did a test using the tutorial you posted (which is great by the way!), but the way the Status API display the error by default kind made me fallback to 5001, which is cleaner.
Though, I do think Google I/O and Apple are also streamed over YouTube.
Can't say much about Oracle...
As long as there's an abstract API for parsing, lexing, transpiling and serializing. It would be possible to even port any FP or FRP language to Caché.
Since you demo'ed JavaScript, it seems programming on a functional way could be possible if we could simply pass methods as parameters.
Well, that's actually the core rule for a language that supports functional paradigms.
Since there's no current support for such paradigm. Maybe we could wrap it using indirections or xecutes?
set array = []
do a.%Push({ "value": "some value to be replaced" })
set result = ##class(FP.Functor).%New()From(array).Map($this, "...ValueWithIndex", scopeParam)
Method ValueWithIndex(item As %DynamicObject, index As %Integer, scopeParams... As %String)
{
// Second core rule: always keep it pure. Map should always Clone the item, which could be implicit for Map.
// But for this case I'll demonstrate it manually.
set clonedItem = item.Clone() // Or %ConstructClone if possible.
set clonedItem .%Set("value", "modified with "_index)
return clonedItem
}
Please note that this still doesn't provide the possibility to use high-order functions. The closest we could have I think is embedding subroutines within your context method. Which could also be reproduced as:
// Can also be Method.
ClassMethod YourContextMethod() As %String
{
set scopeParam = "blahblahblah"
// Now assume we're using %ZEN.proxyObject. Omitted for brevity.
set result = ##class(FP.Functor).From(array).Map("$$HOMapWithScopeParam", scopeParam)
HOMapWithScopeParam(result, item, value, scopeParam...)
// Now Implicitly cloned into result param.
set result.value = scopeParam(0) // Could be improved.
quit result
}
Nope, I forgot that procedures are exclusive for the subroutine that's defining them.
I did a little experiment and here's the result:
ClassMethod testing(item)
{
set array = ##class(%ListOfObjects).%New()
for i=1:1:10 {
set proxy = ##class(%ZEN.proxyObject).%New()
set proxy.value = i
do array.Insert(proxy)
}
set DoubleItemValueSumTwo = $classname()_":DoubleItemValueSumTwo"
set Odds = $classname()_":Odds"
set BiggerThanFive = $classname()_":BiggerThanFive"
set result = ##class(FP.Functor).From(array).Map(DoubleItemValueSumTwo).Filter(Odds).Every(BiggerThanFive).Result()
quit result
}
ClassMethod DoubleItemValueSumTwo(
item As %ZEN.proxyObject,
i)
{
set item.value = (item.value * 2) + $random(2)
quit item
}
ClassMethod Odds(
item As %ZEN.proxyObject,
i)
{
quit (item.value # 2 '= 0)
}
ClassMethod BiggerThanFive(
item As %ZEN.proxyObject,
i As %Integer)
{
quit item.value > 5
}
There is space for a lot of improvements I think. It's not exactly what you would call a performatic implementation. But that's a beginning.
I just posted my implementation on Git for analysis.
https://github.com/rfns/cache-fp-poc
Since you didn't specified which service you're using it's hard to simulate your doubt.
However I did a quick research and noticed that 'padding' actually refers to OAEP, this could be the method you want to use to encrypt. I'm not sure though.
/// This method performs RSA encryption as specified in
/// PKCS #1 v2.1: RSA Cryptography Specifications, section 7 Encryption Schemes.
/// <br><br>
/// Input parameters:
/// <br><br>
/// Plaintext - Data to be encrypted.
/// <br><br>
/// Certificate - An X.509 certificate containing the RSA public key to be used for encryption,
/// in PEM encoded or binary DER format.
/// Note that the length of the plaintext can not be greater than the length of the modulus of
/// the RSA public key contained in the certificate minus 42 bytes.
/// <br><br>
/// CAfile - The name of a file containing trusted Certificate Authority X.509 Certificates in PEM-encoded format, one of which was
/// used to sign the Certificate (optional).
/// <br><br>
/// CRLfile - The name of a file containing X.509 Certificate Revocation Lists in PEM-encoded format that should be checked
/// to verify the status of the Certificate (optional).
/// <br><br>
/// Encoding - PKCS #1 v2.1 encoding method (optional):<br>
/// 1 = OAEP (default)<br>
/// 2 = PKCS1-v1_5<br>
/// <br><br>
/// Return value: Ciphertext.
ClassMethod RSAEncrypt(
Plaintext As %String,
Certificate As %String,
CAfile As %String,
CRLfile As %String,
Encoding As %Integer) As %String
{
}
On %CSP.Session
/// Specifies the timeout value for the session in seconds.
/// <P>If no user requests are received within the specified time period,
/// then the session will end. The default value comes from the CSP application
/// setting for the application that the session starts in which is set in the
/// Cache configuration manager, this is often 900 seconds or 15 minutes.
/// Note that if you start a session in one applicaiton and move to another application
/// the AppTimeout will not be changed to the new applications timeout value, if you wish
/// to modify this when the application changes you can use the session events 'OnApplicationChange'
/// method.
/// <P>For no timeout, set this property to 0.
Property AppTimeout As %Integer [ InitialExpression = 900 ];
You can also specify the timeout for your App as a whole:
<your server ip:57772>/csp/sys/sec/%25CSP.UI.Portal.Applications.Web.zen?PID=%2Fcsp%2Fuser
Oh crap! I knew I had seen it somewhere.
EDIT: Ahh, btw... it's Util, not Utils.
What about this?

Awesome! Can you put it on Github?
You can use value positioning to match their $list index according to their property definition index. But you will need to describe each property to detect their types and if they are instantiable. In case they do, you can use the value to open the instance and the property type. E.g:
set properties = ##class(%Dictionary.CompiledClass).%OpenId("SQLUser.Table").Properties
for i=1:1:propeties.Count() {
set listValue = $lg(SQLUser^TableD(1) , i)
set property = propeties.GetAt(i)
// if property.Type inherits from %RegisteredObject
set value = $System.OBJ.OpenId(property.Type, listValue)
// else
set value = listValue
}
If you don't want to open the class descriptor instance you can also use macros from %occReference.inc. But you will have to deal with $order.
My suggestion takes the longest path. You will need a method that supports each property type behavior, like that lb(,,"bla","20050502123400").
But why can't you use an instance? Is it because the structure is too old and there isn't any %Persistent class to represent it?
/// Returns a list of the Globals in a Cache NameSpace (used for GUI display)<br>
/// <br>
/// <b>Parameters:</b> <br>
/// NameSpace - a Cache namespace. Default is current namespace. <br>
/// Mask - a mask, or comma-separated list of masks, to select globals. Default is "*" for all.<br>
/// SystemGlobals - boolean flag to include system globals in the results. Default is "0".<br>
/// UnavailableDatabases - a returned local array of any databases not currently accessible, i.e. array(name)=status.<br>
/// Index - Internal use only.<br>
/// IgnoreHasData - For faster list of Globals set this to 1 and the HasData column will always be FALSE.<br>
/// Mapped - Return all mapped global nodes when set to 1, the default value of this parameter is 1.
/// <br>
/// Valid masks are as follows:
/// <br>
/// ABC* - All strings starting with ABC<br>
/// A:D - All strings between A and D<br>
/// A:D,Y* - All strings between A and D, and all strings starting with Y<br>
/// A:D,'C* - All strings between A and D, except those starting with C
Query NameSpaceList(
NameSpace As %String,
Mask As %String,
SystemGlobals As %Boolean,
ByRef UnavailableDatabases As %String,
Index As %Integer,
IgnoreHasData As %Boolean = 0,
Mapped As %Boolean = 1) As %Query(ROWSPEC = "Name:%String,Location:%String,ResourceName:%String,Permission:%String,Empty:%String,Keep:%String,Collation:%String,PointerBlock:%String,GrowthBlock:%String,HasData:%Boolean,Journal:%String,LockLocation:%String,HasSubscripts:%Boolean") [ SqlProc ]
{
}
set s = ##class(%SQL.Statement).%New()
do s.%PrepareClassQuery("%SYS.GlobalQuery", "NameSpaceList")
set r = s.%Execute("SAMPLE", "*")
set $namespace = "SAMPLE"
while r.%Next() { kill @r.%Get("Name") }
If you want to work on a project basis and versionate it, but still want to work with Studio, you can try using Port to test if it satisfies you. By default Port will export your project using UDL format and also a XML for backward compability.
If you want to export only files that ends with .csp, you can save your current project with another name and use the Remove classes, Remove routines. From the Source Control menu. Remember though, that Port is made to be ran locally instead of making your pc work like a thin client for Caché. That means your Source code should preferably be local and not remote.
Also, you're welcome to open issues. And, even though there's an alert about the nightly branch is pretty stable for now, since I have been working mostly on adding unit tests and fixing minor bugs.
P.S: Just to take a note. The Port repository is exported using itself.
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.
Great article right there. But hey @Sergey Kamenev, could you put a reference the other two parts that you made as well?
%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...)
}
Using indirection.
set global = "^G"
set value = @global
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).
I think what you want is:
Depends On and Compile After.
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.