検索

質問
· 2019年2月12日

Which is the benefit of using cachetemp to store temp globals? Is it possible to use them if we plan to use ECP?

Suppose we need to store millions of values temporarily, that means, we don't care about them if we lose them but our application use them to get realtime information. Should I use Cachetemp or whatever other DB without journaling enabled? If answer is Cachetemp, shouldn't be a problem if we decide to scale using App Server + ECP? I'm not sure what would happen with the app logic in such architecture as I guess I couldn't map and share cachetemp...

Any idea/suggestion?

5 Comments
ディスカッション (5)1
続けるにはログインするか新規登録を行ってください
記事
· 2019年2月8日 2m read

Client Websockets based CSP

The Caché / Ensemble standard distribution contains in namespace SAMPLES
a nice example of a CSP page consuming WebService as a Client.
I have modified it not only to display the replies but to feed them back into a Global.
I used the classic Hyperevent to achieve this. The replies end up as a log in global^WSREPLY.
When there is no input anymore the page closes and goes away.

There are 2 versions with visible and hidden display during operation.
dc.WSCSP.reverseVerbose.cls and dc.WSCSP.reverseHidden.cls

The message to send is simply passed as a hash after the CSP_URL. (Mind URL encoding!)

You can launch the page using a command pipe or $zf(-1,...) or similar for newer versions
directly out of your application or from the command line.

 /// find your browser location
 set browser="""C:\Program Files (x86)\Google\Chrome\Application\chrome.exe"" "
 /// don't forget the # at the end
 set page="http://localhost:57772/csp/"_$namepace_"/dc.WSCSP.reverseVerbose.cls#"
 /// whatever you send to the server
 set msg="hello ALL"
 /// a CPIPE device
 set dev="|CPIPE|22"

// either
do $zf(-1,browser_page_msg)

// or
open dev:browser_page_msg:0 write $t close dev

and what you get on server looks like this:


^WSREPLY(34)=$lb("2019-02-08 18:03:11","Welcome to Cache WebSocket. NameSpace: SAMPLES")
^WSREPLY(35)=$lb("2019-02-08 18:03:11","'hello ALL' (length=9) recieved on 08 Feb 2019 at 06:03:11PM NameSpace=SAMPLES")
^WSREPLY(36)=$lb("2019-02-08 18:03:21","Timeout after 10 seconds occurred on 08 Feb 2019 at 06:03:21PM")
^WSREPLY(37)=$lb("2019-02-08 18:03:31","Timeout after 10 seconds occurred on 08 Feb 2019 at 06:03:31PM")
^WSREPLY(38)=$lb("2019-02-08 18:03:41","Timeout after 10 seconds occurred on 08 Feb 2019 at 06:03:41PM")
^WSREPLY(39)=$lb("2019-02-08 18:03:41","exit")

This is just a starting point to be adapted to your individual needs.

With Docker it might be hard to start a browser. ZPM is preferred.

GitHub

ディスカッション (0)1
続けるにはログインするか新規登録を行ってください
記事
· 2019年2月8日 2m read

Client Websockets based on Node.js

It will demonstrate the wide range that is openend by making use
of the power embedded in Node.js and its adapter to Caché, Ensemble, Health,..*
Node / JavaScript have wide reputation to work as a WebSocket client.
By using the Caché adapter it becomes easy to control it and to consume the results as a
Client for WebSocket Servers and to collect the replies in Caché, Ensemble, ..

I used node-v6.16.0-x64.msi and cache610.node as cache.node

You provide a Global for input:

 ^WsockIn="wss://ws.postman-echo.com/raw"
 ^WsockIn(0)=6
 ^WsockIn(1)="Hello"
 ^WsockIn(2)="World !"
 ^WsockIn(3)="Robert"
 ^WsockIn(4)="is waiting"
 ^WsockIn(5)="for replies"
 ^WsockIn(6)="exit"

and with this echo server you get back a Global as output

 ^WsockOut(0)=6
 ^WsockOut(1)="Hello"
 ^WsockOut(2)="World !"
 ^WsockOut(3)="Robert"
 ^WsockOut(4)="is waiting"
 ^WsockOut(5)="for replies"
 ^WsockOut(6)="exit"

you may call it from with class code or routine or command line

  kill ^WsockOut
  do $zf(-1,"node ""C:\Program Files\nodejs\user\WsockDemo""")
  zwrite ^WsockOut

or in verbose mode

  SAMPLES>$node "C:\Program Files\nodejs\user\WsockDemo"

