Files
gala-ki-spielwiese/database/docs/plan_pck_net_storage.md

11 KiB

Plan: PCK_NET_STORAGE

Stand: 2026-04-08 Status: Implementiert


Ziel

PL/SQL Package PCK_NET_STORAGE kapselt alle Zugriffe auf OCI Object Storage über REST API. Wird in Oracle APEX für einen Dateimanager genutzt (Browse, Upload, Download, Rename, Move, Delete).


Dateien

tables/
  lg_app_log.tab

types/
  t_net_storage_row.typ     ← Schema-Level Type (siehe Hinweis)
  t_net_storage_tab.typ     ← Nested Table von t_net_storage_row

packages/
  pck_log.pkh / pck_log.pkb
  pck_net_storage.pkh / pck_net_storage.pkb
  pck_auto_import.pkh / pck_auto_import.pkb

ords/
  net_storage_process_incoming.sql

Hinweis Schema-Level Types: TABLE() in SQL erfordert in Oracle schema-level Types. t_net_storage_row / t_net_storage_tab werden ausschließlich intern für den sys_refcursor von f_list_objects benötigt — keine Auswirkung auf die öffentliche API.


Authentifizierung

APEX Web Credentials (Typ: Oracle Cloud Infrastructure) Static ID: OCI_OBJ_STORE_CRED Wird in APEX Admin UI einmalig angelegt mit: OCI User OCID, Tenancy OCID, Fingerprint, Private Key (PEM). In PL/SQL referenziert via p_credential_static_id => c_credential_id in apex_web_service.make_rest_request.

Grund: DBMS_CLOUD ist auf dieser DB nicht installiert. APEX übernimmt OCI HTTP Signature V1 (SHA-256) automatisch.


Konfigurationsparameter

Alle zur Laufzeit via pck_system.f_get_par_wert_by_programmid:

Parameter-ID Inhalt
NET_STORAGE_BUCKET Bucket-Name
NET_STORAGE_NAMESPACE OCI Object Storage Namespace
NET_STORAGE_REGION z.B. eu-frankfurt-1
NET_STORAGE_TENANT_ID Erlaubter Root-Prefix, z.B. mandant_42/
NET_STORAGE_APEX_CREDENTIAL_ID Credential Static ID für apex_web_service calls, z.B. 'OCI_OBJ_STORE_CRED'

Öffentliche Schnittstelle (Package Spec)

Record Type t_object_meta

Feld Typ
object_name varchar2(1024)
object_size number
last_modified date
content_type varchar2(256)
etag varchar2(256)

Öffentliche Prozeduren und Funktionen

Namenskonvention: Prozeduren p_-Präfix, Funktionen f_-Präfix. Parameter immer i_-Präfix.

Name Typ Signatur Rückgabe Anmerkung
f_list_objects Funktion (i_prefix, i_delimiter, i_start_with, i_limit) sys_refcursor Cursor-Spalten: object_name, object_size, last_modified, is_folder(Y/N), etag
f_download_object Funktion (i_object_key) blob
p_upload_object Prozedur (i_object_key, i_content blob, i_content_type)
p_delete_object Prozedur (i_object_key)
p_delete_folder Prozedur (i_prefix) intern: f_list_objects-Loop + p_delete_object
p_rename_object Prozedur (i_object_key, i_new_name) intern: OCI RenameObject API
p_move_object Prozedur (i_object_key, i_target_prefix) intern: OCI RenameObject API
p_create_folder Prozedur (i_prefix, i_folder_name) PUT leeres Objekt mit trailing /
f_get_object_metadata Funktion (i_object_key) t_object_meta HEAD Request

Rechteprüfungen:

Zu beginn von delete-operationen muss folgender Aufruf durchgeführt werden, um die Rechte des nutzers zu prüfen: pck_mitarbeiterrecht.p_hat_recht('ADMIN');

Bei anderen schreib-Operationen, wie rename oder move muss die Prüfung von folgendem Recht erfolgen: pck_mitarbeiterrecht.p_hat_recht('SCHREIBEN_ALLES');

