Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

inline SQLs, consume Kronos ABAP API #3

Merged
merged 53 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
d808686
inline sql initial commit
iliyan-velichkov Jun 12, 2024
a00cbba
rollback package-lock.json
iliyan-velichkov Jun 12, 2024
5d2db05
comment my code
iliyan-velichkov Jun 12, 2024
0572e26
uncomment code + cleanup
iliyan-velichkov Jun 12, 2024
1e232bb
implement some of the interface methods
iliyan-velichkov Jun 13, 2024
c566e29
more interface impl
iliyan-velichkov Jun 13, 2024
7c94075
implement cursor methods, beginTransaction, commit and rollback
iliyan-velichkov Jun 13, 2024
75349ba
add testing table
iliyan-velichkov Jun 13, 2024
9d23e45
remove sql.js since it is not actually needed
iliyan-velichkov Jun 13, 2024
f197f66
fix client
iliyan-velichkov Jun 13, 2024
7db5627
add sdk as external to prevent errors
iliyan-velichkov Jun 13, 2024
9241f06
multiple calls in run method
iliyan-velichkov Jun 13, 2024
04ba247
add dao abap init commit
iliyan-velichkov Jun 13, 2024
3d4b0c6
add zcl_dirigible_employee_dao.clas.abap - not working
iliyan-velichkov Jun 13, 2024
81d87d4
update abap files - not working
iliyan-velichkov Jun 13, 2024
d0d394b
apply review comments
iliyan-velichkov Jun 13, 2024
eee8e1a
transpiler and lint config files
iliyan-velichkov Jun 14, 2024
bdb6c1d
refactor abap code
iliyan-velichkov Jun 14, 2024
bf58d25
update gitignore
iliyan-velichkov Jun 14, 2024
5c23502
directly print the result of select
iliyan-velichkov Jun 14, 2024
b45d6f3
use snake case for the table - otherwise values not extracted
iliyan-velichkov Jun 14, 2024
292b889
format the code
iliyan-velichkov Jun 14, 2024
a8d7c35
add insert method
iliyan-velichkov Jun 17, 2024
7967cd0
add delete_all_employees method
iliyan-velichkov Jun 17, 2024
07dc7c6
add logger
iliyan-velichkov Jun 17, 2024
0f4d411
add more messages
iliyan-velichkov Jun 17, 2024
b2fb34a
reorder methods
iliyan-velichkov Jun 17, 2024
3c4bc46
refactoring
iliyan-velichkov Jun 17, 2024
7b17e71
extract some methods
iliyan-velichkov Jun 17, 2024
84e7058
add comment
iliyan-velichkov Jun 17, 2024
6d4675e
remove quotes for strings in println
iliyan-velichkov Jun 17, 2024
8943477
allow length of 160
iliyan-velichkov Jun 17, 2024
23eefd1
fix update
iliyan-velichkov Jun 17, 2024
f62d2ad
remove comments
iliyan-velichkov Jun 17, 2024
eaf1d0d
[workaround] manually register the table to enable update
iliyan-velichkov Jun 17, 2024
60373cc
database init using kronos abap api
iliyan-velichkov Jun 19, 2024
933d4ce
cleanup
iliyan-velichkov Jun 19, 2024
f1f714a
cleanup
iliyan-velichkov Jun 20, 2024
0eeea95
update dependencies
iliyan-velichkov Jun 20, 2024
eceb627
update build script
iliyan-velichkov Jun 20, 2024
60393f4
restructure the project
iliyan-velichkov Jun 20, 2024
71376da
add abap app class with run method
iliyan-velichkov Jun 20, 2024
8669a38
use introduced Kronos ABAP API from the app
iliyan-velichkov Jun 21, 2024
ec0424e
use renamed ABAP API class
iliyan-velichkov Jun 24, 2024
ae6ba9a
add build-linux.sh
iliyan-velichkov Jun 24, 2024
9b334f1
configure abap lint
iliyan-velichkov Jun 24, 2024
25626ec
add kronos ABAP APIs as libs
iliyan-velichkov Jun 24, 2024
c146dac
add logs
iliyan-velichkov Jun 24, 2024
ffe60f9
fix unit script
iliyan-velichkov Jun 24, 2024
2ed0be2
fix run script
iliyan-velichkov Jun 24, 2024
f25c51a
align the sample with the blog
iliyan-velichkov Jun 28, 2024
6f42aaf
remove test workflow since this project could be run on Kronos only
iliyan-velichkov Jun 28, 2024
efe61cf
cleanup
iliyan-velichkov Jun 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 237 additions & 0 deletions DirigibleDatabaseClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
import { DB } from "sample-abap/node_modules/@abaplint/runtime/build/src";

