<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Jakub Dobruchowski's blog]]></title><description><![CDATA[Jakub Dobruchowski's blog]]></description><link>https://blog.f100.zip</link><generator>RSS for Node</generator><lastBuildDate>Sun, 13 Sep 2026 09:10:24 GMT</lastBuildDate><atom:link href="https://blog.f100.zip/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Apex 26.1 Nice token limit error]]></title><description><![CDATA[This one caught me with my guard down.
While testing NL2IR and the built-in Token Limit, I kept hitting this message:


"An unexpected error occurred." Not very helpful.
I looked at the AI response ha]]></description><link>https://blog.f100.zip/apex-26-1-nice-token-limit-error</link><guid isPermaLink="true">https://blog.f100.zip/apex-26-1-nice-token-limit-error</guid><category><![CDATA[#oracle-apex]]></category><category><![CDATA[APEX-26.1]]></category><category><![CDATA[Oracle Apex AI]]></category><category><![CDATA[AI]]></category><category><![CDATA[Oracle]]></category><category><![CDATA[Oracle Database]]></category><category><![CDATA[oracle-database-26ai]]></category><dc:creator><![CDATA[Jakub Dobruchowski]]></dc:creator><pubDate>Thu, 28 May 2026 18:57:37 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/385026b0-8799-443a-a054-6c08df53a4f0.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This one caught me with my guard down.</p>
<p>While testing NL2IR and the built-in Token Limit, I kept hitting this message:</p>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/fdec8291-eb75-4b13-bc4f-14a329526cb5.png" alt="" style="display:block;margin:0 auto" />

<p>"An unexpected error occurred." Not very helpful.</p>
<p>I looked at the AI response handler, tried a few things, and eventually gave up. I assumed there was some internal code that displays this whenever anything goes wrong during the LLM processing chain. Nothing can be done. Maybe a future release will add customization options.</p>
<p>Oh how wrong I was. This is APEX - everything is standardized. What we are seeing here is just an ORA error, specifically <code>ORA-20962</code> with the APEX error code <code>APEX.AI.ERROR</code>. And ORA errors in APEX can be handled with the standard error handling function.</p>
<h2>Reproducing the error</h2>
<p>Set up a service that will fail immediately:</p>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/6440ad81-d37d-40d7-8803-1704ee018d3e.png" alt="" style="display:block;margin:0 auto" />

<p>The important part is Maximum AI Token Limit - set it to 0.</p>
<p>Now go to Edit Application Definition → AI and assign this service.</p>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/529b0dcc-83be-4043-9b9f-b834f8f2bff7.png" alt="" style="display:block;margin:0 auto" />

<p>Now all NL2IR and Assistant features in this app will fail with the "unexpected error" message. But it is not unexpected, and it is not really an error - it is a token limit check doing exactly what it should. So how do we give the user a proper message?</p>
<h2>The fix</h2>
<p>Create an error handling function:</p>
<pre><code class="language-sql">create or replace function log_apex_error(
    p_error in apex_error.t_error
) return apex_error.t_error_result
as
    l_result apex_error.t_error_result;
begin
    l_result := apex_error.init_error_result(p_error =&gt; p_error);

    -- Handle AI token limit error with a user-friendly message
    if p_error.ora_sqlcode = -20962
       and p_error.apex_error_code = 'APEX.AI.ERROR'
    then
        l_result.message          := 'You have exceeded your available AI tokens. Please contact your administrator.';
        l_result.display_location := apex_error.c_inline_in_notification;
    end if;

    return l_result;
end log_apex_error;
/
</code></pre>
<p>The function hooks into the standard APEX error pipeline. It only modifies the message for that specific ORA code and passes everything else through unchanged.</p>
<p>Now add it to Edit Application Definition → Error Handling → Error Handling Function:</p>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/006262d8-be39-4a87-9d82-c2f04feeb425.png" alt="" style="display:block;margin:0 auto" />

<p>And that is it.</p>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/1d06866f-b45d-4c24-8b43-a61f7f2924b9.png" alt="" style="display:block;margin:0 auto" />

<p>I actually figured this out during an <a href="https://asktom.oracle.com/ords/r/tech/catalog/series-landing-page?p5_series_id=335553533721783363839941154610061380645">APEX Office Hours</a> session - someone else asked a related question and it clicked. If you are not joining those sessions, you should. Half the time the best insights come from other people's problems.</p>
<p>The same pattern works for any AI-related ORA error APEX might throw. If Oracle adds new error codes for other AI limit scenarios, you just add another condition to the same function.</p>
]]></content:encoded></item><item><title><![CDATA[Reducing Oracle Apex AI Costs]]></title><description><![CDATA[Natural Language to IR in Oracle APEX 26.1 is great. It is also expensive.
When I looked at the requests going out for NL2IR, a normal call was around 17k tokens. The important part is that roughly 15]]></description><link>https://blog.f100.zip/reducing-oracle-apex-ai-costs</link><guid isPermaLink="true">https://blog.f100.zip/reducing-oracle-apex-ai-costs</guid><category><![CDATA[orclapex]]></category><category><![CDATA[Oracle]]></category><category><![CDATA[AI]]></category><category><![CDATA[#anthropic]]></category><category><![CDATA[Apex]]></category><category><![CDATA[#oracle-apex]]></category><dc:creator><![CDATA[Jakub Dobruchowski]]></dc:creator><pubDate>Fri, 22 May 2026 13:24:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/c15e08e0-e080-4f04-a538-9670c36f9072.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Natural Language to IR in Oracle APEX 26.1 is great. It is also expensive.</p>
<p>When I looked at the requests going out for NL2IR, a normal call was around 17k tokens. The important part is that roughly 15k of those tokens are not really changing between calls. So if we can cache that stable part of the prompt, we stop paying full price for it every single time.</p>
<p>That is exactly what this post is about.</p>
<p>We are going to route NL2IR to a dedicated Anthropic service, enable prompt caching on the static part of the prompt, and use a request handler package to move the variable structured-data section out of the system prompt.</p>
<p>One important thing up front: this specific implementation is for Anthropic.</p>
<p>The high-level idea can work with other providers too, but not in exactly the same way. This version depends on Anthropic prompt caching and on being able to shape the request so that Anthropic sees a stable prefix it can reuse.</p>
<p>What we will build:</p>
<ul>
<li><p>A dedicated AI service for NL2IR</p>
</li>
<li><p>A static system prompt stored in the service Additional Parameters</p>
</li>
<li><p>Anthropic prompt caching enabled on that static prompt</p>
</li>
<li><p>A request handler package that rewrites the NL2IR request into a cache-friendlier shape</p>
</li>
</ul>
<p>The result is that after the first request pays the initial cost, the vast majority of the prompt gets cached and reused on every subsequent call. In practice, that means every NL2IR call across your reports can benefit from the same cache.</p>
<h2>What Anthropic prompt caching actually does</h2>
<p>If you have not used Anthropic prompt caching before, the short version is simple: Anthropic can cache an identical prompt prefix and reuse it on follow-up calls.</p>
<p>So instead of sending the same big static prompt over and over again at full price, you pay once to write it into cache, and then the next requests can read that cached prefix much more cheaply.</p>
<p>The important part is this: the cached prefix must stay identical.</p>
<p>If the stable part of the prompt keeps changing, you will keep missing the cache and you will not save much. By default, the cache lives for 5 minutes, which is perfectly fine for a feature like NL2IR where users often try several queries one after another.</p>
<p>There is also an option to extend it to 1 hour, although you pay a little bit more, if you expect your usage patterns to be more sporadic.</p>
<p>If you want the full details, Anthropic has a good prompt caching guide here: <a href="https://platform.claude.com/docs/en/build-with-claude/prompt-caching">https://platform.claude.com/docs/en/build-with-claude/prompt-caching</a></p>
<p>That is why prompt shape matters here.</p>
<h2>Why NL2IR is a good candidate for caching</h2>
<p>NL2IR sends a lot of prompt data. And most of that prompt is not the user question.</p>
<p>The user might type something short like:</p>
<pre><code class="language-text">show me orders from last month
</code></pre>
<p>But the request also contains a large system prompt, instructions, metadata, and structured information about the report. That is where the token cost really comes from.</p>
<p>In my tests, the static portion of the instructions made up almost 90% of that massive payload.</p>
<p>That makes NL2IR a very good fit for prompt caching, as long as we can keep the reusable part stable.</p>
<h2>The problem with the default prompt shape</h2>
<p>The problem is that the APEX-generated system prompt does not contain only the static instructions we want to cache.</p>
<p>It also contains a <code>## Structured Data</code> section. That section is the variable part we do not want glued into the static prefix.</p>
<p>So if we just cache the raw system prompt as-is, we are not really controlling the prompt shape. What we want instead is:</p>
<ul>
<li><p>Keep the static instructions in one place</p>
</li>
<li><p>Move the structured data out of that static prompt</p>
</li>
<li><p>Keep the final request stable enough for Anthropic caching to do useful work</p>
</li>
</ul>
<p>That is what the package below does.</p>
<h2>Step 1: Create a dedicated NL2IR service</h2>
<p>Just like in my previous NL2IR post, create a dedicated AI service for Natural Language to IR.</p>
<p>I am using Anthropic and routing NL2IR to a cheaper model. The exact model is up to you, but NL2IR is a very good place to use a lower-cost model because the input is huge and the output is usually small.</p>
<p>The important part is the static ID of the service. In my package I use:</p>
<pre><code class="language-text">haiku-for-ir
</code></pre>
<p>You can call it whatever you want, but remember to update the package if you use a different static ID.</p>
<h2>Step 2: Put the static prompt into Additional Parameters</h2>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/9b8c268e-28a9-4c8d-bf00-05860902df40.png" alt="" style="display:block;margin:0 auto" />