start
server: wss://ws.postman-echo.com/raw
Lines to process: 6

WebSocket Client Connected
client ready

Line: 1 text: Hello
Line: 2 text: World !
Line: 3 text: Robert
Line: 4 text: is waiting
Line: 5 text: for replies
Line: 6 text: exit
lines sent: 6

Received: 'Hello'
Received: 'World !'
Received: 'Robert'
Received: 'is waiting'
Received: 'for replies'
Received: 'exit'
replies received: 6

Client Closed
SAMPLES>

Using the WebSocket Server in namespace USER

ws://localhost:57772/csp/user/Web.SocketTest.cls

the replies look like this:

Received: 'Welcome to Cache WebSocket. NameSpace: SAMPLES'
Received: ''Hello' (length=5) recieved on 08 Feb 2019 at 02:57:08PM NameSpace=USER'
Received: ''World !' (length=7) recieved on 08 Feb 2019 at 02:57:08PM NameSpace=USER'
Received: ''Robert' (length=6) recieved on 08 Feb 2019 at 02:57:08PM NameSpace=USER'
Received: ''is waiting' (length=10) recieved on 08 Feb 2019 at 02:57:08PM NameSpace=USER'
Received: ''for replies' (length=11) recieved on 08 Feb 2019 at 02:57:08PM NameSpace=USER'
replies received:  6

Client Closed

.
. .
I have to admit this was my first piece of code to run in Node.js.

GitHub

ディスカッション (0)1
続けるにはログインするか新規登録を行ってください
質問
· 2019年2月1日

How to recalculate XSL pages from Portrait to Landscape

Hello all,

I have an XSL question and I wanted to see if someone within a community can help me out.  

To Summarize: I am working with XSL documents, single and multi-pages.  
Inside my style sheet XML document, which is supporting my pages, everything on the page, including a headers, footers and the items are calculated 
based on the portrait measurements. 
Pages come up on the screen as portrait, but sometimes, depending on dynamic data are wide, they do not print properly in portrait.

If I turn them to  print in Landscape, now the page fit fine, but the measurements which were calculated for portrait do not readjust and therefore I have issues, specially with footers, which are never at the bottom of the pages anymore.

Question: Is there a way to recalculate XSL pages and change them smoothly from Portrait to Landscape orientation when it's needed during the run or the other way around?
Secondly, is there a way I can specify the printing option -> Landscape from the start for some of my XSL pages, which do not fit well in portrait mode?. Basically I want to set the Landscaping printing mode as soon as I see that the data is too wide for portrait page and avoid the extra step of doing it by hand.

Thank you for your time,
Alex K

1 Comment
ディスカッション (1)1
続けるにはログインするか新規登録を行ってください
記事
· 2019年1月21日 10m read

Should you trust Codd or your objects?

Headache-free stored objects: a simple example of working with InterSystems Caché objects in ObjectScript and Python

Neuschwanstein Castle

Tabular data storages based on what is formally known as the relational data model will be celebrating their 50th anniversary in June 2020. Here is an official document – that very famous article.  Many thanks for it to Doctor Edgar Frank Codd. By the way, the relational data model is on the list of the most important global innovations of the past 100 years published by Forbes.

On the other hand, oddly enough, Codd viewed relational databases and SQL as a distorted implementation of his theory.  For general guidance, he created 12 rules that any relational database management system must comply with (there are actually 13 rules). Honestly speaking, there is zero DBMS's on the market that observes at least Rule 0. Therefore, no one can call their DBMS 100% relational :) If you know any exceptions, please let me know.

The relational data model is not very complex and is very well studied.  Maybe even overstudied. In the meantime, we'll be celebrating another anniversary in 2019 - exactly 10 years ago, when very first #NoSQL hashtag that later became known as "not only SQL" appeared on Twitter and started its rapid expansion into database development models.

Why such a lengthy introduction? Because the software development universe consists of data (and algorithms, of course), while the monopoly of the relational model leads, to put it mildly, to the divergence of the developer's consciousness. Because all the objects in the head of a developer (OOP is also global, right?)  – all these lists, queues, trees, heaps, dictionaries, threads, and so on and so forth – are not tables.

What if we go on and recall the storage architecture in modern DBMS's? Let's be honest, no one stores data in tables.  DBMS developers typically use variations of the B-tree (in PostgreSQL, for example) or, far less often, dictionary-based storage. On the other side of the barricade, developers using DBMS's for storing data do not work with tables either.  And it makes developers constantly bridge the semantic gap using a cumbersome intermediate data layer. And by doing so, cause an internal dichotomy, system-wide discomfort and debugging sleeplessness.