import { database, sql, update, query } from "sdk/db";

import { logging } from "sdk/log";

const DatabaseResultSetHelper = Java.type("org.eclipse.dirigible.components.data.management.helpers.DatabaseResultSetHelper");

export class DirigibleDatabaseClient implements DB.DatabaseClient {

public readonly name = "dirigible-default-database";
private static readonly DEFAULT_DATA_SOURCE_NAME = "DefaultDB";

private readonly logger;
private connection: any;


public constructor() {
this.logger = logging.getLogger("org.eclipse.dirigible.DirigibleDatabaseClient");
this.logger.info("Initializing...")
}

public async connect() {
this.logger.debug("Creating connection...");
this.connection = database.getConnection(DirigibleDatabaseClient.DEFAULT_DATA_SOURCE_NAME);
this.connection.setAutoCommit(false);
// @ts-ignore
if (abap?.context?.databaseConnections && abap.context.databaseConnections["DEFAULT"] === this) {
// @ts-ignore
abap.builtin.sy.get().dbsys?.set(this.name);
}
this.logger.debug("Created connection");
}

public async disconnect() {
if (this.connection) {
this.logger.debug("Closing connection...");
this.connection.close();
this.logger.debug("Connection was closed");
}
}

public async execute(sql: string | string[]): Promise<void> {

if (typeof sql !== "string") {
for (const s of sql) {
await this.execute(s);
}
return;
}

if (sql === "") {
return;
}
if (!this.connection) {
const errorMessage = `SQL [${sql}] cannot be executed because the connection is not inialized`;
this.logger.error(errorMessage);
throw new Error(errorMessage);
}

this.logger.debug("Executing sql [{}]", sql);

try {
const statement = this.connection.createStatement();
const hasResultSet = statement.execute(sql);
if (hasResultSet) {
this.logger.debug("Executed sql [{}] has result set.", sql);
}
} catch (error) {
const errorMessage = `Failed to execute [${sql}]. Error: [${error}]`;
this.logger.error(errorMessage, error);
throw new Error(errorMessage);
}
}

public async beginTransaction() {
return; // todo
}

public async commit() {
if (this.connection) {
this.logger.debug("Committing current connection...");
this.connection.commit();
this.logger.debug("Current connection was committed");
} else {
this.logger.warn("Connection not initialized and cannot be committed");
}
return; // todo
}

public async rollback() {
if (this.connection) {
this.logger.debug("Rolling back current connection...");
this.connection.rollback();
this.logger.debug("Current connection was rollbacked");
} else {
this.logger.warn("Connection not initialized and cannot be rollbacked");
}
}

public async delete(options: DB.DeleteDatabaseOptions) {
let sqlDelete = sql.getDialect()//
.delete()//
.from(options.table)//
.build();

sqlDelete = sqlDelete + ` WHERE ${options.where}`;
return this.executeUpdate(sqlDelete);
}

public async update(options: DB.UpdateDatabaseOptions) {
let sqlUpdate = sql.getDialect()//
.update()//
.table(options.table)//
.build();

sqlUpdate = sqlUpdate + ` SET ${options.set.join(", ")} WHERE ${options.where}`;
return this.executeUpdate(sqlUpdate);
}

public async insert(options: DB.InsertDatabaseOptions) {
const insertBuilder = sql.getDialect()//
.insert()//
.table(options.table);

options.columns.forEach((columnName: string, index: number) => {
const columnValue = options.values[index];
insertBuilder.column(columnName).value(columnValue);
});

const sqlInsert = insertBuilder.build();
return this.executeUpdate(sqlInsert);
}

private executeUpdate(sql: string) {
this.logger.debug("Executing [{}]...", sql);

try {
const affectedRows = update.execute(sql);
this.logger.debug("Affected [{}] rows by executing [{}]", affectedRows, sql);

return this.createCRUDResult(affectedRows, 0)
} catch (error) {
this.logger.error(`Failed to execute [{}]. Error: [{}]`, sql, error);
return this.createErrorCRUDResult();
}
}

private createErrorCRUDResult() {
return this.createCRUDResult(0, 4);
}

private createCRUDResult(affectedRows: number, error: number) {
return {
dbcnt: affectedRows,
subrc: error
}
}

public async select(options: DB.SelectDatabaseOptions) {
let selectSQL = this.convertInputSelect(options.select, options.primaryKey);
this.logger.debug("Executing select [{}]...", selectSQL);

try {
const resultSet = query.execute(selectSQL);
const selectDatabaseResult = { rows: resultSet };
this.logger.debug("Result of select [{}]: [{}]", selectSQL, JSON.stringify(selectDatabaseResult));

return selectDatabaseResult;
} catch (error) {
this.logger.error(`Failed to execute [${selectSQL}]. Error: [${error}]`, error);
// @ts-ignore
if (abap.Classes["CX_SY_DYNAMIC_OSQL_SEMANTICS"] !== undefined) {
// @ts-ignore
throw await new abap.Classes["CX_SY_DYNAMIC_OSQL_SEMANTICS"]().constructor_({ sqlmsg: error.message || "" });
}
throw error;
}
}

private convertInputSelect(select: string, primaryKeys: string[] | undefined) {
let convertedSelect = select.replace(/ UP TO (\d+) ROWS(.*)/i, "$2 LIMIT $1");
if (primaryKeys) {
convertedSelect = convertedSelect.replace(/ ORDER BY PRIMARY KEY/i, " ORDER BY " + primaryKeys.join(", "));
} else {
convertedSelect = convertedSelect.replace(/ ORDER BY PRIMARY KEY/i, "");
}
convertedSelect = convertedSelect.replace(/ ASCENDING/ig, " ASC");
convertedSelect = convertedSelect.replace(/ DESCENDING/ig, " DESC");
convertedSelect = convertedSelect.replace(/~/g, ".");

return convertedSelect;
}

public async openCursor(options: DB.SelectDatabaseOptions): Promise<DB.DatabaseCursorCallbacks> {
if (!this.connection) {
const errorMessage = "Connection is not initialized. Consider calling connect method first.";
this.logger.error(errorMessage);
throw new Error(errorMessage);
}

const statement = this.connection.createStatement();

const selectSQL = options.select;
this.logger.debug("Executing [{}]...", selectSQL);

const resultSet = statement.executeQuery(selectSQL);

return {
fetchNextCursor: (packageSize: number) => this.fetchNextCursor.bind(this)(packageSize, resultSet),
closeCursor: () => this.closeCursor.bind(this)(resultSet),
};
}

private async fetchNextCursor(packageSize: number, resultSet: any): Promise<DB.SelectDatabaseResult> {
this.logger.debug("Fetching next cursor...");
const stringify = false;
const resultSetJson = DatabaseResultSetHelper.toJson(resultSet, packageSize, stringify);

const selectDatabaseResult = {
rows: JSON.parse(resultSetJson)
};
this.logger.debug("Retrieved data [{}]", JSON.stringify(selectDatabaseResult));

return selectDatabaseResult;
}

private async closeCursor(resultSet: any): Promise<void> {
if (resultSet && !resultSet.isClosed()) {
this.logger.debug("Closing result set...")
resultSet.close();
this.logger.debug("Result set was closed")
} else {
this.logger.warn("Result set is not defined or it is already closed.")
}
}
}
2 changes: 1 addition & 1 deletion build-mac.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ rm -rf dist
npm run transpile
npm run lint
find . -name '*.mjs' -print0 | xargs -0 sed -i '' 's/%23/#/g'
esbuild run.mjs --bundle --outdir=dist --format=esm --target=es2022 --external:tls --external:net --external:util --external:util/types --external:crypto --external:zlib --external:http --external:https --external:fs --external:path --external:url --external:sdk/http --inject:./polyfills/buffer.js --inject:./polyfills/process.js --out-extension:.js=.mjs
esbuild run.mjs --bundle --outdir=dist --format=esm --target=es2022 --external:tls --external:net --external:util --external:util/types --external:crypto --external:zlib --external:http --external:https --external:fs --external:path --external:url --external:sdk --inject:./polyfills/buffer.js --inject:./polyfills/process.js --out-extension:.js=.mjs
28 changes: 28 additions & 0 deletions db/employee.table
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"name": "employees",
"type": "TABLE",
"columns": [
{
"name": "id",
"type": "INTEGER",
"length": "0",
"nullable": "false",
"primaryKey": "true",
"defaultValue": ""
},
{
"name": "firstName",
"type": "VARCHAR",
"length": "50",
"nullable": "false",
"primaryKey": "false"
},
{
"name": "lastName",
"type": "VARCHAR",
"length": "50",
"nullable": "false",
"primaryKey": "false"
}
]
}
9 changes: 9 additions & 0 deletions run.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { initializeABAP } from './output/init.mjs';
import { zcl_hello_world } from './output/zcl_hello_world.clas.mjs';