<p>This time the dedicated service is not just about model routing. It is also where we keep the static system prompt and enable Anthropic prompt caching.</p>
<p><strong>Wait, where do I get the static prompt?</strong></p>
<p>Since APEX generates the NL2IR prompt under the hood, you need to grab it first. The easiest way is probably to add logging to the package in the next section and log <code>p_result.request.system_prompt</code>, or just check your provider logs. Copy that massive system prompt, delete the <code>## Structured Data</code> section from it, and use the rest as your static instructions.</p>
<p>In the service Additional Parameters, put that static part of the NL2IR prompt and enable caching.</p>
<pre><code class="language-json">{
  "system": [
    {
      "type": "text",
      "cache_control": { "type": "ephemeral" },
      "text": "&lt;system prompt&gt;"
    }
  ]
}
</code></pre>
<p>The key idea is that this <code>system</code> prompt block should contain only the stable instructions you want Anthropic to cache.</p>
<p>Do not include the variable <code>## Structured Data</code> section there. The package will take care of that part during request handling.</p>
<h2>Step 3: Deploy the request handler package</h2>
<p>Now deploy this package:</p>
<pre><code class="language-sql">create or replace package ai_handler_cache as
  procedure ai_request (
    p_param  in            apex_ai.t_chat_request_handler_param,
    p_result in out nocopy apex_ai.t_chat_request_handler_result );
end ai_handler_cache;
/

create or replace package body ai_handler_cache as

  g_target_service_static_id constant varchar2(255) := 'haiku-for-ir';

  function get_service_id(
    p_service_static_id in varchar2
  ) return number
  as
    l_service_id number;
  begin
    select remote_server_id
      into l_service_id
      from apex_workspace_ai_services
     where upper(remote_server_static_id) = upper(p_service_static_id)
     fetch first 1 rows only;

    return l_service_id;
  end get_service_id;

  function is_interactive_report(
    p_region_id in number
  ) return boolean
  as
    l_dummy number;
  begin
    select 1
      into l_dummy
      from apex_application_page_ir
     where region_id = p_region_id
     fetch first 1 rows only;

    return true;
  exception
    when no_data_found then
      return false;
  end is_interactive_report;

  function least_positive(
    p_left  in pls_integer,
    p_right in pls_integer
  ) return pls_integer
  as
  begin
    if nvl(p_left, 0) = 0 then
      return p_right;
    elsif nvl(p_right, 0) = 0 then
      return p_left;
    else
      return least(p_left, p_right);
    end if;
  end least_positive;

  function next_top_level_heading_pos(
    p_source       in clob,
    p_search_start in pls_integer
  ) return pls_integer
  as
    l_lf_heading_pos   pls_integer;
    l_crlf_heading_pos pls_integer;
  begin
    l_lf_heading_pos := dbms_lob.instr(p_source, chr(10) || '## ', p_search_start);
    l_crlf_heading_pos := dbms_lob.instr(p_source, chr(13) || chr(10) || '## ', p_search_start);

    if l_lf_heading_pos &gt; 0 then
      l_lf_heading_pos := l_lf_heading_pos + 1;
    end if;

    if l_crlf_heading_pos &gt; 0 then
      l_crlf_heading_pos := l_crlf_heading_pos + 2;
    end if;

    return least_positive(l_lf_heading_pos, l_crlf_heading_pos);
  end next_top_level_heading_pos;

  function slice_clob(
    p_source in clob,
    p_start  in pls_integer,
    p_length in pls_integer
  ) return clob
  as
    l_result clob;
  begin
    if p_source is null or p_length &lt;= 0 then
      return null;
    end if;

    dbms_lob.createtemporary(l_result, true);
    dbms_lob.copy(
      dest_lob    =&gt; l_result,
      src_lob     =&gt; p_source,
      amount      =&gt; p_length,
      dest_offset =&gt; 1,
      src_offset  =&gt; p_start );

    return l_result;
  end slice_clob;

  function extract_structured_data_section(
    p_system_prompt in clob
  ) return clob
  as
    c_heading_marker constant varchar2(20) := '## Structured Data';
    l_heading_pos      pls_integer;
    l_next_heading_pos pls_integer;
    l_section_end_pos  pls_integer;
    l_prompt_length    pls_integer;
  begin
    if p_system_prompt is null then
      return null;
    end if;

    l_heading_pos := dbms_lob.instr(p_system_prompt, c_heading_marker);
    if l_heading_pos = 0 then
      return null;
    end if;

    l_prompt_length := dbms_lob.getlength(p_system_prompt);
    l_next_heading_pos := next_top_level_heading_pos(
                            p_source       =&gt; p_system_prompt,
                            p_search_start =&gt; l_heading_pos + length(c_heading_marker) );

    if l_next_heading_pos &gt; l_heading_pos then
      l_section_end_pos := l_next_heading_pos - 1;
    else
      l_section_end_pos := l_prompt_length;
    end if;

    return slice_clob(
             p_source =&gt; p_system_prompt,
             p_start  =&gt; l_heading_pos,
             p_length =&gt; l_section_end_pos - l_heading_pos + 1 );
  end extract_structured_data_section;

  function has_structured_data_message(
    p_messages in apex_ai.t_chat_messages
  ) return boolean
  as
    l_idx pls_integer;
  begin
    l_idx := p_messages.first;
    while l_idx is not null loop
      if p_messages(l_idx).chat_role = apex_ai.c_role_user then
        return false;
      elsif p_messages(l_idx).chat_role = apex_ai.c_role_assistant
        and p_messages(l_idx).message is not null
        and dbms_lob.instr(p_messages(l_idx).message, '## Structured Data') &gt; 0
      then
        return true;
      end if;

      l_idx := p_messages.next(l_idx);
    end loop;

    return false;
  end has_structured_data_message;

  function assistant_message(
    p_message in clob
  ) return apex_ai.t_chat_message
  as
    l_message apex_ai.t_chat_message;
  begin
    l_message.chat_role := apex_ai.c_role_assistant;
    l_message.message_type := apex_ai.c_chat_message_type_text;
    l_message.message := p_message;
    return l_message;
  end assistant_message;

  procedure insert_assistant_message_before_first_user(
    p_messages in out nocopy apex_ai.t_chat_messages,
    p_message  in            clob
  )
  as
    l_existing_messages apex_ai.t_chat_messages := p_messages;
    l_new_messages      apex_ai.t_chat_messages;
    l_source_idx        pls_integer;
    l_target_idx        pls_integer := 0;
    l_inserted          boolean := false;
  begin
    if p_message is null then
      return;
    end if;

    l_source_idx := l_existing_messages.first;
    while l_source_idx is not null loop
      if not l_inserted and l_existing_messages(l_source_idx).chat_role = apex_ai.c_role_user then
        l_target_idx := l_target_idx + 1;
        l_new_messages(l_target_idx) := assistant_message(p_message);
        l_inserted := true;
      end if;

      l_target_idx := l_target_idx + 1;
      l_new_messages(l_target_idx) := l_existing_messages(l_source_idx);
      l_source_idx := l_existing_messages.next(l_source_idx);
    end loop;

    if not l_inserted then
      l_target_idx := l_target_idx + 1;
      l_new_messages(l_target_idx) := assistant_message(p_message);
    end if;

    p_messages := l_new_messages;
  end insert_assistant_message_before_first_user;

  procedure ai_request (
    p_param  in            apex_ai.t_chat_request_handler_param,
    p_result in out nocopy apex_ai.t_chat_request_handler_result )
  as
    l_structured_data_section clob;
  begin
    if p_param.component.type != 'APEX_APPLICATION_PAGE_REGIONS' then
      return;
    end if;

    if not is_interactive_report(p_param.component.id) then
      return;
    end if;

    l_structured_data_section := extract_structured_data_section(p_result.request.system_prompt);

    if l_structured_data_section is not null
       and not has_structured_data_message(p_result.request.messages)
    then
      insert_assistant_message_before_first_user(
        p_messages =&gt; p_result.request.messages,
        p_message  =&gt; l_structured_data_section );
    end if;
        -- add logging here to capture the system prompt
        -- example: insert into my_logs (clob_column) values (p_result.request.system_prompt)
    p_result.request.system_prompt := null;
    p_result.request.service_id := get_service_id(g_target_service_static_id);
  end ai_request;

