Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
8471be6
wip
larshp Dec 3, 2025
e5afcc3
postgres
larshp Dec 3, 2025
0b8e6d2
update
larshp Dec 6, 2025
461135b
wip
larshp Dec 6, 2025
8c7c529
add table
larshp Dec 6, 2025
59c54b8
wip
larshp Dec 6, 2025
28512cc
wip
larshp Dec 6, 2025
ec2f272
fix
larshp Dec 6, 2025
78b3abb
wip
larshp Dec 6, 2025
d3a0a6e
fix
larshp Dec 6, 2025
f217b29
move
larshp Dec 7, 2025
2f983a3
add test stuff
larshp Dec 7, 2025
e8db95c
fix
larshp Dec 7, 2025
c8a2f89
update
larshp Dec 7, 2025
c3df296
update
larshp Dec 7, 2025
c9a8c41
update
larshp Dec 7, 2025
e70d0af
inject
larshp Dec 7, 2025
ddc42d3
update
larshp Dec 7, 2025
8d6ffc1
update
larshp Dec 7, 2025
ada519c
wip
larshp Dec 7, 2025
15a2d0d
wip
larshp Dec 7, 2025
a67e0df
wip
larshp Dec 7, 2025
be6a12e
update
larshp Dec 7, 2025
8dd7a9e
update
larshp Dec 7, 2025
e36857a
fix
larshp Dec 7, 2025
0437e2c
something like this
larshp Dec 7, 2025
ca2b874
update
larshp Dec 7, 2025
e12aab3
add
larshp Dec 7, 2025
dbc78df
add testcases
larshp Dec 7, 2025
5d899b6
fix
larshp Dec 7, 2025
5d9f90b
wip
larshp Dec 7, 2025
7df33c2
wip
larshp Dec 7, 2025
0560640
refactor
larshp Dec 7, 2025
e69ef5d
call build key
larshp Dec 7, 2025
787f7d2
fix
larshp Dec 7, 2025
666f77e
wip
larshp Dec 7, 2025
c5cd1a3
foo
larshp Dec 8, 2025
24a1f6c
cleanup
larshp Dec 8, 2025
f038b4b
cleanup
larshp Dec 8, 2025
a2b3b8c
fix
larshp Dec 8, 2025
a20e5fc
fix
larshp Dec 8, 2025
5d45607
cleanup
larshp Dec 8, 2025
6a43e02
wip
larshp Dec 8, 2025
9a64133
fix
larshp Dec 8, 2025
2da5d75
rename
larshp Dec 8, 2025
c8e68e9
wip
larshp Dec 8, 2025
d38c08f
update
larshp Dec 8, 2025
e3c0314
Update README.md
larshp Dec 8, 2025
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
21 changes: 21 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: test

on:
pull_request:
push:
branches:
- main

permissions:
contents: read

jobs:
unit:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm run docker:start
- run: npm install
- run: npm test
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
output
node_modules
package-lock.json
output_test
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ignore-scripts=true
22 changes: 21 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,22 @@
# open-abap-lock
lock

Concurrent/cross session locking for Open ABAP.

Requires and works with PostgreSQL as the database backend, and only on the DEFAULT connection.

## Notes