import { DirigibleDatabaseClient } from "./DirigibleDatabaseClient";

function initDefaultDataSource() {
console.log("!!! Init default data source for ABAP...");
abap.context.databaseConnections["DEFAULT"] = new DirigibleDatabaseClient();
console.log("!!! Default data source for ABAP was initialized");
}
await initializeABAP();
initDefaultDataSource();

await zcl_hello_world.run();

43 changes: 43 additions & 0 deletions src/zcl_dirigible_employee_dao.clas.abap
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
CLASS zcl_dirigible_employee_dao DEFINITION PUBLIC.

PUBLIC SECTION.

TYPES:
BEGIN OF ty_employee,
id TYPE n LENGTH 10,
firstName TYPE string,
lastName TYPE string,
END OF ty_employee,
ty_employees TYPE TABLE OF ty_employee WITH EMPTY KEY.

CLASS-METHODS:
select_all
RETURNING VALUE(rv_result) TYPE string.

ENDCLASS.

CLASS zcl_dirigible_employee_dao IMPLEMENTATION.

METHOD select_all.
DATA: lt_employees TYPE ty_employees,
ls_employee TYPE ty_employee,
lv_result TYPE string.

SELECT id, firstName, lastName
FROM employees
INTO TABLE lt_employees.

LOOP AT lt_employees INTO ls_employee.
CONCATENATE lv_result
'ID:' ls_employee-id
' First Name:' ls_employee-firstName
' Last Name:' ls_employee-lastName
cl_abap_char_utilities=>cr_lf
INTO lv_result
SEPARATED BY space.
ENDLOOP.