end ai_handler_cache;
/
</code></pre>
<h2>What the package is doing</h2>
<p>The package does not try to reinvent NL2IR. It just changes the request shape before it goes out.</p>
<p>The flow is:</p>
<ol>
<li><p>Check that the request is really coming from an Interactive Report.</p>
</li>
<li><p>Look inside the APEX-generated system prompt for the <code>## Structured Data</code> section.</p>
</li>
<li><p>Extract that section.</p>
</li>
<li><p>Insert it into the message history as an assistant message, before the first user message.</p>
</li>
<li><p>Clear the request system prompt.</p>
</li>
<li><p>Route the request to the dedicated Anthropic service.</p>
</li>
</ol>
<p>That one change is the whole trick.</p>
<p>Instead of sending one big changing system prompt every time, we separate the stable part from the variable part.</p>
<p>The stable instructions now live in the service configuration, where Anthropic can cache them. The structured data is still sent to the model, but it is no longer glued into the same system prompt that we want to keep stable.</p>
<h2>Why the structured data is moved into a message</h2>
<p>This is the part that matters for caching.</p>
<p>The package extracts only the <code>## Structured Data</code> section and injects it as an assistant message before the first user question.</p>
<p>That means the model still gets the structured data it needs, but the static system instructions can stay static.</p>
<p>And static prompt prefixes are exactly what Anthropic caching likes.</p>
<p>The package also checks whether that structured-data message is already present, so it does not keep inserting duplicates into the conversation.</p>
<h2>Step 4: Configure the request handler in APEX</h2>
<p>Once the package is deployed, go to your application:</p>
<ul>
<li><p>Edit Application Definition</p>
</li>
<li><p>AI tab</p>
</li>
<li><p>Request Handler Procedure</p>
</li>
</ul>
<p>Enter:</p>
<pre><code class="language-text">ai_handler_cache.ai_request
</code></pre>
<p>That is it.</p>
<p>From that point on, NL2IR requests for Interactive Reports will be rewritten and routed through the dedicated cached service.</p>
<p>Everything else is left alone, because the package exits unless the request is really coming from an Interactive Report.</p>
<h2>What changes in practice</h2>
<p>From the user point of view, nothing really changes. They still type natural language into the report and get the same feature.</p>
<p>From the request point of view, quite a lot changes.</p>
<p>Before:</p>
<ul>
<li><p>Around 17k tokens per request</p>
</li>
<li><p>Big prompt repeated every time</p>
</li>
<li><p>No real benefit from prompt reuse</p>
</li>
</ul>
<p>After:</p>
<ul>
<li><p>First request still pays the full setup cost</p>
</li>
<li><p>Roughly 15k tokens can be cached</p>
</li>
<li><p>Those cached tokens are reused on each NL2IR call</p>
</li>
</ul>
<p>That is where the savings come from.</p>
<h2>A few important notes</h2>
<p>This package is built around the current shape of the APEX NL2IR prompt, so there are a few assumptions worth calling out.</p>
<ul>
<li><p>It only targets Interactive Reports</p>
</li>
<li><p>It expects the generated prompt to contain a <code>## Structured Data</code> heading</p>
</li>
<li><p>It routes requests to a dedicated service with static ID <code>haiku-for-ir</code></p>
</li>
<li><p>It assumes your static system prompt is already stored in the dedicated service Additional Parameters</p>
</li>
</ul>
<p>So if Oracle changes the internal NL2IR prompt structure in a future release, this package may need a small update.</p>
<h2>What about other providers?</h2>
<p>This post uses Anthropic for the concrete implementation.</p>
<p>If your provider of choice is OpenAI, caching is fully automatic, so it will probably cache something even without changes. If you want to make it work better, you would need to modify my package so it removes the Structured Data section from the system prompt, but does not null out the <code>system_prompt</code> attribute. Instead, you would populate it with the rest of the system prompt. Also, because the system prompt is part of the message array rather than a separate attribute in OpenAI, you would want to modify how the package injects the structured data into the message array.</p>
<p>With Google, caching is also automatic, and the same general approach as OpenAI could work. If it does not, Google also has a separate endpoint where you define the cache, populate it with the content you want cached, and then refresh the TTL every hour or so. That could become a separate job to schedule and maintain.</p>
<p>I did not test those approaches, but I would be very happy to consult on them if someone dares to do it.</p>
<h2>A few closing thoughts on the APEX AI hooks</h2>
<p>I actually like that Oracle gave us request and response handler injection points this early. Without them, this whole approach would not really be possible.</p>
<p>That said, I do think the current request-building model works against optimization.</p>
<p>Right now Oracle builds the request such that messages land in the middle of it, between the system prompt, tools, and anything that we add in service configuration. For cost optimization, caching, and even debugging, that is a bit backwards.</p>
<p>What I would really like is a lower-level injection point just before the request is sent to the LLM service, after Oracle has finished building the final payload. That would make it much easier to do provider-specific optimizations cleanly instead of reverse-engineering the generated prompt shape.</p>
<p>I would also like better visibility into the actual provider request and response payloads. Right now that is one of the frustrating parts of working with caching. Anthropic exposes useful usage information, including cache-related fields, but Oracle does not really expose that final response data back to us in a way that lets us inspect cache hits properly.</p>
<p>So yes, the hooks Oracle gave us are already useful. But if Oracle exposed the final outbound payload and the real provider usage details, building and validating optimizations like this would be much easier.</p>
<p>If this were a production project, I would probably suggest not relying too heavily on the standard Oracle AI services directly. In that kind of setup, it would probably be better to configure one standard Ollama or OpenAI-compatible service in APEX, put a small proxy in front of the real provider, and let that proxy intercept the calls, optimize them properly, and then pass them on to the actual LLM service or at least use a service like OpenRouter.</p>
<h2>And that is it</h2>
<p>The interesting part here is that the big saving does not come from only switching to a cheaper model.</p>
<p>It comes from changing the request shape so the expensive, stable part of the prompt can actually be cached.</p>
<p>Instead of paying for a massive system prompt on every single turn, you turn it into a flow where the heavy lifting is cached and much cheaper on follow-up calls.</p>
<p>That is a much better place to be.</p>
<p>If you want to build on top of this, the obvious next steps are moving the service static ID into configuration, adding more defensive error handling around service lookup, and hoping Oracle eventually exposes enough provider-level usage data to make cache-hit validation much easier.</p>
]]></content:encoded></item><item><title><![CDATA[Custom LLM Service for Natural Language to IR]]></title><description><![CDATA[Normally, when you create any LLM-enabled component in APEX, you can choose which LLM service it should use. The main exception is the new Natural Language to IR feature in Apex 26.1. This post shows ]]></description><link>https://blog.f100.zip/custom-llm-service-for-natural-language-to-ir</link><guid isPermaLink="true">https://blog.f100.zip/custom-llm-service-for-natural-language-to-ir</guid><category><![CDATA[orclapex]]></category><category><![CDATA[#oracle-apex]]></category><category><![CDATA[Apex]]></category><category><![CDATA[AI]]></category><category><![CDATA[Oracle]]></category><dc:creator><![CDATA[Jakub Dobruchowski]]></dc:creator><pubDate>Thu, 21 May 2026 08:10:39 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/73ccd1f8-39a7-4902-a619-0e545f7e438d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Normally, when you create any LLM-enabled component in APEX, you can choose which LLM service it should use. The main exception is the new Natural Language to IR feature in Apex 26.1. This post shows how to work around that limitation.</p>
<p>But first, why would you want to do this? Usually because you want a less expensive model for this feature. The system prompt for NL2IR is massive and very restrictive, and a decent chunk of metadata and tool information is sent along with each request. When I looked at the logs, each NL2IR call contained around 17,000 input tokens and only about 100 output tokens. That is massive, maybe even too massive.</p>
<p>My LLM provider of choice is Anthropic, and as of May 2026, 17k tokens works out to about \(0.11 per request on Sonnet (the mid-tier model), while on Haiku (the small, fast model) it is about \)0.04. So no matter what you are doing, you probably want NL2IR glued to the cheapest model possible.</p>
<p>This post also came from a question by <a href="https://hashnode.com/@lufcmattylad" class="user-mention" data-type="mention" title="Matt Mulvaney">Matt Mulvaney</a> And if an Oracle ACE Director asks, the only real answer is: let’s do it ;)</p>
<p>We will start by creating two AI services.</p>
<p>One named Dummy, which we will set as our default service. I am setting Maximum AI Tokens to 0 to show the solution working, but you can set it however you want.</p>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/f01ef136-f906-4d23-9a19-8a61b04d6495.png" alt="" style="display:block;margin:0 auto" />

