UNPKG

23 kBMarkdownView Raw
1:heavy_exclamation_mark: Notice :heavy_exclamation_mark:
2====================================
3
4__Only critical fixes will be provided in hdb. Please continue using the new hana-client ([documentation](https://help.sap.com/viewer/0eec0d68141541d1b07893a39944924e/2.0.02/en-US/58c18548dab04a438a0f9c44be82b6cd.html)).__
5
6```shell
7npm config set @sap:registry https://npm.sap.com
8npm install @sap/hana-client
9```
10
11SAP HANA Database Client for Node
12====================================
13
14
15A JavaScript client for Node implementing the
16[SAP HANA Database SQL Command Network Protocol](http://help.sap.com/hana/SAP_HANA_SQL_Command_Network_Protocol_Reference_en.pdf).
17
18[![Version](https://img.shields.io/npm/v/hdb.svg?style=flat-square)](https://npmjs.org/package/hdb)
19[![Build](https://img.shields.io/travis/SAP/node-hdb.svg?style=flat-square)](http://travis-ci.org/SAP/node-hdb)
20[![Coverage](https://img.shields.io/coveralls/SAP/node-hdb/master.svg?style=flat-square)](https://coveralls.io/r/SAP/node-hdb?branch=master)
21[![Dependencies](https://img.shields.io/david/SAP/node-hdb.svg?style=flat-square)](https://david-dm.org/SAP/node-hdb#info=dependencies&view=list)
22[![DevDependencies](https://img.shields.io/david/dev/SAP/node-hdb.svg?style=flat-square)](https://david-dm.org/SAP/node-hdb?type=dev&view=list)
23[![License](https://img.shields.io/npm/l/hdb.svg?style=flat-square)](http://www.apache.org/licenses/LICENSE-2.0.html)
24[![Downloads](https://img.shields.io/npm/dm/hdb.svg?style=flat-square)](http://npm-stat.com/charts.html?package=hdb)
25
26Table of contents
27-------------
28
29* [Install](#install)
30* [Getting started](#getting-started)
31* [Establish a database connection](#establish-a-database-connection)
32* [Direct Statement Execution](#direct-statement-execution)
33* [Prepared Statement Execution](#prepared-statement-execution)
34* [Bulk Insert](#bulk-insert)
35* [Streaming results](#streaming-results)
36* [Transaction handling](#transaction-handling)
37* [Streaming Large Objects](#streaming-large-objects)
38* [CESU-8 encoding support](#cesu-8-encoding-support)
39* [Running tests](#running-tests)
40* [Running examples](#running-examples)
41* [Todo](#todo)
42
43Install
44-------
45
46Install from npm:
47
48```bash
49npm install hdb
50```
51
52or clone from the [GitHub repository](https://github.com/SAP/node-hdb) to run tests and examples locally:
53
54```bash
55git clone https://github.com/SAP/node-hdb.git
56cd node-hdb
57npm install
58```
59
60Getting started
61------------
62
63If you do not have access to a SAP HANA server, go to the [SAP HANA Developer Center](http://scn.sap.com/community/developer-center/hana) and choose one of the options to [get your own trial SAP HANA Server](http://scn.sap.com/docs/DOC-31722).
64
65This is a very simple example how to use this module:
66
67```js
68var hdb = require('hdb');
69var client = hdb.createClient({
70 host : 'hostname',
71 port : 30015,
72 user : 'user',
73 password : 'secret'
74});
75client.on('error', function (err) {
76 console.error('Network connection error', err);
77});
78client.connect(function (err) {
79 if (err) {
80 return console.error('Connect error', err);
81 }
82 client.exec('select * from DUMMY', function (err, rows) {
83 client.end();
84 if (err) {
85 return console.error('Execute error:', err);
86 }
87 console.log('Results:', rows);
88 });
89});
90```
91
92Establish a database connection
93-------------------------------
94
95The first step to establish a database connection is to create a client object. It is recommended to pass all required `connect` options like `host`, `port`, `user` and `password` to the `createClient` function. They will be used as defaults for following connect calls on the created client instance. In case of network connection errors like a connection timeout or a database restart you should register an error event handler in order to be able to handle this kind of problems. If there are no error event handlers, errors will not be emitted.
96
97```js
98var hdb = require('hdb');
99var client = hdb.createClient({
100 host : 'hostname',
101 port : 30015,
102 user : 'user',
103 password : 'secret'
104});
105client.on('error', function (err) {
106 console.error('Network connection error', err);
107});
108console.log(client.readyState); // new
109```
110
111When a client instance is created it does not immediately open a network connection to the database host. Initially the client is in state `new`. When you call `connect` the first time two things are done internally.
112
1131. A network connection is established and the communication is initialized (Protocol - and Product Version exchange). Now the connection is ready for exchanging messages but no user session is established. The client is in state `disconnected`. This step is skipped if the client is already in state `disconnected`.
114
1152. The authentication process is initiated. After a successful user authentication a database session is established and the client is in state `connected`. If authentication fails the client remains in state `'disconnect'`.
116
117```js
118client.connect(function (err) {
119 if (err) {
120 return console.error('Error:', err);
121 }
122 console.log(client.readyState); // connected
123});
124```
125If user and password are specified they will override the defaults of the client. It is possible to disconnect and reconnect with a different user on the same client instance and the same network connection.
126
127The client also supports HANA systems installed in multiple-container (MDC) mode. In this case a single HANA system may contain several isolated tenant databases.
128A database is identified by its name. One of the databases in an MDC setup is the system database which is used for central system administration.
129One can connect to a specific tenant database directly via its host and SQL port (as shown in the example above) or via the system database which may lookup the exact host and port of a particular database by a given name.
130
131```js
132var hdb = require('hdb');
133var client = hdb.createClient({
134 host : 'hostname', // system database host
135 port : 30013, // system database port
136 databaseName : 'DB1', // name of a particular tenant database
137 user : 'user', // user for the tenant database
138 password : 'secret' // password for the user specified
139});
140```
141
142The client also accepts an instance number instead of the port of the system database:
143
144```js
145var hdb = require('hdb');
146var client = hdb.createClient({
147 host : 'hostname', // system database host
148 instanceNumber : '00', // instance number of the HANA system
149 databaseName : 'DB1', // name of a particular tenant database
150 user : 'user', // user for the tenant database
151 password : 'secret' // password for the user specified
152});
153```
154
155Multiple hosts can be provided to the client as well:
156
157```js
158var hdb = require('hdb');
159var client = hdb.createClient({
160 hosts : [ { host: 'host1', port: 30015 }, { host: 'host2', port: 30015 } ],
161 user : 'user',
162 password : 'secret'
163});
164```
165
166This is suitable for Multiple-host HANA systems which are distributed over several hosts. The client will establish a connection to the first available host from the list.
167
168### Authentication mechanisms
169Details about the different authentication method can be found in the [SAP HANA Security Guide](http://help.sap.com/hana/SAP_HANA_Security_Guide_en.pdf).
170
171#### User / Password
172Users authenticate themselves with their database `user` and `password`.
173
174#### SAML assertion
175SAML bearer assertions as well as unsolicited SAML responses that include an
176unencrypted SAML assertion can be used to authenticate users. SAML assertions and responses must be signed using XML signatures. XML Digital signatures can be created with [xml-crypto](https://www.npmjs.org/package/xml-crypto) or [xml-dsig](https://www.npmjs.org/package/xml-dsig).
177
178Instead of `user` and `password` you have to provide a SAML `assertion`.
179
180```js
181client.connect({
182 assertion: '<Assertion xmlns="urn:oasis:names:tc:SAML:2.0:assertion" ...>...</Assertion>'
183},function (err) {
184 if (err) {
185 return console.error('Error:', err);
186 }
187 console.log('User:', client.get('user'));
188 console.log('SessionCookie:', client.get('SessionCookie'));
189});
190```
191
192After a successful SAML authentication the server returns the database `user` and a `SessionCookie` which can be used for reconnect.
193
194#### Kerberos
195A Kerberos authentication provider can be used to authenticate users.
196> This mechanism is not implemented and I do not plan to implement a Kerberos authentication provider myself. Contributions via pull request are welcome.
197
198### Encrypted network communication
199To establish an encrypted database connection just pass whether `key`, `cert` and `ca` or a `pfx` to createClient.
200
201```js
202var client = hdb.createClient({
203 host : 'hostname',
204 port : 30015,
205 key : fs.readFileSync('client-key.pem'),
206 cert : fs.readFileSync('client-cert.pem'),
207 ca : [fs.readFileSync('trusted-cert.pem')],
208 ...
209});
210```
211
212Use the `useTLS` option if you would like to connect to HANA using Node.js's trusted certificates.
213
214```js
215var client = hdb.createClient({
216 host : 'hostname',
217 port : 30015,
218 useTLS: true,
219 ...
220});
221```
222
223**Note** for MDC use cases: The system database and the target tenant database may be configured to work with different certificates.
224If so, make sure to include all the necessary TLS-related properties for both the databases in the client's options.
225
226In case you need custom logic to validate the server's hostname against the certificate, you can assign a callback function to the `checkServerIdentity` property, alongside the other connection options. The callback is
227supplied to the `tls.connect` funciton of the [TLS](https://nodejs.org/api/tls.html#connect) API and should conform to the signature described there.
228
229Direct Statement Execution
230--------------------------
231
232Direct statement execution is the simplest way to execute SQL statements.
233The only input parameter is the SQL command to be executed.
234Generally we return the statement execution results using callbacks.
235The type of returned result depends on the kind of statement.
236
237### DDL Statement
238
239In the case of a DDL Statement nothing is returned.
240
241```js
242client.exec('create table TEST.NUMBERS (a int, b varchar(16))', function (err) {
243 if (err) {
244 return console.error('Error:', err);
245 }
246 console.log('Table TEST.NUMBERS has been created');
247});
248```
249
250### DML Statement
251
252In the case of a DML Statement the number of `affectedRows` is returned.
253
254```js
255client.exec('insert into TEST.NUMBERS values (1, \'one\')', function (err, affectedRows) {
256 if (err) {
257 return console.error('Error:', err);
258 }
259 console.log('Number of affected rows:', affectedRows);
260});
261```
262
263### Query
264
265The `exec` function is a convenient way to completely retrieve the result of a query. In this case all selected `rows` are fetched and returned in the callback. The `resultSet` is automatically closed and all `Lobs` are completely read and returned as Buffer objects. If streaming of the results is required you will have to use the `execute` function. This is described in section [Streaming results](#streaming-results).
266
267```js
268client.exec('select A, B from TEST.NUMBERS order by A', function(err, rows) {
269 if (err) {
270 return console.error('Error:', err);
271 }
272 console.log('Rows:', rows);
273});
274```
275
276Different Representations of Query Results
277-----------------------------------
278The default representation of a single row is an Object where the property names are the columnDisplayNames of the resultSetMetadata.
279
280```js
281var command = 'select top 1 * from t1';
282client.exec(command, function(err, rows) {
283 /* rows will be an array like this:
284 [{
285 ID: 1,
286 A: 't1.1.a',
287 B: 't1.1.b'
288 }]
289 */
290});
291```
292
293If your SQL statement is a join with overlapping column names, you may want to get separate objects for each table per row. This is possible if you set option `nestTables` to true.
294
295```js
296var command = 'select top 1 * from t1 join t2 on t1.id = t2.id';
297var options = {
298 nestTables: true
299};
300client.exec(command, options, function(err, rows) {
301 /* rows will be an array like this now:
302 [{
303 T1: {
304 ID: 1,
305 A: 't1.1.a',
306 B: 't1.1.b',
307 },
308 T2: {
309 ID: 1
310 A: 't2.1.a',
311 B: 't2.1.b',
312 },
313 }]
314 */
315});
316```
317
318It is also possible to return all rows as an Array where the order of the column values is exactly the same as in the resultSetMetadata. In this case you have to set the option `rowsAsArray` to true.
319
320```js
321var command = 'select top 1 * from t1 join t2 on t1.id = t2.id';
322var options = {
323 rowsAsArray: true
324};
325client.exec(command, options, function(err, rows) {
326 /* rows will be an array like this now:
327 [[
328 1,
329 't1.1.a',
330 't1.1.b',
331 1
332 't2.1.a',
333 't2.1.b'
334 ]]
335 */
336});
337```
338
339Prepared Statement Execution
340----------------------------
341
342### Prepare a Statement
343
344The client returns a `statement` object which can be executed multiple times.
345
346```js
347client.prepare('select * from DUMMY where DUMMY = ?', function (err, statement){
348 if (err) {
349 return console.error('Error:', err);
350 }
351 // do something with the statement
352 console.log('StatementId', statement.id);
353});
354```
355
356### Execute a Statement
357
358The execution of a prepared statement is similar to the direct statement execution on the client. The difference is that the first parameter of `exec` function is an array with positional `parameters`. In case of named parameters it can also be an `parameters` object.
359
360```js
361statement.exec(['X'], function (err, rows) {
362 if (err) {
363 return console.error('Error:', err);
364 }
365 console.log('Rows:', rows);
366});
367```
368
369If you use the `execute` instead of `exec` function the `resultSet` is returned in the callback like in direct query execution above.
370
371### Calling Stored Procedures
372
373If you have for example the following stored procedure:
374
375```sql
376create procedure PROC_DUMMY (in a int, in b int, out c int, out d DUMMY, out e TABLES)
377 language sqlscript
378 reads sql data as
379 begin
380 c := :a + :b;
381 d = select * from DUMMY;
382 e = select * from TABLES;
383 end
384```
385you can call it via a prepared statement.
386The second argument is always an object with the scalar parameters.
387If there are no scalar parameters, an empty object ``{}`` will be returned.
388The following arguments are the `resultSets`.
389
390```js
391client.prepare('call PROC_DUMMY (?, ?, ?, ?, ?)', function(err, statement){
392 if (err) {
393 return console.error('Prepare error:', err);
394 }
395 statement.exec({
396 A: 3,
397 B: 4
398 }, function(err, parameters, dummyRows, tableRows) {
399 if (err) {
400 return console.error('Exec error:', err);
401 }
402 console.log('Parameters:', parameters);
403 console.log('Dummies:', dummyRows);
404 console.log('Tables:', tableRows);
405 });
406});
407```
408**Note:** Default values for stored procedures are not supported.
409
410### Drop Statement
411
412To drop the statement simply call
413
414```js
415statement.drop(function(err){
416 if (err) {
417 return console.error('Drop error:', err);
418 }
419 console.log('Statement dropped');
420});
421```
422The callback is optional in this case.
423
424### Using Datetime types
425
426If you want to use datetime types in a prepared statement,
427be aware that strings like `'14.04.2016 12:41:11.215'` are not
428processed by the SAP HANA Database but by the node-hdb module.
429Therefore you must use the exact required format that would be returned
430by a selection made with this module.
431The formats are:
432```js
433TIME: '13:32:20'
434DATE: '2016-04-14'
435TIMESTAMP: '2016-04-14T13:32:20.737'
436SECONDDATE: '2016-04-14T13:32:20'
437```
438
439Another possibility is to use the functions
440`TO_DATE`, `TO_DATS`, `TO_TIME` and `TO_TIMESTAMP` in your
441SQL statement to convert your string to a valid datetime type.
442
443Bulk Insert
444---------------
445
446If you want to insert multiple rows with a single execute you just
447have to provide the all parameters as array.
448
449```js
450client.prepare('insert into TEST.NUMBERS values (?, ?)', function(err, statement){
451 if (err) {
452 return console.error('Prepare error:', err);
453 }
454 statement.exec([[1, 'one'], ['2', 'two'], [3, 'three']], function(err, affectedRows) {
455 if (err) {
456 return console.error('Exec error:', err);
457 }
458 console.log('Array of affected rows:', affectedRows);
459 });
460});
461```
462Take a look at the example [app9](https://github.com/SAP/node-hdb/blob/master/examples/app9.js) for further details.
463
464
465Streaming results
466---------------
467
468If you use the `execute` function of client or statement instead of the `exec` function, a `resultSet` object is returned in the callback instead of an array of all rows. The `resultSet` object allows you to create an object based `row` stream or an array based stream of `rows` which can be piped to an writer object. Don't forget to close the `resultSet` if you use the `execute` function.
469
470```js
471client.execute('select A, B from TEST.NUMBERS order by A', function(err, rs) {
472 if (err) {
473 return console.error('Error:', err);
474 }
475 rs.setFetchSize(2048);
476 rs.createObjectStream()
477 .pipe(new MyWriteStream())
478 .on('finish', function (){
479 if (!rs.closed) {
480 rs.close();
481 }
482 });
483});
484```
485Take a look at the example [app4](https://github.com/SAP/node-hdb/blob/master/examples/app4.js) for further details.
486
487Transaction handling
488---------------
489
490The default behavior is that each statement is automatically commited. If you want to manually control `commit ` and `rollback` of a transaction, you can do this by calling `setAutoCommit(false)` on the client object.
491
492```js
493function execTransaction(cb) {
494 client.setAutoCommit(false);
495 async.series([
496 client.exec.bind(client, "insert into NUMBERS values (1, 'one')"),
497 client.exec.bind(client, "insert into NUMBERS values (2, 'two')")
498 ], function (err) {
499 if (err) {
500 client.rollback(function(err){
501 if (err) {
502 err.code = 'EROLLBACK';
503 return cb(err);
504 }
505 cb(null, false);
506 });
507 } else {
508 client.commit(function(commitError){
509 if (err) {
510 err.code = 'ECOMMIT';
511 return cb(err);
512 }
513 cb(null, true);
514 });
515 }
516 client.setAutoCommit(true);
517 });
518}
519
520execTransaction(function(err, ok){
521 if (err) {
522 return console.error('Commit or Rollback error', err);
523 }
524 if (ok) {
525 console.log('Commited');
526 } else {
527 console.log('Rolled back');
528 }
529})
530
531```
532
533Take a look at the example [tx1](https://github.com/SAP/node-hdb/blob/master/examples/tx1.js) for further details.
534
535Streaming Large Objects
536-------------
537
538### Read Streams
539
540Reading large object as stream can be done if you use the `execute` method of client or statement. In this case for all LOB columns a [Lob](https://github.com/SAP/node-hdb/blob/master/lib/protocol/Lob.js) object is returned. You can call `createReadStream` or `read` in order create a readable stream or to read the LOB completely.
541
542### Write Streams
543
544Writing large objects is automatically done. You just have to pass instance of [`Readable`](http://nodejs.org/api/stream.html#stream_class_stream_readable_1) or a Buffer object as parameter.
545
546Take a look at the example [app7](https://github.com/SAP/node-hdb/blob/master/examples/app7.js) for further details.
547
548CESU-8 encoding support
549-------------
550
551SAP HANA server connectivity protocol uses [CESU-8](https://en.wikipedia.org/wiki/CESU-8) encoding. Node.js does not suport CESU-8 natively and the driver by default converts all text to CESU-8 format in the javascript layer including SQL statements.
552
553Due to the fact that Node.js has built-in support for UTF-8, using UTF-8 in the HDB drivers can lead to performance gains especially for large text data.
554If you are sure that your data contains only [BMP](https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane) characters, you can disable CESU-8 conversion by setting a flag in the client configuration.
555
556`createClient` accepts the parameter `useCesu8` to disable CESU-8 support. Here is how to provide the configuration:
557
558```js
559var hdb = require('hdb');
560var client = hdb.createClient({
561 host : 'hostname',
562 port : 30015,
563 user : 'user',
564 password : 'secret',
565 useCesu8 : false
566});
567
568```
569
570This setting is per client and cannot be changed later.
571
572__Note:__ Using CESU-8 brings performance penalties proportionate to the text size that has to be converted.
573
574Running tests
575-------------
576
577To run the unit tests for _hdb_ simply run:
578
579```bash
580make test-unit
581```
582
583To run the unit tests as well as acceptance tests for _hdb_ you have to run:
584
585```bash
586make test
587```
588
589For the acceptance tests a database connection has to be established. Therefore you need to copy the configuration template [config.tpl.json](https://github.com/SAP/node-hdb/blob/master/test/db/config.tpl.json) in the ```test/db``` folder to ```config.json``` and change the connection data to yours. If the ```config.json``` file does not exist a local mock server is started.
590
591
592Running examples
593----------------
594
595Also, for the examples you need a valid a ```config.json``` in the ```test/db``` folder.
596
597
598- [app1](https://github.com/SAP/node-hdb/blob/master/examples/app1.js): Simple query.
599- [app2](https://github.com/SAP/node-hdb/blob/master/examples/app2.js): Fetch rows from `ResultSet`.
600- [app3](https://github.com/SAP/node-hdb/blob/master/examples/app3.js): Streaming rows `createObjectStream()`.
601- [app4](https://github.com/SAP/node-hdb/blob/master/examples/app4.js): Pipe row into JSON-Transform and to `stdout`.
602- [app5](https://github.com/SAP/node-hdb/blob/master/examples/app6.js): Stream from the filesystem into a db table.
603- [app6](https://github.com/SAP/node-hdb/blob/master/examples/app5.js): Stream from a db table into the filesystem.
604- [app7](https://github.com/SAP/node-hdb/blob/master/examples/app7.js): Insert a row with a large image into a db table (uses WriteLobRequest and Transaction internally).
605- [app8](https://github.com/SAP/node-hdb/blob/master/examples/app8.js): Automatic reconnect when network connection is lost.
606- [app9](https://github.com/SAP/node-hdb/blob/master/examples/app9.js): Insert multiple rows with large images into a db table as one batch.
607- [app10](https://github.com/SAP/node-hdb/blob/master/examples/app10.js): Usage example of query option `nestTables`.
608- [call1](https://github.com/SAP/node-hdb/blob/master/examples/call1.js): Call stored procedure.
609- [call2](https://github.com/SAP/node-hdb/blob/master/examples/call2.js): Call stored procedure with lob input and output parameter.
610- [call3](https://github.com/SAP/node-hdb/blob/master/examples/call3.js): Call stored procedure with table as input parameter.
611- [tx1](https://github.com/SAP/node-hdb/blob/master/examples/tx1.js): Transaction handling (shows how to use commit and rollback).
612- [csv](https://github.com/SAP/node-hdb/blob/master/examples/csv.js): Stream a db table into csv file.
613- [server](https://github.com/SAP/node-hdb/blob/master/examples/server.js): Stream rows into http response `http://localhost:1337/{schema}/{tablename}?top={top}`
614
615To run e.g. the first example:
616
617```bash
618node examples/app1
619```
620
621Todo
622----
623* Improve documentation of the client api
624* Improve error handling
625* ...