Bei lese-Operationen wie f_get_object_metadata und f_download_object muss folgendes Recht geprüft werden:

pck_mitarbeiterrecht.p_hat_recht('LESEN_ALLES');

Schlägt eine Prüfung fehl, wirft die p_hat_recht Prozedur einen speziellen Application Error mit Fehlermeldung für den Benutzer.


Private Helpers (nur Body)

Name Typ Zweck
f_get_cfg Funktion Wrapper für pck_system.f_get_par_wert_by_programmid
f_build_url Funktion Baut vollständige OCI REST URL (/n/{ns}/b/{bucket}/o/{key})
p_assert_allowed Prozedur Tenant-Prefix-Guard + Path-Traversal-Prüfung
f_make_request Funktion Wrapper für apex_web_service.make_rest_request, wertet HTTP-Status aus, gibt Response-Body zurück

Pagination (f_list_objects)

OCI liefert max. 1000 Objekte pro Request. f_list_objects ruft OCI intern in einer Schleife auf (nextStartWith-Token aus JSON-Response) bis alle Seiten geladen sind. Ergebnis wird in einer lokalen Collection gesammelt, am Ende als sys_refcursor geöffnet. i_limit = 0 → unbegrenzt, i_limit > 0 → Abbruch wenn Limit erreicht.

OCI-Response trennt Unterordner (prefixes-Array) von Dateien (objects-Array) — beide werden eingesammelt, Ordner mit is_folder = 'Y'.


Fehlerbehandlung

f_make_request wirft bei HTTP-Fehler:

HTTP-Status Fehlercode Meldung
404 -20001 Object not found: {key}
401 / 403 -20002 OCI authentication failed
409 -20007 Object already exists: {key}
sonstige 4xx/5xx -20006 OCI API error {status}: {response body}

p_assert_allowed wirft:

Fall Fehlercode Meldung
.. im Pfad -20004 Path traversal attempt detected
Pfad außerhalb Tenant-Prefix -20005 Access denied: outside tenant scope

Bucket nicht erreichbar → -20003 via f_make_request


Sicherheit / Tenant-Isolation

p_assert_allowed wird am Anfang jeder öffentlichen Funktion/Prozedur aufgerufen:

  1. Tenant-Prefix laden via f_get_cfg('NET_STORAGE_TENANT_ID')
  2. ..-Sequenzen im übergebenen Key prüfen → -20004
  3. Prüfen ob Key mit Tenant-Prefix beginnt → -20005

Dateieingang-Verarbeitung

OCI-Struktur

Der Quarkus Automaton legt Dateien in einem Unterordner ab, der nach der ZIP-Datei benannt ist. Unterordner innerhalb der ZIP werden beibehalten:

eingang/<zip-name>/datei1.csv
eingang/<zip-name>/unterordner/datei3.csv
eingang/<zip-name>/_READY_FOR_DB_PROCESSING_    ← Marker

Marker-Datei

Der Quarkus Automaton legt nach erfolgreichem Upload aller Dateien eine leere Marker-Datei ab: eingang/<zip-name>/_READY_FOR_DB_PROCESSING_

Die DB verarbeitet einen Unterordner ausschließlich wenn der Marker vorhanden ist. Der Marker wird erst gelöscht wenn alle Dateien des Unterordners erfolgreich verarbeitet wurden — so werden fehlgeschlagene Dateien beim nächsten Lauf erneut versucht.

Prozedur p_process_incoming_files (in pck_auto_import, nicht in pck_net_storage)

1. Unterordner von eingang/ auflisten (f_list_objects mit delimiter='/')
2. Für jeden Unterordner:
   a. Marker eingang/<zip-name>/_READY_FOR_DB_PROCESSING_ vorhanden?
      → nein: überspringen (unvollständiger Upload)
   b. Alle Dateien in eingang/<zip-name>/ auflisten (ohne Marker)
   c. Für jede Datei einzeln:
      begin
          Fachliche Verarbeitung (Import) — noch kein Commit
          log_object_ref := 'eingang/<zip-name>/datei.csv'  ← ZIP aus Pfad ableitbar
          pck_net_storage.p_move_object(datei → Zielordner/<zip-name>/)
          commit
          pck_log.p_info(...)
      exception when others then
          rollback
          pck_log.p_error(i_object_ref => 'eingang/<zip-name>/datei.csv', ...)
      end
   d. Noch Dateien in eingang/<zip-name>/ (außer Marker)?
      → nein: pck_net_storage.p_delete_object(Marker) — Batch abgeschlossen
      → ja:  Marker bleibt — nächster Lauf versucht verbleibende Dateien