<p>And the second will be the one we actually want to use.</p>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/62cb0126-c23a-4452-ae18-4e3f81db4f64.png" alt="" style="display:block;margin:0 auto" />

<p>The choice of model does not really matter. What matters is the static ID of the service we want to use for NL2IR. For me, it is <strong>haiku-for-ir</strong>.</p>
<p>Now we need to configure the default service in the app. Go to Edit Application Definition of your selected app, click the AI tab, and select the Dummy service. This is required for the NL2IR feature to show up.</p>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/37a76d89-eb31-4c3c-a74f-9dceed5ab56e.png" alt="" style="display:block;margin:0 auto" />

<p>Let's go into the app and observe the behavior.</p>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/56fb8ba3-c203-445b-8735-5adb5e98b94c.png" alt="" style="display:block;margin:0 auto" />

<p>Okay, looks like Oracle may need an NVL when checking the limits, because the first call is partially working, but after that we get errors. This is what we expected for this demo, so now let's make it use the correct service.</p>
<p>First, create this procedure. If you are coming from my <a href="https://blog.f100.zip/apex-26-1-per-user-token-management">previous blog post</a>, you can modify the contents of the package we created there. Remember to change <strong>haiku-for-ir</strong> to the static ID of your LLM service.</p>
<pre><code class="language-sql">create or replace procedure ai_request (
    p_param  in            apex_ai.t_chat_request_handler_param,
   p_result in out nocopy apex_ai.t_chat_request_handler_result
) is
   l_test number;
   l_service_id number;
begin
   -- Check if the request is coming from IR.
   select count(*)
     into l_test
     from apex_application_page_ir
     where region_id = p_param.component.id;

   if l_test = 0 then
      return; -- Not an IR request, skip.
   end if;

   -- Get the service ID.
   select remote_server_id
      into l_service_id
      from apex_workspace_ai_services
     where upper(remote_server_static_id) = upper('haiku-for-ir')
     fetch first 1 rows only;

   -- Set the new service.
   p_result.request.service_id := l_service_id;
end;
</code></pre>
<p>Then enter the procedure name, <strong>ai_request</strong>, into the Request Handler Procedure field on the AI tab of Edit Application Definition.</p>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/e020126d-251a-418e-86ba-d884807f798f.png" alt="" style="display:block;margin:0 auto" />