That is, briefly speaking, we have a contradiction here – we pack data into objects suitable for our purpose, and at the same time have to care about some tables when saving data.

Dead end? Nope :) And what about object-relational representation,  more commonly known as ORM? Let's leave this holy war to Egor Bugaenko & Co. And this whole last-century story, according to Uncle Bob, shouldn't be too much of a worry.

We should definitely mention that the "bag of bytes" (Robert Martin, Clean Architecture) can be serialized or dumped to a file or pushed to a suitable thread. First, it will immediately restrict us to a particular language. Second, we are going to focus on saving to a DBMS only for now.

All of these "bag of bytes" twists and turns have a pleasant exception - InterSystems Caché DBMS (now the InterSystems IRIS data platform as well). It may be the only DBMS in the world that doesn't hide the obvious from developers and takes it even further by saving them the trouble of thinking about "storing all this stuff the right way". Suffice it to say that the class extends the Persistent metaclass and it's in the bag - or, rather, in globals (don't confuse them with global variables!)

You can store all types of data, including character and binary streams. Here is the simplest example:

// a class for the object being saved with a single string parameter

// surprise: you can name objects with any string. We used quote marks in our example, but they are obviously not needed for identifiers without spaces

Class FW.Events Extends %Persistent {

    Property "My name" As %String;

}



// trying to run it through the terminal

// creating a new "clean" object

set haJS = ##class(FW.Events).%New()



// save it

write haJS.%Id()

What's truly remarkable is that you can access stored objects not just via ObjectScript that is native for Caché but can also retrieve and save them directly in your programs written in Python, Java, JavaScript, C++, C#, Perl. And even - oh my! :) -  retrieve information from these objects directly using SQL queries, as well as call your own methods. To be precise, methods in this case automatically (with a little help of SqlProc) turn into stored procedures. The Caché DBMS has all this magic under the hood.

How to get free test access to the InterSystems Caché DBMS?

It's 100% possible, no matter what some people say! :) Download and install the single-user, fully functional version of Caché by clicking on this link (requires free registration). MacOS, Windows, and Linux builds are available.

The most convenient way of working with ObjectScript code with direct access to the Caché DBMS server (and the InterSystems IRIS platform as well) is to use the Atelier IDE based on Eclipse. All download and installation instructions can be found here.

If it's more convenient for you, you can use the simple and comfortable Visual Studio Code in combination with the ObjectScript plugin developed and supported by the community.

A few practical examples now.  Let's try to create a couple of associated objects and work with them in ObjectScript and Python. Integration with other languages is implemented in a very similar manner. Python was chosen on the grounds of its "maximum kinship" to ObjectScript – both languages are script languages supporting OOP and lacking strong typing :)

To get some ideas, let's turn our sights to the popular "Framework Weekend" projects in Khabarovsk. The sample source code is available here: github.com/Hajsru/framework-weekend. Ours is provided in the text below.

An important detail of macOS users. When launching support modules for Python, don't forget that you need to specify the DYLD_LIBRARY_PATH path to the Caché installation folder. For instance, like that:

export DYLD_LIBRARY_PATH=/application/Cache/bin:$DYLD_LIBRARY_PATH

This is highlighted in the documentation.

Creating stored ObjectScript classes

Let's go then. Our Caché classes will be very simple. We can do without an IDE - just copy the code of the classes directly through the portal of your instance of the Caché platform (yes, the Caché DBMS is far from being just a DBMS): System browser > Classes > Import (Namespace USER).

After saving, objects will appear in globals with names matching those of corresponding classes. Look for them also in the Caché management portal: System browser > Globals (Namespace USER).

// an event object includes a name, a description, a date, and a list of participants

Class FW.Event Extends %Persistent {

    Property title as %String;

    Property description as %String;

    Property date as %Date;

    Property visitors as list of FW.Attendee;

}



// the participant object has a name/nickname

Class FW.Attendee Extends %Persistent {

    Property name As %String;

}

Getting access to Caché objects from Python

First, let's connect to the Caché DBMS database. Let's do everything exactly as specified in the documentation.

Just a useful fact for work. The free educational version of the Caché DBMS allows you to do everything its paid counterpart can do but supports two active connections only. Therefore, you won't be able to keep one connection from the IDE and concurrently try to run some code for interacting with the server. The simplest solution is to close the IDE while Python code is being executed.

# import of the Caché module for integrating it with Python3

import intersys.pythonbind3

# connecting to the server

conn = intersys.pythonbind3.connection()

conn.connect_now("localhost[1972]:USER","_SYSTEM","SYS", None)

# checking the connection descriptor

print ("conn = %d " % conn.handle)

# connecting to the database

database = intersys.pythonbind3.database(conn)

Now, let's create an object database with data on IT events and their participants. A very, very simple one. First, let's create a class for registering users and storing participants' details.  For simplicity, the class only contains the participant's name.

// a class for objects containing information about registered participants

class Attendee:
    

# initialization of a new empty object in RAM

    def __init__ (self):

        self.att = database.create_new("FW.Attendee", None)

    

# writing the participant's name and saving the object to the database with a unique id

    def new (self, name):

        self.att.set("name", name)

        self.att.run_obj_method("%Save",[])

    

# loading an object by id from the participant database

    def use (self, id):

        self.att = database.openid("FW.Attendee",str(id),-1,-1)


# removing an object from the participant database

    def clean (self):

        id = self.att.run_obj_method("%Id",[])

        self.att.run_obj_method("%DeleteId", [id])

As you can see, we are using ready wrapper functions for methods intended for accessing object fields in Caché:  "set", combined with passing a property name in quotes in the parameters, and "openid" with the name of the package and class. There are examples for the identical "get" function below. To access any other methods, including those inherited by the class from its ancestors, use the run_obj_method() function with the name of the method and call parameters, if necessary.

The magic is in this line: self.att.run_obj_method("%Save",[])

That's how we can save objects directly and without having to involve additional libraries and frameworks, such as the ubiquitous and inconvenient ORM's.

Besides, thanks to the OOP nature of ObjectScript along with the methods of your class (in our example, we didn't create them), we get access from Python to the entire set of methods inherited from the Persistent class and its children as a free bonus. Here's the complete list, just in case.

Let's create the first participant:

att = Attendee()

att.new("Adam")

Running this code will add a new global called FW.AttendeeD containing the newly saved object to the database (as seen in the screenshot):

After saving, this object gets a unique id (number 1). Therefore, you can load it to our program using this id:

att = Attendee()

att.use(1)

print (att.att.get("name"))

Knowing the id, we can now remove an object from the participant database:

att = Attendee()

att.use(1)

att.clean()

Make sure to check that after this sample code is run, the object record should disappear from the global. Although the loaded data still remain in the "memory" of your object until the program is closed.

Let's make the next step. Let's create actual event records.

# a class for objects containing  event details

class Event:

# initializing a new empty object in RAM

    def __init__ (self):

        self.event = database.create_new("FW.Event", None)


# populating an object with data and saving it in the database with a unique id

    def new (self, title, desc, date):

        self.event.set("title", title)

        self.event.set("description", desc)

        self.event.set("date", date)

        self.event.run_obj_method("%Save",[])

   
# loading an object by id from the database

    def use (self, id):

        self.event = database.openid("FW.Event",str(id),-1,-1)


# adding a participant to the event participants list

    def addAttendee (self, att):

        eventAtt = self.event.get("visitors")

        eventAtt.run_obj_method("Insert", [att])

        self.event.set("visitors", eventAtt)

        self.event.run_obj_method("%Save",[])
  

# removing an object from the database

    def clean (self):

        id = self.event.run_obj_method("%Id",[])

        self.event.run_obj_method("%DeleteId", [id])

The structure of the class is almost identical to that of the participant class above. Most importantly, we now have a method for adding participants to the list of event participants: addAttendee(att).

Let's try to create an object record about the new event and save it in the database.

haJS = Event()

haJS.new("haJS", "Frontend meetup", "2019-01-19")

The result should look like this (please note that the date was automatically converted to the ObjectScript format and will be returned in the default format when loaded into a Python object):

All we need to do is to add the participant to the event:

# loading the previously saved event

haJS = Event()

haJS.use(1)

# creating a new participant

att = Attendee()

att.new("Mark")

# adding the participant to our event

haJS.addAttendee(att.att)

As you can see from these examples, you don't necessarily need to simultaneously think about your data model and its tabular storage schema. You can use more obvious tools for your tasks.

Detailed instructions on connecting and using Caché with Python and other languages are always available in the official documentation and on the InterSystems developer community portal – and it's as many as 5000 members talking to each other here at community.intersystems.com

Fact: the multi-model InterSystems Caché DBMS remains a global leader on the object database market

ディスカッション (0)1
続けるにはログインするか新規登録を行ってください