Rollback nach fehlgeschlagenem p_move_object macht auch den Import rückgängig. Kein Verhalten wird aus lg_app_log abgeleitet — es dient ausschließlich dem Audit.

ORDS-Endpunkt

POST /ords/.../net_storage/process_incoming → ruft p_process_incoming_files auf. Wird von n8n nach erfolgreichem Upload aufgerufen. Schlägt der Aufruf fehl: APEX Automation greift beim nächsten Stundenlauf.


APEX REST Data Source (manuell einrichten)

Für das APEX Interactive Report im Dateimanager-Browser:

Name:              NET_STORAGE_LIST_OBJECTS
Base-URL:          https://objectstorage.{region}.oraclecloud.com
Endpoint:          /n/{namespace}/b/{bucket}/o
Method:            GET
Web Credential:    OCI_OBJ_STORE_CRED

URL-Parameter:
  prefix      → aktueller Ordner-Prefix (Bind-Variable aus APEX)
  delimiter   → /
  limit       → 1000
  start       → für Pagination (optional)

Spalten-Mapping:
  objects[*].name           → OBJECT_NAME
  objects[*].size           → OBJECT_SIZE
  objects[*].timeModified   → LAST_MODIFIED
  objects[*].etag           → ETAG

Hinweis: prefixes (Unterordner) und objects (Dateien) sind zwei separate JSON-Arrays. Für ein gemeinsames IR entweder zwei REST Sources mit UNION, oder f_list_objects über eine APEX Collection als IR-Quelle nutzen.


Logging: lg_app_log + pck_log

Tabelle lg_app_log

Allgemeine Anwendungs-Log-Tabelle, modulübergreifend nutzbar. Keine Indizes (Log-Volumen ist gering, Ad-hoc-Suche reicht ohne Index).

Spalte Typ Pflicht Beschreibung
lg_app_log_id number generated by default as identity ja PK
log_timestamp timestamp ja Zeitpunkt des Log-Eintrags
log_level varchar2(10 char) ja INFO, WARN, ERROR
log_module varchar2(100 char) ja z.B. PCK_NET_STORAGE
log_action varchar2(100 char) nein z.B. PROCESS_FILE
log_object_ref varchar2(512 char) nein z.B. eingang/datei.csv
log_message varchar2(4000 char) nein Kurzmeldung
log_detail clob nein Stack Trace, JSON, etc.
log_user varchar2(100 char) nein APEX- oder DB-User
log_session_id number nein Oracle Session-ID

Keine Audit-Spalten (Log-Tabelle ist self-auditing durch log_timestamp + log_user).

Package pck_log

Öffentliche Prozeduren:

Name Signatur
p_info (i_module, i_action, i_message, i_object_ref default null)
p_warn (i_module, i_action, i_message, i_object_ref default null)
p_error (i_module, i_action, i_message, i_detail default null, i_object_ref default null)

Intern immer PRAGMA AUTONOMOUS_TRANSACTION + COMMIT — Log-Einträge sind transaktionsunabhängig und überleben Rollbacks der Haupttransaktion.


OCI API Endpunkte (Referenz)

Operation Method Pfad
List Objects GET /n/{ns}/b/{bucket}/o?prefix=...&delimiter=/
Get Object GET /n/{ns}/b/{bucket}/o/{objectName}
Put Object PUT /n/{ns}/b/{bucket}/o/{objectName}
Delete Object DELETE /n/{ns}/b/{bucket}/o/{objectName}
Head Object HEAD /n/{ns}/b/{bucket}/o/{objectName}
Rename Object POST /n/{ns}/b/{bucket}/actions/renameObject