<p>And you are done.<br />If we go back to the app, we will see that even though our default service is Dummy, NL2IR works perfectly.</p>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/bb8c6bde-2412-4988-b691-ca622b22f6f2.png" alt="" style="display:block;margin:0 auto" />]]></content:encoded></item><item><title><![CDATA[Apex 26.1  Per User Token Management]]></title><description><![CDATA[Oracle APEX 26.1 brings more AI features than ever.More AI features means more governance, especially around token usage.
APEX can limit token usage per service over 24 hours, but not directly per app]]></description><link>https://blog.f100.zip/apex-26-1-per-user-token-management</link><guid isPermaLink="true">https://blog.f100.zip/apex-26-1-per-user-token-management</guid><category><![CDATA[#oracle-apex]]></category><category><![CDATA[APEX-26.1]]></category><category><![CDATA[Oracle Apex AI]]></category><category><![CDATA[AI]]></category><category><![CDATA[Oracle]]></category><category><![CDATA[Oracle Database]]></category><category><![CDATA[oracle-database-26ai]]></category><dc:creator><![CDATA[Jakub Dobruchowski]]></dc:creator><pubDate>Tue, 19 May 2026 10:41:45 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/50a9a1d0-7435-4aba-a043-7401eb2dca71.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Oracle APEX 26.1 brings more AI features than ever.<br />More AI features means more governance, especially around token usage.</p>
<p>APEX can limit token usage per service over 24 hours, but not directly per app user.<br />In this post, I will show a simple pattern to enforce per-user limits.</p>
<p>What we will build:</p>
<ul>
<li><p>A table to log AI usage per user</p>
</li>
<li><p>Request/response handlers in APEX AI configuration</p>
</li>
<li><p>Daily quota checks with warning and hard-stop behavior</p>
</li>
</ul>
<p>Let's start by creating a table that will store all our users' AI calls.</p>
<pre><code class="language-sql">create table ai_stats (
    id                     number default on null to_number(sys_guid(), 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX') 
                           constraint ai_stats_id_pk primary key,
    apex_user              varchar2(4000 char),
	input_tokens           number,
    output_tokens          number,
    total_tokens           number,
    resp_time              date,
    component_name         varchar2(4000 char),
    component_static_id    varchar2(4000 char)
);
</code></pre>
<p>Then we need to intercept the standard Oracle APEX calls to the LLM. Fortunately, Oracle APEX 26.1 gives us a standard way to do that.</p>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/9e571a77-13d3-40fb-8d78-be4191423ef2.png" alt="" style="display:block;margin:0 auto" />

<p>Go to the Edit Application Definition of your chosen APEX app, then to the AI tab.<br />What we are interested in are the Request Handler Procedure and Response Handler Procedure fields. By default, they are empty.<br />Enter the following values:</p>
<pre><code class="language-plaintext">ai_handler.ai_request
</code></pre>
<pre><code class="language-plaintext">ai_handler.ai_response
</code></pre>
<p>Now every LLM request and response in your app will be routed through the ai_handler package.</p>
<p>The request handler blocks over-limit calls before they run, while the response handler logs usage and adds warnings as users approach the limit.</p>
<p>This implementation enforces limits based on total_tokens (input + output), not input tokens alone.</p>
<p>So let's create it:</p>
<pre><code class="language-sql">create or replace package ai_handler as
	procedure ai_request (
		p_param  in apex_ai.t_chat_request_handler_param,
		p_result in out nocopy apex_ai.t_chat_request_handler_result
	);

	procedure ai_response (
		p_param  in apex_ai.t_chat_response_handler_param,
		p_result in out nocopy apex_ai.t_chat_response_handler_result
	);
end ai_handler;
/
create or replace package body ai_handler as
	g_daily_total_allowance                   number := 10000;
	g_daily_total_allowance_warning_threshold number := 8000;

	procedure ai_request (
		p_param  in apex_ai.t_chat_request_handler_param,
		p_result in out nocopy apex_ai.t_chat_request_handler_result
	) is
	l_total_tokens number;
	begin
			select nvl(sum(total_tokens),0)
		  into l_total_tokens
		  from ai_stats
		  where apex_user = sys_context(
			   'APEX$SESSION',
			   'APP_USER'
		   )
		   and trunc(resp_time) = trunc(sysdate);

       if l_total_tokens &gt; g_daily_total_allowance then
         RAISE_APPLICATION_ERROR(-20001, 'Daily token allowance exceeded. You have used ' || l_total_tokens || ' total tokens today, which exceeds the daily allowance of ' || g_daily_total_allowance || ' tokens.');
        end if;
  end ai_request;

	procedure ai_response (
		p_param  in apex_ai.t_chat_response_handler_param,
		p_result in out nocopy apex_ai.t_chat_response_handler_result
	) is
		l_total_tokens number;
	begin
		insert into ai_stats (
			apex_user,
			input_tokens,
			output_tokens,
			total_tokens,
			resp_time,
			component_name,
			component_static_id
		) values ( 
      sys_context('APEX$SESSION','APP_USER'),
		  p_result.response.input_tokens,
		  p_result.response.output_tokens,
		  p_result.response.total_tokens,
		  sysdate,
		  p_param.component.name,
		  p_param.component.static_id );

		select nvl(sum(total_tokens),0)
		  into l_total_tokens
		  from ai_stats
		 where apex_user = sys_context(
			   'APEX$SESSION',
			   'APP_USER'
		   )
		   and trunc(resp_time) = trunc(sysdate);

        if l_total_tokens &gt; g_daily_total_allowance then
         RAISE_APPLICATION_ERROR(-20001, 'Daily token allowance exceeded. You have used ' || l_total_tokens || ' total tokens today, which exceeds the daily allowance of ' || g_daily_total_allowance || ' tokens.');
        end if;

		if
			l_total_tokens &gt; g_daily_total_allowance_warning_threshold
			and p_result.response.message.message is not null
		then
			p_result.response.message.message := p_result.response.message.message
			                                     || chr(10)
			                                     || 'Warning: You have used '
			                                     || l_total_tokens
			                                     || ' total tokens today, which is approaching the daily allowance of '
			                                     || g_daily_total_allowance
			                                     || ' tokens.';
		end if;

	end ai_response;
end ai_handler;
</code></pre>
<p>And that's it.</p>
<p>You may want to modify the g_daily_total_allowance and g_daily_total_allowance_warning_threshold variables. Or even move them to a separate table and build a whole system around this approach.</p>
<p>This is how it looks with a warning:</p>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/7619f1e6-69f1-45b9-a7c6-8196affe8f5d.png" alt="" style="display:block;margin:0 auto" />

<p>And this is what will happen when you exceed the limit:</p>
<img src="https://cdn.hashnode.com/uploads/covers/63f76731bdf1a5e7a027a53a/8fe6ae06-ad25-417c-a8ac-733d4c13f7c7.png" alt="" style="display:block;margin:0 auto" />

<p>APEX does not currently provide a clean cancel mechanism for this flow, so raising an application error is the most reliable way to stop over-limit requests.<br />With warning thresholds in place, users get clear notice before they hit the hard limit.</p>
]]></content:encoded></item><item><title><![CDATA[SQLCL liquibase tags explained #JoelKallmanDay]]></title><description><![CDATA[Preface
Here at Pretius we love Liquibase. It is a perfect tool that works at many levels of complexity, from simple generate schema no git setup to complex CI\CD pipelines that automatically install and upgrade dozens of environments. In this articl...]]></description><link>https://blog.f100.zip/sqlcl-liquibase-tags-explained</link><guid isPermaLink="true">https://blog.f100.zip/sqlcl-liquibase-tags-explained</guid><category><![CDATA[#oracle-apex]]></category><category><![CDATA[liquibase]]></category><category><![CDATA[sqlcl]]></category><dc:creator><![CDATA[Jakub Dobruchowski]]></dc:creator><pubDate>Wed, 11 Oct 2023 16:17:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1697043379631/3fcca9d9-a9f6-4129-bb56-bfa5536ba4b4.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-preface">Preface</h2>
<p>Here at Pretius we love Liquibase. It is a perfect tool that works at many levels of complexity, from simple generate schema no git setup to complex CI\CD pipelines that automatically install and upgrade dozens of environments. In this article, we will be focusing on the more complex use of oracle flavored liquibase. If you need a refresher on what liquibase is and how to use it you can visit <a target="_blank" href="https://rafal.hashnode.dev/">Rafal's</a> blog.</p>
<p>Let’s imagine you would like to write your own XML changeset using all the bells and whistles that Oracle provides, what are your options?</p>
<p>SQLCL documentation gives you a nice but unfortunately lacking <a target="_blank" href="https://docs.oracle.com/en/database/oracle/sql-developer-command-line/20.2/sqcug/using-liquibase-sqlcl.html#GUID-AA97A806-F886-4286-A14D-372F20456284">table</a>. What are the parameters for those tags? What are they used for? How to use them? Do we even know?</p>
<h2 id="heading-how-to-use-them">How to use them?</h2>
<p>To use those tags you need to add a new XSD to your database change log - Notice the xmlns:n0 parameter in the example below - and use SQLCL to run your changes. Alternatively, you can add the extension to the standalone liquibase, a description of how to do it is in SQLCL documentation.</p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">databaseChangeLog</span> 
        <span class="hljs-attr">xmlns</span>=<span class="hljs-string">"http://www.liquibase.org/xml/ns/dbchangelog"</span> 
        <span class="hljs-attr">xmlns:xsi</span>=<span class="hljs-string">"http://www.w3.org/2001/XMLSchema-instance"</span> 
        <span class="hljs-attr">xmlns:n0</span>=<span class="hljs-string">"http://www.oracle.com/xml/ns/dbchangelog-ext"</span> 
        <span class="hljs-attr">xsi:schemaLocation</span>=<span class="hljs-string">"http://www.liquibase.org/xml/ns/dbchangelog 
        http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-latest.xsd"</span>&gt;</span>
</code></pre>
<h2 id="heading-oracle-dbchangelog-ext">Oracle dbchangelog-ext</h2>
<p>Unfortunately, while Oracle gives you a path to XSD to add to your XMLs this link leads to nowhere. I did some digging and found the file in SQLCL, but it is missing a lot of tags that Oracle specifies can be used. Hopefully, in future iterations, we will get a working link and full XSD.</p>
<h2 id="heading-tags">Tags</h2>
<p>Most of the tags do exactly what is written on the tin, but not all. Parameter specification is taken from dbchangelog-ext where possible but most are a result of me tinkering with them.</p>
<p>On the objectName attribute - Be sure that it matches the name of the object you are creating, SQLCL will use that attribute to generate rollback statements and it will put it into quotation marks. Unless you are using mixed case letters in your tables, triggers, sequences etc. always use all caps for this attribute.</p>
<h3 id="heading-createoracledictionary">createOracleDictionary</h3>
<p>Missing from XSD.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the dictionary to create</td></tr>
<tr>
<td>objectType</td><td>attribute string optional</td><td>This should be always set to DICTIONARY</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
<tr>
<td>replaceIfExists</td><td>attribute boolean required</td><td>Should object be recreated on execution</td></tr>
<tr>
<td>source</td><td>element string required</td><td>Put here full ddl to create the dictionary</td></tr>
</tbody>
</table>
</div><p>Example:</p>
<pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">n0:createOracleDirectory</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"DATA_PUMP_DIR"</span> <span class="hljs-attr">objectType</span>=<span class="hljs-string">"DIRECTORY"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"ADMIN"</span>  <span class="hljs-attr">replaceIfExists</span>=<span class="hljs-string">"true"</span> &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>&lt;![CDATA[CREATE OR REPLACE DIRECTORY "DATA_PUMP_DIR" AS 'dpdump';]]&gt;<span class="hljs-tag">&lt;/<span class="hljs-name">n0:source</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:createOracleDirectory</span>&gt;</span>
</code></pre>
<h3 id="heading-createoracleprocedure">createOracleProcedure</h3>
<p>Missing from XSD.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the procedure to create</td></tr>
<tr>
<td>objectType</td><td>attribute string optional</td><td>This should be always set to PROCEDURE</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
<tr>
<td>replaceIfExists</td><td>attribute boolean required</td><td>Should object be recreated on execution</td></tr>
<tr>
<td>source</td><td>element string required</td><td>Put here full ddl to create the procedure</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">n0:createOracleProcedure</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"HELLOLIQUIBASE"</span>  <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"ADMIN"</span>  <span class="hljs-attr">replaceIfExists</span>=<span class="hljs-string">"true"</span> &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>&lt;![CDATA[CREATE OR REPLACE EDITIONABLE PROCEDURE "ADMIN"."HELLOLIQUIBASE" 
(
  NAME IN VARCHAR2 
) AS 
BEGIN
  dbms_output.put_line('Hello '||name);
