orientechnologies / orientjs

The official fast, lightweight node.js client for OrientDB
http://orientdb.com
Other
326 stars 68 forks source link

Newline in batch CREATE FUNCTION causes lexical error in OrientDB #444

Open MarttiR opened 3 years ago

MarttiR commented 3 years ago

OrientJS version: 3.0.11

When using CREATE FUNCTION in session.batch(), having a newline in the function body causes a lexical error in OrientDB.

The same CREATE FUNCTION statements work from Functions Management in Studio, although that way the functions get saved without any newlines.

Working example with everything on one line:

await session.batch(`
CREATE FUNCTION newlineTest
"var foo = 'test ok'; return foo;"
IDEMPOTENT TRUE
LANGUAGE JAVASCRIPT
`).all();

But if the code is split into multiple lines...

await session.batch(`
CREATE FUNCTION newlineTest
"var foo = 'test ok';
return foo;"
IDEMPOTENT TRUE
LANGUAGE JAVASCRIPT
`).all();

... it produces an error:

{"name":"OrientDB.ConnectionError [10]","code":10,"message":"Cannot select the server","data":{}}

Stack trace from OrientDB logs:

Uncaught exception in thread OrientDB (/172.19.0.4:2424) <- BinaryClient (/172.19.0.1:45546)
com.orientechnologies.orient.core.sql.parser.TokenMgrError: Lexical error at line 4, column 28.  Encountered: "\n" (10), after : "\"var foo = \'test ok\';"
    at com.orientechnologies.orient.core.sql.parser.OrientSqlTokenManager.getNextToken(OrientSqlTokenManager.java:5460)
    at com.orientechnologies.orient.core.sql.parser.OrientSql.jj_scan_token(OrientSql.java:39075)
    at com.orientechnologies.orient.core.sql.parser.OrientSql.jj_3R_592(OrientSql.java:33962)
    at com.orientechnologies.orient.core.sql.parser.OrientSql.jj_3R_81(OrientSql.java:30002)
    at com.orientechnologies.orient.core.sql.parser.OrientSql.jj_3_16(OrientSql.java:32575)
    at com.orientechnologies.orient.core.sql.parser.OrientSql.jj_3_49(OrientSql.java:32759)
    at com.orientechnologies.orient.core.sql.parser.OrientSql.jj_3R_71(OrientSql.java:32931)
    at com.orientechnologies.orient.core.sql.parser.OrientSql.jj_3R_232(OrientSql.java:32997)
    at com.orientechnologies.orient.core.sql.parser.OrientSql.jj_3R_69(OrientSql.java:33018)
    at com.orientechnologies.orient.core.sql.parser.OrientSql.jj_3_3(OrientSql.java:33987)
    at com.orientechnologies.orient.core.sql.parser.OrientSql.jj_2_3(OrientSql.java:23786)
    at com.orientechnologies.orient.core.sql.parser.OrientSql.parseScript(OrientSql.java:307)
    at com.orientechnologies.orient.core.sql.OSQLEngine.parseScript(OSQLEngine.java:91)
    at com.orientechnologies.orient.core.sql.OSQLEngine.parseScript(OSQLEngine.java:85)
    at com.orientechnologies.orient.core.command.OSqlScriptExecutor.execute(OSqlScriptExecutor.java:58)
    at com.orientechnologies.orient.core.db.document.ODatabaseDocumentEmbedded.execute(ODatabaseDocumentEmbedded.java:784)
    at com.orientechnologies.orient.server.OConnectionBinaryExecutor.executeQuery(OConnectionBinaryExecutor.java:1494)
    at com.orientechnologies.orient.client.remote.message.OQueryRequest.execute(OQueryRequest.java:143)
    at com.orientechnologies.orient.server.network.protocol.binary.ONetworkProtocolBinary.sessionRequest(ONetworkProtocolBinary.java:355)
    at com.orientechnologies.orient.server.network.protocol.binary.ONetworkProtocolBinary.execute(ONetworkProtocolBinary.java:239)
    at com.orientechnologies.common.thread.OSoftThread.run(OSoftThread.java:67)

Workaround

As a workaround, the newlines can be escaped, but this can be a burden for larger scripts:

await session.batch(`
CREATE FUNCTION newlineTest
"var foo = 'test ok'; \
return foo;"
IDEMPOTENT TRUE
LANGUAGE JAVASCRIPT
`).all();

These examples use JavaScript functions, but the problem is the same when creating functions with LANGUAGE SQL.

MarttiR commented 3 years ago

A significantly better workaround is to not use the CREATE FUNCTION statement, but instead construct an INSERT statement. This is also how Function Management in Studio inserts functions.

Make sure to escape backslashes, pipes and double quotes from the code.

const statement = `INSERT INTO OFunction CONTENT {
  "name": "someFunction",
  "code": "${someFunctionCodeString.replace(/\\|"/g, (m) => '\\'+m)}",
  "parameters": ["param1","param2"],
  "idempotent": true,
  "language": "JAVASCRIPT"
};`;

As an idea for future development: the above is done so functions can be kept in their own .js and .sql files, from which jest-docblock parses @idempotent and @param pragmas; the filename and extension are used for function name and language.

Having everything about the function in one file is a nice pattern for organizing custom functions, and it would be great if OrientJS included a function manager that did this out-of-the-box.