* [PostgreSQL - Advisory Locks](https://www.postgresql.org/docs/current/explicit-locking.html#ADVISORY-LOCKS)
* [PostgreSQL - Advisory Lock Functions](https://www.postgresql.org/docs/9.1/functions-admin.html#FUNCTIONS-ADVISORY-LOCKS)

wildcards?
scope?
release on crash
table `pg_locks`

## Todo

* `_scope`
* `wait` flag
* `mode`
* release at commit work if update task
25 changes: 25 additions & 0 deletions abap_transpile.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"input_folder": ["src", "test/src"],
"input_filter": [],
"output_folder": "output",
"write_unit_tests": true,
"write_source_map": true,
"libs": [
{
"url": "https://github.com/open-abap/open-abap-core"
}
],
"options": {
"ignoreSyntaxCheck": false,
"addFilenames": true,
"addCommonJS": true,
"populateTables": {
"reposrc": false
},
"setup": {
"filename": "../test/setup.mjs",
"postFunction": "postFunction",
"preFunction": "preFunction"
}
}
}
29 changes: 8 additions & 21 deletions abaplint.jsonc
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"global": {
"files": "/src/**/*.*"
"files": [
"/src/**/*.*",
"/test/src/**/*.*"
]
},
"dependencies": [
{
Expand Down Expand Up @@ -130,7 +133,7 @@
"exportDynpro": true,
"dynamicSQL": true
},
"db_operation_in_loop": true,
"db_operation_in_loop": false,
"definitions_top": true,
"description_empty": false,
"double_space": {
Expand Down Expand Up @@ -308,25 +311,9 @@
"checkForms": true
},
"method_overwrites_builtin": true,
"method_parameter_names": {
"exclude": [],
"severity": "Error",
"patternKind": "required",
"ignoreNames": [],
"ignorePatterns": [],
"ignoreExceptions": true,
"importing": "^I._.+$",
"returning": "^R._.+$",
"changing": "^C._.+$",
"exporting": "^E._.+$"
},
"method_parameter_names": false,
"mix_returning": true,
"modify_only_own_db_tables": {
"exclude": [],
"severity": "Error",
"reportDynamic": true,
"ownTables": "^[yz]"
},
"modify_only_own_db_tables": false,
"msag_consistency": {
"exclude": [],
"severity": "Error",
Expand Down Expand Up @@ -548,7 +535,7 @@
"unused_types": false,
"unused_variables": false,
"use_bool_expression": true,
"use_class_based_exceptions": true,
"use_class_based_exceptions": false,
"use_line_exists": true,
"use_new": true,
"when_others_last": true,
Expand Down
20 changes: 20 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"name": "open-abap-lock",
"version": "1.0.0",
"private": true,
"description": "test",
"scripts": {
"lint": "abaplint",
"unit": "rm -rf output && abap_transpile abap_transpile.jsonc && echo RUNNING && node output/index.mjs",
"docker:start": "docker compose -p open-abap-lock -f test/stack.yml up -d",
"docker:stop": "docker compose -p open-abap-lock -f test/stack.yml down -v",
"test": "npm run lint && npm run unit"
},
"license": "",
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The license field is empty. This should either specify a license (e.g., "MIT", "Apache-2.0") or be set to "UNLICENSED" if the code is proprietary. An empty string is not a valid license identifier.

Suggested change
"license": "",
"license": "UNLICENSED",

Copilot uses AI. Check for mistakes.
"dependencies": {
"@abaplint/cli": "^2.114.6",
"@abaplint/runtime": "^2.12.16",
"@abaplint/database-pg": "^2.11.78",
"@abaplint/transpiler-cli": "^2.12.16"
}
}
124 changes: 124 additions & 0 deletions src/kernel_lock_concurrent.clas.abap
Original file line number Diff line number Diff line change
@@ -1,7 +1,131 @@
CLASS kernel_lock_concurrent DEFINITION PUBLIC.
PUBLIC SECTION.
CLASS-METHODS class_constructor.

CLASS-METHODS enqueue
IMPORTING
input TYPE any
table_name TYPE string
enqueue_name TYPE string
EXCEPTIONS
foreign_lock
system_failure.

CLASS-METHODS dequeue
IMPORTING
table_name TYPE string
enqueue_name TYPE string
input TYPE any.

TYPES: BEGIN OF ty_cleanup,
valid_locks TYPE i,
cleaned_locks TYPE i,
END OF ty_cleanup.
CLASS-METHODS cleanup_locks
RETURNING
VALUE(rs_result) TYPE ty_cleanup.
PRIVATE SECTION.
CLASS-METHODS build_lock_key
IMPORTING
input TYPE any
table_name TYPE string
RETURNING
VALUE(rv_lock_key) TYPE kernel_locks-lock_key.
ENDCLASS.

CLASS kernel_lock_concurrent IMPLEMENTATION.

METHOD class_constructor.
cleanup_locks( ).
ENDMETHOD.

METHOD cleanup_locks.
SELECT * FROM kernel_locks INTO TABLE @DATA(lt_locks) ORDER BY PRIMARY KEY ##SUBRC_OK.
LOOP AT lt_locks INTO DATA(ls_lock).
DATA(lv_exists) = lcl_advisory=>exists( lcl_key=>encode( ls_lock-lock_key ) ).
IF lv_exists = abap_true.
rs_result-valid_locks = rs_result-valid_locks + 1.
ELSE.
DELETE FROM kernel_locks WHERE table_name = @ls_lock-table_name AND lock_key = @ls_lock-lock_key.
rs_result-cleaned_locks = rs_result-cleaned_locks + 1.
ENDIF.
ENDLOOP.
ENDMETHOD.

METHOD build_lock_key.

DATA lr_dref TYPE REF TO data.
DATA lo_structdescr TYPE REF TO cl_abap_structdescr.
DATA lv_string TYPE string.

FIELD-SYMBOLS <lg_row> TYPE any.

CREATE DATA lr_dref TYPE (table_name).
ASSIGN lr_dref->* TO <lg_row>.

lo_structdescr ?= cl_abap_typedescr=>describe_by_data( <lg_row> ).
ASSERT lo_structdescr IS NOT INITIAL.

LOOP AT lo_structdescr->components INTO DATA(ls_component).
WRITE '@KERNEL lv_string.set(input[ls_component.get().name.get().toLowerCase().trimEnd()] || "");'.
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using @KERNEL directive with JavaScript/TypeScript code embedded in ABAP is unusual and may be fragile. Consider documenting this transpiler-specific feature or finding a more standard approach if possible.

Suggested change
WRITE '@KERNEL lv_string.set(input[ls_component.get().name.get().toLowerCase().trimEnd()] || "");'.
" Dynamically get the value from input structure for the current component
FIELD-SYMBOLS <lv_input_field> TYPE any.
ASSIGN COMPONENT ls_component-name OF STRUCTURE input TO <lv_input_field>.
IF sy-subrc = 0 AND <lv_input_field> IS NOT INITIAL.
lv_string = |{ <lv_input_field> }|.
ELSE.
lv_string = ''.
ENDIF.

Copilot uses AI. Check for mistakes.

ASSIGN COMPONENT ls_component-name OF STRUCTURE <lg_row> TO FIELD-SYMBOL(<lv_row_field>).
ASSERT sy-subrc = 0.
<lv_row_field> = lv_string.
ENDLOOP.

rv_lock_key = <lg_row>.

ENDMETHOD.

METHOD enqueue.

DATA ls_lock_row TYPE kernel_locks.

*******************

DATA(lv_lock_key) = build_lock_key(
input = input
table_name = table_name ).
ASSERT lv_lock_key IS NOT INITIAL.

ls_lock_row-table_name = table_name.
ls_lock_row-lock_key = lv_lock_key.
ls_lock_row-username = sy-uname.
GET TIME STAMP FIELD ls_lock_row-timestamp.
ls_lock_row-hostname = sy-host.
ls_lock_row-lock_mode = ''.
ls_lock_row-lock_name = enqueue_name.

TRY.
lcl_advisory=>lock( lcl_key=>encode( ls_lock_row-lock_key ) ).
CATCH lcx_advisory_lock_failed.
RAISE foreign_lock.
ENDTRY.

INSERT kernel_locks FROM @ls_lock_row.
ASSERT sy-subrc = 0.

ENDMETHOD.

METHOD dequeue.

DATA(lv_lock_key) = build_lock_key(
input = input
table_name = table_name ).

TRY.
lcl_advisory=>lock( lcl_key=>encode( lv_lock_key ) ).
CATCH lcx_advisory_lock_failed.
" it doesnt have the lock, or another session has the lock
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment on line 120 says "it doesnt have the lock" but the logic is attempting to acquire the lock in the dequeue method. This is confusing - the dequeue method should release a lock, not attempt to acquire one. The comment should clarify why acquiring a lock is necessary during dequeue, or this may indicate a logic issue.

Suggested change
" it doesnt have the lock, or another session has the lock
" Unable to acquire the lock: either this session does not hold the lock, or another session currently holds it.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Grammar/spelling error in comment: "it doesnt" should be "it doesn't" (missing apostrophe).

Suggested change
" it doesnt have the lock, or another session has the lock
" it doesn't have the lock, or another session has the lock

Copilot uses AI. Check for mistakes.
RETURN.
ENDTRY.

DELETE FROM kernel_locks WHERE table_name = table_name AND lock_key = lv_lock_key.
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DELETE statement uses unquoted variables in the WHERE clause which may not be bound correctly. Consider using @table_name and @lv_lock_key to ensure proper parameter binding.

Suggested change
DELETE FROM kernel_locks WHERE table_name = table_name AND lock_key = lv_lock_key.
DELETE FROM kernel_locks WHERE table_name = @table_name AND lock_key = @lv_lock_key.

Copilot uses AI. Check for mistakes.

" advisory locks stack,
lcl_advisory=>unlock( lcl_key=>encode( lv_lock_key ) ).
lcl_advisory=>unlock( lcl_key=>encode( lv_lock_key ) ).
Copy link

Copilot AI Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comment says "advisory locks stack" but calling unlock twice suggests each lock operation may need to be balanced. However, in the dequeue method, only one lock is acquired (line 118) but two unlock calls are made. This appears to be an off-by-one error that could cause issues with the lock state.

Suggested change
lcl_advisory=>unlock( lcl_key=>encode( lv_lock_key ) ).

Copilot uses AI. Check for mistakes.
ENDMETHOD.

ENDCLASS.
Loading