END HELLOLIQUIBASE;
/]]&gt;<span class="hljs-tag">&lt;/<span class="hljs-name">n0:source</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:createOracleProcedure</span>&gt;</span>
</code></pre>
<h3 id="heading-createoracleconstraint">createOracleConstraint</h3>
<p>Missing from XSD.</p>
<p>There is a problem with this one. 1st of all it's missing from XSD, and lb generate-schema or lb generate-object will not use this tag, so there is no way for me to work out the intended use for it. Secondly, I tinkered with it a bit and in the example, you can see what I found working, but lb rollback will not be able to properly run. The same goes for createOracleRefConstraint although it gave me a slightly different error. I would advise you to not use those tags currently.</p>
<pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">n0:createOracleConstraint</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"TEST_CONST2"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"SC_CORE"</span>  &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>&lt;![CDATA[ ALTER TABLE test_table ADD CONSTRAINT test_const2 UNIQUE (column3); ]]&gt;
            <span class="hljs-tag">&lt;/<span class="hljs-name">n0:source</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:createOracleConstraint</span>&gt;</span>
</code></pre>
<h3 id="heading-createoraclegrant">createOracleGrant</h3>
<p>Missing from XSD.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the grant to create</td></tr>
<tr>
<td>objectType</td><td>attribute string optional</td><td>This should be always set to OBJECT_GRANT</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
<tr>
<td>replaceIfExists</td><td>attribute boolean required</td><td>Should object be recreated on execution</td></tr>
<tr>
<td>source</td><td>element string required</td><td>Put here full ddl to create the grant</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">n0:createOracleGrant</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"object_grant0"</span> <span class="hljs-attr">objectType</span>=<span class="hljs-string">"OBJECT_GRANT"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"SC_CORE"</span>  <span class="hljs-attr">replaceIfExists</span>=<span class="hljs-string">"true"</span> &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>&lt;![CDATA[
  GRANT INSERT ON "SC_CORE"."TEST_TABLE" TO "LIQUIBASE_USER"]]&gt;<span class="hljs-tag">&lt;/<span class="hljs-name">n0:source</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:createOracleGrant</span>&gt;</span>
</code></pre>
<h3 id="heading-createoraclepackagebody">createOraclePackageBody</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the Package to create</td></tr>
<tr>
<td>objectType</td><td>attribute string optional</td><td>This should be always set to PACKAGE_BODY</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
<tr>
<td>replaceIfExists</td><td>attribute boolean required</td><td>Should object be recreated on execution</td></tr>
<tr>
<td>source</td><td>element string required</td><td>Put here full ddl to create the package specification</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">n0:createOraclePackageBody</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"TEST"</span> <span class="hljs-attr">objectType</span>=<span class="hljs-string">"PACKAGE_BODY"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"SC_CORE"</span>  <span class="hljs-attr">replaceIfExists</span>=<span class="hljs-string">"true"</span> &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>&lt;![CDATA[
  CREATE OR REPLACE EDITIONABLE PACKAGE BODY "SC_CORE"."TEST" as
procedure p_test  as
begin
null;
end p_test;
end TEST;]]&gt;<span class="hljs-tag">&lt;/<span class="hljs-name">n0:source</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:createOraclePackageBody</span>&gt;</span>
</code></pre>
<h3 id="heading-createoraclejob">createOracleJob</h3>
<p>Missing from XSD.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the job to create</td></tr>
<tr>
<td>objectType</td><td>attribute string optional</td><td>This should be always set to JOB</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
<tr>
<td>replaceIfExists</td><td>attribute boolean required</td><td>Should object be recreated on execution</td></tr>
<tr>
<td>source</td><td>element string required</td><td>Put here code to create the job</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">n0:createOracleJob</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"LIQUI_JOB"</span> <span class="hljs-attr">objectType</span>=<span class="hljs-string">"JOB"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"SC_CORE"</span>  <span class="hljs-attr">replaceIfExists</span>=<span class="hljs-string">"true"</span> &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>&lt;![CDATA[

BEGIN 
dbms_scheduler.create_job('LIQUI_JOB',
job_type=&gt;'PLSQL_BLOCK', job_action=&gt;
'begin
 null;
end;'
, number_of_arguments=&gt;0,
start_date=&gt;TO_TIMESTAMP_TZ('04-OCT-2023 10.01.38.534232000 PM EUROPE/ATHENS','DD-MON-RRRR HH.MI.SSXFF AM TZR','NLS_DATE_LANGUAGE=english'), repeat_interval=&gt; 
'FREQ=YEARLY;BYDATE=1101;BYTIME=213700'
, end_date=&gt;NULL,
job_class=&gt;'DEFAULT_JOB_CLASS', enabled=&gt;FALSE, auto_drop=&gt;FALSE,comments=&gt;
'My test job for blogpost'
);
sys.dbms_scheduler.set_attribute('LIQUI_JOB','NLS_ENV','NLS_LANGUAGE=''ENGLISH'' NLS_TERRITORY=''POLAND'' NLS_CURRENCY=''zł'' NLS_ISO_CURRENCY=''POLAND'' NLS_NUMERIC_CHARACTERS='', '' NLS_CALENDAR=''GREGORIAN'' NLS_DATE_FORMAT=''RR/MM/DD'' NLS_DATE_LANGUAGE=''ENGLISH'' NLS_SORT=''BINARY'' NLS_TIME_FORMAT=''HH24:MI:SSXFF'' NLS_TIMESTAMP_FORMAT=''RR/MM/DD HH24:MI:SSXFF'' NLS_TIME_TZ_FORMAT=''HH24:MI:SSXFF TZR'' NLS_TIMESTAMP_TZ_FORMAT=''RR/MM/DD HH24:MI:SSXFF TZR'' NLS_DUAL_CURRENCY=''zł'' NLS_COMP=''BINARY'' NLS_LENGTH_SEMANTICS=''BYTE'' NLS_NCHAR_CONV_EXCP=''FALSE''');
dbms_scheduler.enable('LIQUI_JOB');
COMMIT; 
END; 
]]&gt;<span class="hljs-tag">&lt;/<span class="hljs-name">n0:source</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:createOracleJob</span>&gt;</span>
</code></pre>
<h3 id="heading-createoraclepackagespec">createOraclePackageSpec</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the Package to create</td></tr>
<tr>
<td>objectType</td><td>attribute string optional</td><td>This should be always set to PACKAGE_SPEC</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
<tr>
<td>replaceIfExists</td><td>attribute boolean required</td><td>Should object be recreated on execution</td></tr>
<tr>
<td>source</td><td>element string required</td><td>Put here full ddl to create the package specification</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">n0:createOraclePackageSpec</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"TEST"</span> <span class="hljs-attr">objectType</span>=<span class="hljs-string">"PACKAGE_SPEC"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"SC_CORE"</span>  <span class="hljs-attr">replaceIfExists</span>=<span class="hljs-string">"true"</span> &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>&lt;![CDATA[
CREATE OR REPLACE EDITIONABLE PACKAGE "SC_CORE"."TEST" AS 
procedure p_test;
end;
]]&gt;<span class="hljs-tag">&lt;/<span class="hljs-name">n0:source</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:createOraclePackageSpec</span>&gt;</span>
</code></pre>
<h3 id="heading-createoraclepublicsynonym">createOraclePublicSynonym</h3>
<p>Missing from XSD. Lb generate-schema will not use this tag. Functions the same as createOracleSynonym.</p>
<h3 id="heading-createoraclerefconstraint">createOracleRefConstraint</h3>
<p>Missing from XSD. See createOracleConstraint.</p>
<h3 id="heading-createoraclesynonym">createOracleSynonym</h3>
<p>Missing from XSD. Lb generate-schema will use this tag even for public synonyms.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the Package to create</td></tr>
<tr>
<td>objectType</td><td>attribute string optional</td><td>This should be always set to PACKAGE_SPEC</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
<tr>
<td>replaceIfExists</td><td>attribute boolean required</td><td>Should object be recreated on execution</td></tr>
<tr>
<td>source</td><td>element string required</td><td>Put here full ddl to create the package specification</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">n0:createOracleSynonym</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"LOGGER_LOGS_5_MIN"</span> <span class="hljs-attr">objectType</span>=<span class="hljs-string">"SYNONYM"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"SC_CORE"</span>  <span class="hljs-attr">replaceIfExists</span>=<span class="hljs-string">"true"</span> &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>&lt;![CDATA[
  CREATE OR REPLACE EDITIONABLE SYNONYM "SC_CORE"."LOGGER_LOGS_5_MIN" FOR "LOGGER_USER"."LOGGER_LOGS_5_MIN"]]&gt;<span class="hljs-tag">&lt;/<span class="hljs-name">n0:source</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:createOracleSynonym</span>&gt;</span>