rv_result = lv_result.
ENDMETHOD.

ENDCLASS.
30 changes: 28 additions & 2 deletions src/zcl_hello_world.clas.abap
Original file line number Diff line number Diff line change
@@ -1,12 +1,38 @@
CLASS zcl_hello_world DEFINITION PUBLIC FINAL CREATE PUBLIC.

PUBLIC SECTION.
CLASS-METHODS run.

PRIVATE SECTION.
DATA: lo_employee_dao TYPE REF TO zcl_dirigible_employee_dao.

ENDCLASS.

CLASS zcl_hello_world IMPLEMENTATION.

METHOD run.
DATA: lv_result TYPE string.

CREATE OBJECT lo_employee_dao.

zcl_dirigible_response=>println(
EXPORTING
message_in = 'hello world' ).
message_in = 'Hello world!' ).

zcl_dirigible_response=>println(
EXPORTING
message_in = 'Selecting all employees...' ).

lv_result = lo_employee_dao->select_all( ).

zcl_dirigible_response=>println(
EXPORTING
message_in = 'Employees selected.' ).

zcl_dirigible_response=>println(
EXPORTING
message_in = 'Employee Data:'
data_in = lv_result ).
ENDMETHOD.
ENDCLASS.

ENDCLASS.
Loading
Loading