</code></pre>
<h3 id="heading-createoracletrigger">createOracleTrigger</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the Trigger to create</td></tr>
<tr>
<td>objectType</td><td>attribute string optional</td><td>This should be always set to TRIGGER</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
<tr>
<td>replaceIfExists</td><td>attribute boolean required</td><td>Should object be recreated on execution</td></tr>
<tr>
<td>source</td><td>element string required</td><td>Put here full ddl to create the trigger</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">n0:createOracleTrigger</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"TEST_TRIGGER"</span> <span class="hljs-attr">objectType</span>=<span class="hljs-string">"TRIGGER"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"SC_CORE"</span>  <span class="hljs-attr">replaceIfExists</span>=<span class="hljs-string">"true"</span> &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>&lt;![CDATA[
  CREATE OR REPLACE EDITIONABLE TRIGGER "SC_CORE"."TEST_TRIGGER" 
BEFORE DELETE OR INSERT OR UPDATE ON TEST_TABLE 
BEGIN
  NULL;
END;
/
ALTER TRIGGER "SC_CORE"."TEST_TRIGGER" ENABLE]]&gt;<span class="hljs-tag">&lt;/<span class="hljs-name">n0:source</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:createOracleTrigger</span>&gt;</span>
</code></pre>
<h3 id="heading-createoracletypebody">createOracleTypeBody</h3>
<p>Missing from XSD.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the type body to create</td></tr>
<tr>
<td>objectType</td><td>attribute string optional</td><td>This should be always set to TYPE_BODY</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
<tr>
<td>replaceIfExists</td><td>attribute boolean required</td><td>Should object be recreated on execution</td></tr>
<tr>
<td>source</td><td>element string required</td><td>Put here full ddl to create the type body</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">n0:createOracleTypeBody</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"DATA_TYP1"</span> <span class="hljs-attr">objectType</span>=<span class="hljs-string">"TYPE_BODY"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"SC_CORE"</span>  <span class="hljs-attr">replaceIfExists</span>=<span class="hljs-string">"true"</span> &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>&lt;![CDATA[
  CREATE OR REPLACE EDITIONABLE TYPE BODY "SC_CORE"."DATA_TYP1" IS   
      MEMBER FUNCTION prod (invent NUMBER) RETURN NUMBER IS 
         BEGIN 
             RETURN (year + invent);
         END; 
      END; ]]&gt;<span class="hljs-tag">&lt;/<span class="hljs-name">n0:source</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:createOracleTypeBody</span>&gt;</span>
</code></pre>
<h3 id="heading-createoracletypespec">createOracleTypeSpec</h3>
<p>Missing from XSD.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the Type Specification to create</td></tr>
<tr>
<td>objectType</td><td>attribute string optional</td><td>This should be always set to TRIGGER</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
<tr>
<td>replaceIfExists</td><td>attribute boolean required</td><td>Should object be recreated on execution</td></tr>
<tr>
<td>source</td><td>element string required</td><td>Put here full ddl to create the type specification</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">n0:createOracleTypeSpec</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"DATA_TYP1"</span> <span class="hljs-attr">objectType</span>=<span class="hljs-string">"TYPE_SPEC"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"SC_CORE"</span>  <span class="hljs-attr">replaceIfExists</span>=<span class="hljs-string">"true"</span> &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>&lt;![CDATA[
  CREATE OR REPLACE EDITIONABLE TYPE "SC_CORE"."DATA_TYP1" AS OBJECT 
   ( year NUMBER, 
     MEMBER FUNCTION prod(invent NUMBER) RETURN NUMBER 
   ); 
]]&gt;<span class="hljs-tag">&lt;/<span class="hljs-name">n0:source</span>&gt;</span>
</code></pre>
<h3 id="heading-createsxmlobject">createSxmlObject</h3>
<p>A bit strange but can be useful in a pinch. This tag lets you create an object defined by SXML. What is SXML? In this context, this is a specification of a given object generated by dbms_metadata with SXML transformation applied. This tag is used by Oracle to generate table changesets when you run lb generate-schema instead of doing separate changesets for each 'parts' of the table.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string</td><td>Name of the object to create</td></tr>
<tr>
<td>objectType</td><td>attribute string</td><td>Type of the object that will be created</td></tr>
<tr>
<td>ownerName</td><td>attribute string</td><td>Name of the schema owning this object</td></tr>
<tr>
<td>replaceIfExists</td><td>attribute boolean ??</td><td>Should object be recreated on execution. This parameter doesn't exist in XSD but oracle adds it when generating this tag</td></tr>
<tr>
<td>source</td><td>element string required</td><td>Full SXML goes here</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">n0:createSxmlObject</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"TEST_TABLE"</span> <span class="hljs-attr">objectType</span>=<span class="hljs-string">"TABLE"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"SC_CORE"</span>  <span class="hljs-attr">replaceIfExists</span>=<span class="hljs-string">"true"</span> &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>&lt;![CDATA[
  &lt;TABLE xmlns="http://xmlns.oracle.com/ku" version="1.0"&gt;
   &lt;SCHEMA&gt;SC_CORE&lt;/SCHEMA&gt;
   &lt;NAME&gt;TEST_TABLE&lt;/NAME&gt;
   &lt;RELATIONAL_TABLE&gt;
      &lt;COL_LIST&gt;
         &lt;COL_LIST_ITEM&gt;
            &lt;NAME&gt;COLUMN1&lt;/NAME&gt;
            &lt;DATATYPE&gt;NUMBER&lt;/DATATYPE&gt;
            &lt;NOT_NULL&gt;&lt;/NOT_NULL&gt;
         &lt;/COL_LIST_ITEM&gt;
         &lt;COL_LIST_ITEM&gt;
            &lt;NAME&gt;COLUMN3&lt;/NAME&gt;
            &lt;DATATYPE&gt;VARCHAR2&lt;/DATATYPE&gt;
            &lt;LENGTH&gt;20&lt;/LENGTH&gt;
            &lt;COLLATE_NAME&gt;USING_NLS_COMP&lt;/COLLATE_NAME&gt;
         &lt;/COL_LIST_ITEM&gt;
         &lt;COL_LIST_ITEM&gt;
            &lt;NAME&gt;COLUMN2&lt;/NAME&gt;
            &lt;DATATYPE&gt;DATE&lt;/DATATYPE&gt;
         &lt;/COL_LIST_ITEM&gt;
      &lt;/COL_LIST&gt;
      &lt;PRIMARY_KEY_CONSTRAINT_LIST&gt;
         &lt;PRIMARY_KEY_CONSTRAINT_LIST_ITEM&gt;
            &lt;NAME&gt;TEST_TABLE_PK&lt;/NAME&gt;
            &lt;COL_LIST&gt;
               &lt;COL_LIST_ITEM&gt;
                  &lt;NAME&gt;COLUMN1&lt;/NAME&gt;
               &lt;/COL_LIST_ITEM&gt;
            &lt;/COL_LIST&gt;
            &lt;USING_INDEX&gt;
               &lt;INDEX_ATTRIBUTES&gt;
                  &lt;PCTFREE&gt;10&lt;/PCTFREE&gt;
                  &lt;INITRANS&gt;20&lt;/INITRANS&gt;
                  &lt;MAXTRANS&gt;255&lt;/MAXTRANS&gt;
                  &lt;TABLESPACE&gt;DATA&lt;/TABLESPACE&gt;
                  &lt;LOGGING&gt;Y&lt;/LOGGING&gt;
               &lt;/INDEX_ATTRIBUTES&gt;
            &lt;/USING_INDEX&gt;
         &lt;/PRIMARY_KEY_CONSTRAINT_LIST_ITEM&gt;
      &lt;/PRIMARY_KEY_CONSTRAINT_LIST&gt;
      &lt;DEFAULT_COLLATION&gt;USING_NLS_COMP&lt;/DEFAULT_COLLATION&gt;
      &lt;PHYSICAL_PROPERTIES&gt;
         &lt;HEAP_TABLE&gt;
            &lt;SEGMENT_ATTRIBUTES&gt;
               &lt;SEGMENT_CREATION_DEFERRED&gt;&lt;/SEGMENT_CREATION_DEFERRED&gt;
               &lt;PCTFREE&gt;10&lt;/PCTFREE&gt;
               &lt;PCTUSED&gt;40&lt;/PCTUSED&gt;
               &lt;INITRANS&gt;10&lt;/INITRANS&gt;
               &lt;MAXTRANS&gt;255&lt;/MAXTRANS&gt;
               &lt;TABLESPACE&gt;DATA&lt;/TABLESPACE&gt;
               &lt;LOGGING&gt;Y&lt;/LOGGING&gt;
            &lt;/SEGMENT_ATTRIBUTES&gt;
            &lt;COMPRESS&gt;N&lt;/COMPRESS&gt;
         &lt;/HEAP_TABLE&gt;
      &lt;/PHYSICAL_PROPERTIES&gt;
   &lt;/RELATIONAL_TABLE&gt;
&lt;/TABLE&gt;]]&gt;<span class="hljs-tag">&lt;/<span class="hljs-name">n0:source</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:createSxmlObject</span>&gt;</span>
</code></pre>
<h3 id="heading-dropsxmlobject">dropSxmlObject</h3>
<p>So this should remove your object. It will just try and drop anything you will give it. I allowed myself a small joke in this example ( 'lb up' will try to execute it )</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the object to drop</td></tr>
<tr>
<td>objectType</td><td>attribute string required</td><td>Type of the object that will be droped</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
<tr>
<td>source</td><td>element string required</td><td>From my testing, it doesn't matter what you put here, but it can't be empty</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">n0:dropSxmlObject</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"HOT"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"SC_CORE"</span> <span class="hljs-attr">objectType</span>=<span class="hljs-string">'IT LIKE IT<span class="hljs-symbol">&amp;apos;</span>S '</span> &gt;</span>
            <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>Anything can go here
            <span class="hljs-tag">&lt;/<span class="hljs-name">n0:source</span>&gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:dropSxmlObject</span>&gt;</span>
</code></pre>
<h3 id="heading-droporacleprocedure">dropOracleProcedure</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the procedure to drop</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">n0:dropOracleprocedure</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"HELLOLIQUIBASE"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"ADMIN"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">n0:dropOracleprocedure</span>&gt;</span>
</code></pre>
<h3 id="heading-droporaclefunction">dropOracleFunction</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the function to drop</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">n0:dropOracleFunction</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"HELLOLIQUIBASE"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"ADMIN"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">n0:dropOracleFunction</span>&gt;</span>
</code></pre>
<h3 id="heading-droporaclegrant">dropOracleGrant</h3>
<p>Missing from xsd. I was unable to work out how to make that one work. It requires objectName and ownerName parameters but even those trow an error.</p>
<h3 id="heading-droporaclepackagebody">dropOraclePackageBody</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the package to drop</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">n0:dropOraclePackageBody</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"HELLOLIQUIBASE"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"ADMIN"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">n0:dropOraclePackageBody</span>&gt;</span>
</code></pre>
<h3 id="heading-droporaclepackagespec">dropOraclePackageSpec</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the package to drop</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">n0:dropOraclePackageSpec</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"HELLOLIQUIBASE"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"ADMIN"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">n0:dropOraclePackageSpec</span>&gt;</span>
</code></pre>
<h3 id="heading-droporaclerefconstraint">dropOracleRefConstraint</h3>
<p>Missing from xsd. Unfortunatly i wasn't able to make that one work.</p>
<h3 id="heading-droporacletrigger">dropOracleTrigger</h3>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the trigger to drop</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">n0:dropOracleTrigger</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"HELLOLIQUIBASE"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"ADMIN"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">n0:dropOracleTrigger</span>&gt;</span>
</code></pre>
<h3 id="heading-droporacletypebody">dropOracleTypeBody</h3>
<p>Missing from xsd.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the type to drop</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">n0:dropOracleTypeBody</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"DATA_TYP1"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"SC_CORE"</span>  &gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:dropOracleTypeBody</span>&gt;</span>
</code></pre>
<h3 id="heading-droporacletypespec">dropOracleTypeSpec</h3>
<p>Missing from xsd.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the type to drop</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">n0:dropOracleTypeSpec</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"DATA_TYP1"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"SC_CORE"</span>  &gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:dropOracleTypeSpec</span>&gt;</span>
</code></pre>
<h3 id="heading-droporaclesynonym">dropOracleSynonym</h3>
<p>Missing from xsd.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>Name of the type to drop</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema owning this object</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml">        <span class="hljs-tag">&lt;<span class="hljs-name">n0:dropOracleSynonym</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"DATA_TYP1"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"SC_CORE"</span>  &gt;</span>
        <span class="hljs-tag">&lt;/<span class="hljs-name">n0:dropOracleSynonym</span>&gt;</span>
</code></pre>
<h3 id="heading-runoraclescript">runOracleScript</h3>
<p>This one is well documented <a target="_blank" href="https://docs.oracle.com/en/database/oracle/sql-developer-command-line/20.2/sqcug/using-liquibase-sqlcl.html#GUID-CDA2226F-0E86-4D6B-AA5A-242DD87D71B4">here</a></p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>This is the name of the script to run , it doesn't seem to do anything</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema to run this script on</td></tr>
<tr>
<td>sourceType</td><td>attribute string required</td><td>Set to URL, STRING or FILE</td></tr>
<tr>
<td>source</td><td>element string required</td><td>Depending on sourceType it's either the content of the script to run or a path or URL to script to execute</td></tr>
</tbody>
</table>
</div><p>Example taken from documentation:</p>
<pre><code class="lang-xml">   <span class="hljs-tag">&lt;<span class="hljs-name">n0:runOracleScript</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"myScript"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"JDOE"</span> <span class="hljs-attr">sourceType</span>=<span class="hljs-string">"STRING"</span>&gt;</span>
       <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>&lt;![CDATA[DEFINE table_name = RUNNERSTRING;create table &amp;&amp;table_name (id number);]]&gt;<span class="hljs-tag">&lt;/<span class="hljs-name">n0:source</span>&gt;</span>
    <span class="hljs-tag">&lt;/<span class="hljs-name">n0:runOracleScript</span>&gt;</span>
</code></pre>
<h3 id="heading-runapexscripts">runApexScripts</h3>
<p>This one is undocumented and used by Oracle in creating changesets that install Apex applications. I didn't do any testing on this one. It seems that compared to runOracleScript it adds some context values and probably sets the correct security group.</p>
<div class="hn-table">
<table>
<thead>
<tr>
<td>Name</td><td>Type</td><td>Comment</td></tr>
</thead>
<tbody>
<tr>
<td>objectName</td><td>attribute string required</td><td>This is the name of the script to run , it doesn't seem to do anything</td></tr>
<tr>
<td>ownerName</td><td>attribute string required</td><td>Name of the schema to run this script on</td></tr>
<tr>
<td>sourceType</td><td>attribute string required</td><td>Set to URL, STRING or FILE</td></tr>
<tr>
<td>source</td><td>element string required</td><td>Depending on sourceType it's either the content of the script to run or a path or URL to script to execute</td></tr>
</tbody>
</table>
</div><pre><code class="lang-xml">  <span class="hljs-tag">&lt;<span class="hljs-name">n0:runApexScript</span> <span class="hljs-attr">objectName</span>=<span class="hljs-string">"install"</span> <span class="hljs-attr">objectType</span>=<span class="hljs-string">"SCRIPT"</span> <span class="hljs-attr">ownerName</span>=<span class="hljs-string">"LIQUIBASE_USER"</span> <span class="hljs-attr">sourceType</span>=<span class="hljs-string">"STRING"</span> &gt;</span>
        <span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>&lt;![CDATA[
-- your apex script goes here
-- you can also call a file like this
--@@path/to/file/install.sql
]]&gt;
<span class="hljs-tag">&lt;<span class="hljs-name">n0:source</span>&gt;</span>
<span class="hljs-tag">&lt;/<span class="hljs-name">n0:runApexScript</span>&gt;</span>
</code></pre>
<h2 id="heading-whats-next">What's next</h2>
<p>Hopefully, this list will save you a little bit of time next time You decide to write your own XML changesets. In the future, I plan to release part 2 where we will take a look at how those tags translate into automatic rollbacks.</p>
]]></content:encoded></item></channel></rss>