<?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" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Oracle Developers - Medium]]></title>
        <description><![CDATA[Aggregation of articles from Oracle engineers, Oracle ACEs, and Java Champions on all things Oracle technology. The views expressed are those of  the authors and not necessarily of Oracle. - Medium]]></description>
        <link>https://medium.com/oracledevs?source=rss----749dcac244ef---4</link>
        <image>
            <url>https://cdn-images-1.medium.com/proxy/1*TGH72Nnw24QL3iV9IOm4VA.png</url>
            <title>Oracle Developers - Medium</title>
            <link>https://medium.com/oracledevs?source=rss----749dcac244ef---4</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Thu, 12 Nov 2026 18:25:21 GMT</lastBuildDate>
        <atom:link href="https://medium.com/feed/oracledevs" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com/" rel="hub"/>
        <item>
            <title><![CDATA[ODP.NET SQL Scripting and the Deep Data Security End-User Security Context Provider]]></title>
            <link>https://medium.com/oracledevs/odp-net-sql-scripting-and-the-deep-data-security-end-user-security-context-provider-b31c59aedef1?source=rss----749dcac244ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/b31c59aedef1</guid>
            <category><![CDATA[opentelemetry]]></category>
            <category><![CDATA[aspnetcore]]></category>
            <category><![CDATA[sql-scripting]]></category>
            <category><![CDATA[deep-data-security]]></category>
            <category><![CDATA[odpnet]]></category>
            <dc:creator><![CDATA[Alex Keh]]></dc:creator>
            <pubDate>Mon, 09 Nov 2026 21:11:01 GMT</pubDate>
            <atom:updated>2026-11-09T21:10:59.784Z</atom:updated>
            <content:encoded><![CDATA[<h4>ODP.NET 23.26.4 brings production ready SQL scripting, stronger application security, updated monitoring, and cleaner pooled sessions</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*A-ODzSJuDm0hfXPWpNv7nw.png" /></figure><h3>Key Takeaways</h3><ul><li>.NET applications can now run Oracle SQL and PL/SQL scripts from files, cloud storage, REST APIs, or other stream based sources.</li><li>A new ASP.NET Core provider automatically applies Deep Data Security context with minimal application changes.</li><li>ODP.NET Deep Data Security no longer requires a human user identity, supporting services and AI agents that operate independently.</li><li>The release also adds current OpenTelemetry conventions, session state resets, and performance improvements for EF Core and asynchronous operations.</li></ul><h3>Introduction</h3><p>I’m proud to announce that <a href="https://www.nuget.org/packages/Oracle.ManagedDataAccess.Core/23.26.400">ODP.NET 23.26.4</a> is now available on NuGet with these features:</p><ul><li>Execute SQL and PL/SQL scripts from .NET</li><li>Use the Deep Data Security end-user security context provider with ASP.NET Core</li><li>Configure an optional user identity for Deep Data Security</li><li>Incorporate new ODP.NET OpenTelemetry attribute tags and semantic conventions</li><li>Reset each database session to the original application state</li><li>Benefit from ODP.NET performance improvements</li></ul><p>Let’s explore these enhancements.</p><h3>SQL Script Execution for .NET</h3><p>As more workloads move to cloud environments and AI agents administer them, organizations need programmatic interfaces for executing SQL and PL/SQL scripts. Command-line tools were generally designed for human operators with privileged access to on-premises databases. AI workloads instead rely on automated processes with least-privilege access, while cloud environments often restrict direct administrative access to database resources.</p><p>ODP.NET Core SQL script execution addresses these requirements. .NET applications can execute SQL and PL/SQL commands programmatically from a stream. They can bind script parameters as JSON and receive results as a JSON string.</p><p>Now in production with ODP.NET 23.26.4, this capability helps .NET applications apply and automate database changes. Scripts can come from files, blob storage, REST APIs, or any other source that can be read by a .NET StreamReader.</p><p>At runtime, an application passes a StreamReader to the OracleCommand.ExecuteStatements extension method. Parameters can be supplied as a JSON document through OracleCommand.SetParametersForExecuteStatements or through the OracleCommand.Parameters collection.</p><p>I described <a href="https://medium.com/@alex.keh/run-oracle-sql-scripts-in-net-bd4e950ff104">ODP.NET SQL script execution in a previous blog post</a> if you want more details.</p><p>In short, this feature gives .NET developers and DevOps teams a flexible scripting tool for modern cloud and AI applications.</p><h3>Deep Data Security: End-User Security Context Provider in ASP.NET Core</h3><p>The new ODP.NET Deep Data Security (DeepSec) provider model lets ASP.NET Core applications adopt Oracle DeepSec with minimal code changes.</p><p>After a one-time setup, existing applications can use DeepSec without explicitly creating or assigning an end-user security context.</p><p>Oracle AI Database version 23.26.2 first introduced the feature. I provided an overview of <a href="https://medium.com/@alex.keh/database-enforced-authorization-for-agentic-ai-net-applications-b383408453f6">ODP.NET Deep Data Security and its benefits in a previous blog post</a>. Here’s how to use the new provider with ASP.NET Core.</p><p>First, install the <a href="https://www.nuget.org/packages/Oracle.ManagedDataAccess.DependencyInjection">Oracle.ManagedDataAccess.DependencyInjection</a> NuGet package, configure its settings, and call builder.Services.AddOracleEndUserSecurityContextProvider(builder.Configuration) before builder.Build().</p><p>Applications provide end-user security information through the authenticated request. When enabled, ODP.NET discovers and uses the provider implicitly. Immediately before an eligible database operation, the provider builds the security context from the current request and attaches it to the database operation. Requests must use JSON Web Token (JWT) bearer authentication so the provider can access the request context.</p><p>Configure the provider in appsettings.json or through the ASP.NET Core IConfiguration model under Oracle.ManagedDataAccess:EndUserSecurityContext. The provider is enabled by default because the EnableEndUserSecurityContextProvider option defaults to true.</p><p>Here’s a sample appsettings.json configuration for the end-user security context provider:</p><pre>{<br>  &quot;Oracle.ManagedDataAccess&quot;: {<br>    &quot;EndUserSecurityContext&quot;: {<br>      &quot;EnableEndUserSecurityContextProvider&quot;: true,<br>      &quot;Type&quot;: &quot;AZURE&quot;,<br>      &quot;TokenUri&quot;: &quot;https://&lt;identity-provider-host&gt;/&lt;tenant&gt;/oauth2/v2.0/token&quot;,<br>      &quot;GrantType&quot;: &quot;client_credentials&quot;,<br>      &quot;ClientId&quot;: &quot;&lt;client-id&gt;&quot;,<br>      &quot;ClientSecret&quot;: &quot;&lt;client-secret&gt;&quot;,<br>      &quot;Scope&quot;: &quot;&lt;database-token-scope&gt;&quot;,<br>      &quot;TokenRenewalOffsetSeconds&quot;: 60,<br>      &quot;SessionDataRoles&quot;: &quot;&lt;role-1&gt;,&lt;role-2&gt;&quot;,<br>      &quot;EndUserContextAttribute&quot;: &quot;{\&quot;&lt;namespace&gt;\&quot;:{\&quot;&lt;key&gt;\&quot;:\&quot;&lt;value&gt;\&quot;}}&quot;,<br>      &quot;FetchGroupsOnScope&quot;: &quot;&lt;optional-oci-groups-scope&gt;&quot;,<br>      &quot;RoleClaimType&quot;: &quot;ORACLE_DATA_ROLE&quot;,<br>      &quot;AttributeClaimType&quot;: &quot;ORACLE_CONTEXT_ATTRIBUTE&quot;<br>    }<br>  }<br>}</pre><p>Note: In a production app, you would supply the client secret in a more secure manner, such as from vault storage.</p><p>Once configured, import Oracle.ManagedDataAccess.DependencyInjection.EndUserSecurityContext and call its registration API before building the host to enable the provider. Here’s a code snippet of what that looks like:</p><pre>using Oracle.ManagedDataAccess.DependencyInjection.EndUserSecurityContext;<br><br>var builder = WebApplication.CreateBuilder(args);<br>builder.Services.AddAuthentication(/* app authentication setup */);<br>builder.Services.AddAuthorization();<br><br>builder.Services.AddOracleEndUserSecurityContextProvider(builder.Configuration);<br><br>var app = builder.Build();<br><br>app.UseAuthentication();<br>app.UseAuthorization();<br><br>app.MapGet(&quot;/health&quot;, () =&gt; Results.Ok(&quot;ok&quot;));<br>app.Run();</pre><p>For more information, see the <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/odpnt/featConnecting.html#end-user-security-context-provider">Deep Data Security End-User Security Context Provider section in the ODP.NET Developer’s Guide</a>. For database set up, review the <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/ddscg/quick-starts-configure-oracle-deep-data-security.html">configuration chapter in the Oracle Deep Data Security Guide</a>.</p><h3>Deep Data Security: Optional User Identity</h3><p>Previously, DeepSec required an end-user identity: an access token or username. This release makes the identity optional.</p><p>Some services host multiple applications and AI agents in a single database. Each application or agent may use its own connection pool and perform work independently, without acting on behalf of a human user. By removing the identity requirement, DeepSec now supports these services alongside AI agents and human users.</p><p>Two new OracleEndUserSecurityContext methods create contexts without a user identity:</p><ul><li><a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/odpnt/EndUserSecurityContextCreateWithDatabaseAccessToken.html#GUID-CDC0F1E9-79A7-428B-999B-EE68DF0BA4D5">CreateWithDatabaseAccessToken(string)</a> — creates an end-user security context using only a database access token.</li><li><a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/odpnt/EndUserSecurityContextCreateWithoutEndUser.html#GUID-429BE95B-1927-4532-81C8-E773125AA4E4">CreateWithoutEndUser()</a> — creates a new end-user security context with the same database access token, data role, and attribute values as the current instance except for the end user identity.</li></ul><h3>New OpenTelemetry Attribute Tags and Semantic Conventions</h3><p>ODP.NET 23.26.4 supports the latest OpenTelemetry database semantic conventions and attribute names. Deprecated attributes remain available for compatibility.</p><p>Developers can configure ODP.NET to emit traces using the latest attributes, deprecated attributes, or both. The changes include:</p><ul><li>db.name is deprecated; db.namespace replaces it.</li><li>db.odp.connection.id is deprecated; oracle.db.connection.id replaces it.</li><li>db.odp.sql_id is deprecated; oracle.db.sql.id replaces it.</li><li>db.statement is deprecated; db.query.text replaces it.</li><li>db.system is deprecated; db.system.name replaces it.</li><li>db.user is deprecated.</li></ul><p>To configure the behavior, use one of these settings:</p><ul><li>OTEL_SEMCONV_STABILITY_OPT_IN environment variable</li><li>OpenTelemetrySemconvStabilityOptIn .NET configuration setting, or</li><li>OracleConfiguration.OpenTelemetrySemconvStabilityOptIn property</li></ul><p>The configuration sources support the same values. They are:</p><p>• database: emit the new tags only;</p><p>• database/dup: emit both the deprecated and new tags;</p><p>• any other value, or no value: emit the deprecated tags only.</p><p>Read more about <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/odpnt/featOpenTelemetry.html#odpnet-opentelemetry-traces">ODP.NET OpenTelemetry in the ODP.NET Developer’s Guide</a>.</p><h3>Reset Each Database Session to the Original Application State</h3><p>When an ODP.NET connection returns to the pool, ODP.NET can reset the client and database session state to the original login state. This prevents state from leaking to the next user who receives the connection. Enable this feature by setting the RESET_STATE database service attribute to LOGIN. ODP.NET Core, managed ODP.NET, and unmanaged ODP.NET support it starting with version 23.26.4.</p><p>With this setting, each pooled connection starts with the same session state, reducing artifacts that might persist after a failover.</p><p>To learn more about using the RESET_STATE attribute and LOGIN value, read the <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/adfns/high-availability.html#GUID-7950B60E-E05A-4B96-ACFC-777074B768F2">Application Continuity section of the Oracle Database Development Guide</a>.</p><h3>ODP.NET Performance Improvements</h3><p>For this release, ODP.NET includes performance improvements across the provider, including faster EF Core SaveChanges operations and improved asynchronous execution. Performance remains a focus, and we plan to deliver further improvements in upcoming ODP.NET releases in 2027.</p><h3>Coming Soon</h3><p>In November, Microsoft will release .NET 11 and EF Core 11. ODP.NET will support both soon afterward, and I will announce availability in future blog posts.</p><p>Until then, read more about the <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/odpnt/release_changes.html#GUID-608C2CE9-86B7-4B29-BF17-8B33826E6256">ODP.NET 23.26.4 features in the ODP.NET Developer’s Guide</a> and download ODP.NET packages from <a href="https://www.nuget.org/profiles/Oracle">NuGet Gallery</a>.</p><h3>FAQs</h3><p><strong>How does SQL script execution exchange inputs and results?</strong><br>Applications can bind parameters through JSON or the standard command parameter collection, while execution results are returned as JSON.</p><p><strong>How is the Deep Data Security provider enabled?</strong><br>Install the dependency injection package, configure the provider, and register it before building the ASP.NET Core application. The provider is enabled by default after setup.</p><p><strong>Does the release remove older OpenTelemetry attributes?</strong><br>No. It supports the latest attribute names and conventions while retaining deprecated attributes for compatibility.</p><p><strong>What does the new session reset feature do?</strong><br>It restores pooled database connections to their original login state, helping prevent one user’s session settings from affecting the next user.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=b31c59aedef1" width="1" height="1" alt=""><hr><p><a href="https://medium.com/oracledevs/odp-net-sql-scripting-and-the-deep-data-security-end-user-security-context-provider-b31c59aedef1">ODP.NET SQL Scripting and the Deep Data Security End-User Security Context Provider</a> was originally published in <a href="https://medium.com/oracledevs">Oracle Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Run Oracle SQL Scripts in .NET]]></title>
            <link>https://medium.com/oracledevs/run-oracle-sql-scripts-in-net-bd4e950ff104?source=rss----749dcac244ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/bd4e950ff104</guid>
            <category><![CDATA[odpnet]]></category>
            <category><![CDATA[sql]]></category>
            <category><![CDATA[plsql]]></category>
            <category><![CDATA[sql-scripting]]></category>
            <category><![CDATA[oracle-ai-database]]></category>
            <dc:creator><![CDATA[Alex Keh]]></dc:creator>
            <pubDate>Mon, 09 Nov 2026 21:09:12 GMT</pubDate>
            <atom:updated>2026-11-09T21:09:11.055Z</atom:updated>
            <content:encoded><![CDATA[<h4><strong>ODP.NET script execution is designed </strong>for safer, automated Oracle database updates across cloud and AI workflows</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*kIStepNXoVZUPJ2JL0NLCg.png" /></figure><h3>Key takeaways</h3><ul><li>The preview feature runs multiple SQL and PL/SQL statements directly from .NET applications without a separate scripting tool.</li><li>Scripts can come from files, cloud storage, REST APIs, or any other source available as a .NET stream.</li><li>Results, output parameters, metadata, and detailed errors are returned as JSON for easy processing and recovery.</li><li>Developers can control error handling, parameter binding, transactions, output limits, and concurrent script execution using .NET.</li></ul><p>ODP.NET Core introduces a scripting option for applying database changes. Developers can now programmatically manage schema changes, errors, and scale their workflows more easily without a separate tool. ODP.NET scripting benefits include:</p><ul><li>Cloud and AI-friendly</li></ul><p>As more applications move to the cloud and adopt AI, management tools must support cloud automation and AI-driven operations. ODP.NET scripting can run in the cloud and enable both AI agents and human operators to execute database tasks.</p><p>· Enhanced security and safety</p><p>ODP.NET scripting can retrieve credentials at runtime from encrypted vaults or secure environment configurations, so they do not appear in command history.</p><p>· Robust error handling and conditional logic</p><p>.NET applications can inspect detailed statement execution errors to automate recovery workflows, such as retries after a temporary network outage or rolling back changes. Developers can inspect the execution results to determine application-level failures and take the appropriate next steps.</p><p>· Seamless data sharing and state management</p><p>Programmers can capture script output as JSON, parse it, and manipulate it as variables or objects. Keeping intermediate results in memory can avoid disk I/O, which can improve performance and reduce exposure of sensitive data.</p><p>· Parallelism and concurrency</p><p>.NET provides APIs for asynchronous programming, threads, and parallel processing, making it easier to run multiple scripts concurrently.</p><h3><strong>ODP.NET SQL Script Execution Features</strong></h3><p>For these reasons, we have introduced ODP.NET SQL scripting. Available on NuGet as a preview release, the <a href="https://www.nuget.org/packages/Oracle.ManagedDataAccess.SQLScriptExecution">Oracle.ManagedDataAccess.SQLScriptExecution</a> package supports:</p><ul><li>Executing SQL scripts with OracleCommand.ExecuteStatements</li><li>Running multiple SQL and PL/SQL statements from one stream</li><li>Returning result sets, output parameters, and errors as JSON</li><li>Binding parameters through JSON or APIs</li><li>Choosing whether to stop after an error or continue execution</li><li>Setting LOB and LONG fetch limits for JSON results</li><li>Getting statement-level metadata with detailed error information</li></ul><p>Let’s explore ODP.NET SQL script execution.</p><h3><strong>Using SQL Script Execution</strong></h3><p>ODP.NET Core can execute SQL and PL/SQL from a StreamReader so that developers can apply and automate their database changes. The scripts can originate from a file, blob storage, a REST API, or any other source that can be represented by a .NET StreamReader.</p><p>Programmatically, a script is represented by a .NET StreamReader object and passed to an OracleCommand.ExecuteStatements extension method to run.</p><p>Parameters can be provided either as a JSON document via OracleCommand.SetParametersForExecuteStatements method or through the OracleCommand.Parameters collection. If both are provided, the parameter collection takes precedence. JSON-based parameters support bind-by-name only, while the parameter collection supports bind-by-name and bind-by-position.</p><p>SQL script execution supports binding all the parameter data types ODP.NET can bind except for associative arrays, array binds, and user-defined types.</p><p>Output result sets, parameters, and metadata, such as rows affected are all returned as JSON strings.</p><p>For every error, its related information is returned, including its number, message, and statement parsing metadata. Developers can configure whether the script aborts upon an error (default) or continues to execute after the error. After an error, previously committed statements are not undone. Retries must account for partially committed scripts.</p><p>These scripts can only contain SQL and PL/SQL syntax. A forward slash (/) on a line by itself is supported as a PL/SQL block terminator. It is recognized by the SQL script execution parser as a statement terminator and is not included in the statement text returned in the result JSON. Other SQL*Plus and SQLcl syntax is not supported.</p><p>By default, statements execute with the connection’s auto-commit behavior. Transactions are supported. However, if a DDL statement is supplied within a transaction, its execution implicitly commits the current transaction. The remaining statements are executed in connection’s auto-commit mode.</p><p>ODP.NET SQL script execution is backwards compatible with the same database versions as the ODP.NET version it depends on. For example, ODP.NET 26ai is backwards compatible with Oracle Database 19c.</p><h4><strong>Sample Scripts and Output</strong></h4><p>Here is a C# code sample that supplies a script and its input parameters through StreamReader objects, sets the script error option behavior, and executes the script. The results are returned as a JSON string. The script and parameters are retrieved from Oracle Cloud infrastructure Object Storage.</p><pre>const string namespaceName = &quot;&lt;OCI_NAMESPACE&gt;&quot;;<br>const string bucketName = &quot;&lt;BUCKET_NAME&gt;&quot;;<br>const string sqlObjectName = &quot;oci_sql_script.sql&quot;;<br>const string jsonParametersObjectName = &quot;oci_sql_parameters.json&quot;;<br><br>// Authenticate using the DEFAULT OCI profile.<br>var provider = new ConfigFileAuthenticationDetailsProvider(&quot;DEFAULT&quot;);<br><br>using var objectStorageClient = new ObjectStorageClient(provider);<br><br>// Retrieve the SQL script from OCI Object Storage.<br>var sqlRequest = new GetObjectRequest<br>{<br>    NamespaceName = namespaceName,<br>    BucketName = bucketName,<br>    ObjectName = sqlObjectName<br>};<br><br>var sqlResponse = await objectStorageClient.GetObject(<br>    sqlRequest,<br>    completionOption: HttpCompletionOption.ResponseHeadersRead);<br><br>using var sqlReader = new StreamReader(<br>    sqlResponse.InputStream,<br>    Encoding.UTF8,<br>    detectEncodingFromByteOrderMarks: true);<br><br>// Retrieve the JSON parameters from OCI Object Storage.<br>var jsonParametersRequest = new GetObjectRequest<br>{<br>    NamespaceName = namespaceName,<br>    BucketName = bucketName,<br>    ObjectName = jsonParametersObjectName<br>};<br><br>var jsonResponse =<br>      await objectStorageClient.GetObject(jsonParametersRequest);<br><br>using var jsonParametersReader = new StreamReader(<br>    jsonResponse.InputStream,<br>    Encoding.UTF8,<br>    detectEncodingFromByteOrderMarks: true);<br><br>// Open the database connection.<br>using var connection = new OracleConnection(&quot;&lt;CONNECTION_STRING&gt;&quot;);<br>connection.Open();<br><br>using var command = connection.CreateCommand();<br><br>// Set parameters from the JSON stream.<br>command.SetParametersForExecuteStatements(jsonParametersReader);<br><br>// Set execution behavior.<br>var executionSettings = new OracleStatementExecutionSettings<br>{<br>    ExecutionOption = OracleExecutionOption.ContinueOnError<br>};<br><br>// Execute the SQL/PLSQL script from the stream.<br>string results = command.ExecuteStatements(<br>    sqlReader,<br>    executionSettings);</pre><p>The SQL script contains a series of SQL and/or PL/SQL statements.</p><pre>insert into EMPTABLE(EMPNO, ENAME) values (5,&#39;John&#39;);<br>insert into INVALIDEMPTABLE(EMPNO, ENAME) values (10, &#39;Max&#39;) returning EMPNO into :param1;<br>select * FROM EMPTABLE where ENAME = :param2;<br><br>BEGIN<br>:p_message := NVL(:p_message, &#39;&#39;) || &#39;is an anonymous PL/SQL block.&#39;;<br>END;<br>/</pre><p>A JSON stream provides the parameter binding information for the SQL script. The JSON below supplies the parameters for the SQL script above.</p><pre>[<br>  {<br>    &quot;name&quot;: &quot;param1&quot;,<br>    &quot;type&quot;: &quot;NUMBER&quot;,<br>    &quot;direction&quot;: &quot;Output&quot;<br>  },<br>  {<br>    &quot;name&quot;: &quot;param2&quot;,<br>    &quot;type&quot;: &quot;VARCHAR2&quot;,<br>    &quot;size&quot;: 32,<br>    &quot;value&quot;: &quot;John&quot;,<br>    &quot;direction&quot;: &quot;Input&quot;<br>  },<br>  {<br>    &quot;name&quot;: &quot;p_message&quot;,<br>    &quot;type&quot;: &quot;VARCHAR2&quot;,<br>    &quot;size&quot;: 40,<br>    &quot;value&quot;: &quot;This &quot;,<br>    &quot;direction&quot;: &quot;InputOutput&quot;<br>  }<br>]</pre><p>After the script executes, the results from the SELECT statement are returned as a JSON output string. Here are several examples of the output.</p><blockquote>Note: The JSON document returned from SQL script execution does not escape the characters &lt;, &gt;, &amp;, and ‘. If the JSON is embedded in HTML or ‘&lt;script&gt;’ content, apply the appropriate context-specific encoding before inserting it to avoid any security vulnerabilities.</blockquote><p><strong>DML Statement Output</strong></p><pre>{ <br>    &quot;statement&quot;: &quot;insert into EMPTABLE(EMPNO, ENAME) values (5,&#39;John&#39;)&quot;,  <br>    &quot;errors&quot;: null,<br>    &quot;rowsAffected&quot;: 1,<br>    &quot;result&quot;: null<br>}</pre><p><strong>Execution Error Output</strong></p><pre>{<br>  &quot;statement&quot;: &quot;insert into INVALIDEMPTABLE(EMPNO, ENAME) values (10, &#39;Max&#39;) returning EMPNO into :param1&quot;,<br>  &quot;rowsAffected&quot;: null,<br>  &quot;result&quot;: null,<br>  &quot;errors&quot;: {<br>    &quot;startLine&quot;: 2,<br>    &quot;endLine&quot;: 2,<br>    &quot;startColumn&quot;: 1,<br>    &quot;endColumn&quot;: 90,<br>    &quot;items&quot;: [<br>      {<br>        &quot;number&quot;: 942,<br>        &quot;message&quot;: &quot;ORA-00942: table or view does not exist&quot;<br>      }<br>    ]<br>  }<br>}</pre><p><strong>SELECT Output</strong></p><pre>{<br>    &quot;statement&quot;: &quot;select * FROM EMPTABLE where ENAME = :param2;&quot;,<br>    &quot;errors&quot;: null,<br>    &quot;rowsAffected&quot;: -1,<br>    &quot;result&quot;: [<br>      {<br>        &quot;columns&quot;: [<br>          { <br>            &quot;name&quot;: &quot;EMPNO&quot;, <br>            &quot;type&quot;: &quot;NUMBER&quot;,<br>            &quot;precision&quot;: 0,<br>            &quot;scale&quot;: -127,<br>            &quot;columnDefinition&quot;: &quot;NUMBER&quot;<br>          },<br>          { <br>            &quot;name&quot;: &quot;ENAME&quot;, <br>            &quot;type&quot;: &quot;VARCHAR2&quot;,<br>            &quot;size&quot;: 32,<br>            &quot;isByteSemantic&quot;: true,<br>            &quot;columnDefinition&quot;: &quot;VARCHAR2(32)&quot;<br>          }<br>          ],<br>        &quot;rows&quot;: [<br>          [<br>            5, <br>            &quot;John&quot;<br>          ],<br>          [<br>            20, <br>            &quot;John&quot;<br>          ]<br>        ]<br>      }<br>    ]<br>}</pre><p><strong>PL/SQL Output</strong></p><pre>{<br>    &quot;statement&quot;: &quot;BEGIN\r\n:p_message := NVL(:p_message, &#39;&#39;) || &#39;is an anonymous PL/SQL block.&#39;;\r\nEND;\r\n&quot;,<br>    &quot;errors&quot;: null,<br>    &quot;rowsAffected&quot;: -1,<br>    &quot;result&quot;: [<br>      {<br>        &quot;columns&quot;: [<br>          {<br>            &quot;name&quot;: &quot;p_message&quot;,<br>            &quot;type&quot;: &quot;VARCHAR2&quot;<br>          }<br>        ],<br>        &quot;rows&quot;: [<br>          [<br>            &quot;This is an anonymous PL/SQL block.&quot;<br>          ]<br>        ]<br>      }<br>    ]<br>}</pre><h4><strong>Oracle Data Type Representation in JSON Results</strong></h4><p>This table defines how Oracle database data types are mapped to JSON value types (JsonValueKind) in .NET for accurate JSON representation</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/542/1*uSFFrJ8dntm2gNF66b9K0A.png" /><figcaption>ODP.NET SQL Scripting data type representation in JSON</figcaption></figure><p>Now that the capabilities and features have been covered, let’s explore the ODP.NET SQL scripting APIs in detail.</p><h3><strong>SQL Script Execution APIs</strong></h3><h4><strong>OracleCommand.ExecuteStatements Method</strong></h4><p>This method executes a stream of SQL and PL/SQL statements and returns any results as a JSON string.</p><p><strong>Declaration</strong></p><pre>public static string ExecuteStatements (this OracleCommand cmd, StreamReader streamReader, OracleStatementExecutionSettings executionSettings = null);</pre><p><strong>Parameters</strong></p><ul><li><em>cmd</em> — The OracleCommand instance</li><li><em>streamReader — </em>The stream of SQL and PL/SQL statements to execute.</li><li><em>executionSettings — </em>The mode the statements are executed and output size limits for LONG/LOB columns.</li></ul><p><strong>Return Value</strong></p><p>A JSON string with result sets, result metadata, and error information, if applicable.</p><p><strong>Remarks</strong></p><p>By default, each statement execution is auto-committed. App can use ExecuteStatements in a transaction by starting the transaction before calling this method.</p><p>executionSettings determines behavior when a statement returns an error.</p><p><strong>Statement Execution Result Format</strong></p><p>The return value is output in the following JSON schema format:</p><pre>{<br>  &quot;$schema&quot;: &quot;https://json-schema.org/draft/2020-12/schema&quot;,<br>  &quot;$id&quot;: &quot;urn:oracle:manageddataaccess:sqlscriptexecution:schemas:statement-result:1.0&quot;,<br>  &quot;title&quot;: &quot;ScriptExecutionResults&quot;,<br>  &quot;type&quot;: &quot;array&quot;,<br>  &quot;items&quot;: {<br>    &quot;type&quot;: &quot;object&quot;,<br>    &quot;required&quot;: [<br>      &quot;statement&quot;,<br>      &quot;errors&quot;,<br>      &quot;rowsAffected&quot;,<br>      &quot;result&quot;<br>    ],<br>    &quot;properties&quot;: {<br>      &quot;statement&quot;: {<br>        &quot;type&quot;: &quot;string&quot;,<br>        &quot;description&quot;: &quot;SQL or PL/SQL command text&quot;<br>      },<br>      &quot;errors&quot;: {<br>        &quot;description&quot;: &quot;Error details if execution failed; null otherwise&quot;,<br>        &quot;oneOf&quot;: [<br>          {<br>            &quot;type&quot;: &quot;null&quot;<br>          },<br>          {<br>            &quot;type&quot;: &quot;object&quot;,<br>            &quot;required&quot;: [<br>              &quot;startLine&quot;,<br>              &quot;endLine&quot;,<br>              &quot;startColumn&quot;,<br>              &quot;endColumn&quot;,<br>              &quot;items&quot;<br>            ],<br>            &quot;properties&quot;: {<br>              &quot;startLine&quot;: {<br>                &quot;type&quot;: &quot;integer&quot;,<br>                &quot;minimum&quot;: 1<br>              },<br>              &quot;endLine&quot;: {<br>                &quot;type&quot;: &quot;integer&quot;,<br>                &quot;minimum&quot;: 1<br>              },<br>              &quot;startColumn&quot;: {<br>                &quot;type&quot;: &quot;integer&quot;,<br>                &quot;minimum&quot;: 1<br>              },<br>              &quot;endColumn&quot;: {<br>                &quot;type&quot;: &quot;integer&quot;,<br>                &quot;minimum&quot;: 1<br>              },<br>              &quot;items&quot;: {<br>                &quot;type&quot;: &quot;array&quot;,<br>                &quot;minItems&quot;: 1,<br>                &quot;items&quot;: {<br>                  &quot;type&quot;: &quot;object&quot;,<br>                  &quot;required&quot;: [<br>                    &quot;number&quot;,<br>                    &quot;message&quot;<br>                  ],<br>                  &quot;properties&quot;: {<br>                    &quot;number&quot;: {<br>                      &quot;type&quot;: [<br>                        &quot;integer&quot;,<br>                        &quot;null&quot;<br>                      ],<br>                      &quot;description&quot;: &quot;Oracle error number or null for non-SQL errors&quot;<br>                    },<br>                    &quot;message&quot;: {<br>                      &quot;type&quot;: &quot;string&quot;<br>                    }<br>                  },<br>                  &quot;additionalProperties&quot;: false<br>                }<br>              }<br>            },<br>            &quot;additionalProperties&quot;: false<br>          }<br>        ]<br>      },<br>      &quot;rowsAffected&quot;: {<br>        &quot;description&quot;: &quot;-1 for successful SELECT, DDL, and PL/SQL statements; a nonnegative affected-row count for successful DML; null when statement execution fails&quot;,<br>        &quot;oneOf&quot;: [<br>          {<br>            &quot;type&quot;: &quot;integer&quot;<br>          },<br>          {<br>            &quot;type&quot;: &quot;null&quot;<br>          }<br>        ]<br>      },<br>      &quot;result&quot;: {<br>        &quot;description&quot;: &quot;Query result sets or OUT / IN OUT bind values&quot;,<br>        &quot;oneOf&quot;: [<br>          {<br>            &quot;type&quot;: &quot;null&quot;<br>          },<br>          {<br>            &quot;type&quot;: &quot;array&quot;,<br>            &quot;items&quot;: {<br>              &quot;type&quot;: &quot;object&quot;,<br>              &quot;required&quot;: [<br>                &quot;columns&quot;,<br>                &quot;rows&quot;<br>              ],<br>              &quot;properties&quot;: {<br>                &quot;columns&quot;: {<br>                  &quot;description&quot;: &quot;Metadata for each column in the result set, in the same order as values appear in each row.&quot;,<br>                  &quot;type&quot;: &quot;array&quot;,<br>                  &quot;items&quot;: {<br>                    &quot;$ref&quot;: &quot;#/$defs/columnMetadata&quot;<br>                  }<br>                },<br>                &quot;rows&quot;: {<br>                  &quot;description&quot;: &quot;Result set rows; each row is an array of column values&quot;,<br>                  &quot;type&quot;: &quot;array&quot;,<br>                  &quot;items&quot;: {<br>                    &quot;type&quot;: &quot;array&quot;,<br>                    &quot;items&quot;: {}<br>                  }<br>                }<br>              },<br>              &quot;additionalProperties&quot;: false<br>            }<br>          }<br>        ]<br>      }<br>    },<br>    &quot;additionalProperties&quot;: false<br>  },<br>  &quot;$defs&quot;: {<br>    &quot;oracleNativeType&quot;: {<br>      &quot;type&quot;: &quot;string&quot;,<br>      &quot;description&quot;: &quot;Oracle native data type or PL/SQL data type.&quot;,<br>      &quot;enum&quot;: [<br>        &quot;NUMBER&quot;,<br>        &quot;INT&quot;,<br>        &quot;INTEGER&quot;,<br>        &quot;FLOAT&quot;,<br>        &quot;LONG&quot;,<br>        &quot;BINARY_FLOAT&quot;,<br>        &quot;BINARY_DOUBLE&quot;,<br>        &quot;VARCHAR2&quot;,<br>        &quot;NVARCHAR2&quot;,<br>        &quot;CHAR&quot;,<br>        &quot;NCHAR&quot;,<br>        &quot;CLOB&quot;,<br>        &quot;NCLOB&quot;,<br>        &quot;BLOB&quot;,<br>        &quot;BFILE&quot;,<br>        &quot;DATE&quot;,<br>        &quot;TIMESTAMP&quot;,<br>        &quot;TIMESTAMP WITH TIME ZONE&quot;,<br>        &quot;TIMESTAMP WITH LOCAL TIME ZONE&quot;,<br>        &quot;INTERVAL DAY TO SECOND&quot;,<br>        &quot;INTERVAL YEAR TO MONTH&quot;,<br>        &quot;RAW&quot;,<br>        &quot;LONG RAW&quot;,<br>        &quot;ROWID&quot;,<br>        &quot;BOOLEAN&quot;,<br>        &quot;JSON&quot;,<br>        &quot;VECTOR&quot;,<br>        &quot;REFCURSOR&quot;,<br>        &quot;SYS_REFCURSOR&quot;,<br>        &quot;XMLTYPE&quot;<br>      ]<br>    },<br>    &quot;columnMetadata&quot;: {<br>      &quot;type&quot;: &quot;object&quot;,<br>      &quot;required&quot;: [<br>        &quot;name&quot;,<br>        &quot;type&quot;<br>      ],<br>      &quot;properties&quot;: {<br>        &quot;name&quot;: {<br>          &quot;type&quot;: &quot;string&quot;,<br>          &quot;description&quot;: &quot;Column name for SELECT result sets, or parameter name for OUT/INOUT/ReturnValue bind results&quot;<br>        },<br>        &quot;type&quot;: {<br>          &quot;$ref&quot;: &quot;#/$defs/oracleNativeType&quot;<br>        },<br>        &quot;precision&quot;: {<br>          &quot;type&quot;: &quot;integer&quot;,<br>          &quot;minimum&quot;: 0,<br>          &quot;description&quot;: &quot;Numeric precision (optional)&quot;<br>        },<br>        &quot;scale&quot;: {<br>          &quot;type&quot;: &quot;integer&quot;,<br>          &quot;minimum&quot;: -127,<br>          &quot;description&quot;: &quot;Numeric scale (optional)&quot;<br>        },<br>        &quot;size&quot;: {<br>          &quot;type&quot;: &quot;integer&quot;,<br>          &quot;minimum&quot;: 1,<br>          &quot;description&quot;: &quot;Size of the column in bytes or chars depending on isByteSemantic (optional)&quot;<br>        },<br>        &quot;isByteSemantic&quot;: {<br>          &quot;type&quot;: &quot;boolean&quot;,<br>          &quot;description&quot;: &quot;True for byte semantics, false for character semantics (optional)&quot;<br>        },<br>        &quot;vectorDimensions&quot;: {<br>          &quot;type&quot;: &quot;string&quot;,<br>          &quot;description&quot;: &quot;Vector dimension count for VECTOR types (optional)&quot;<br>        },<br>        &quot;vectorType&quot;: {<br>          &quot;type&quot;: &quot;string&quot;,<br>          &quot;enum&quot;: [<br>            &quot;DENSE&quot;,<br>            &quot;SPARSE&quot;<br>          ],<br>          &quot;description&quot;: &quot;Vector storage type for VECTOR columns (optional)&quot;<br>        },<br>        &quot;vectorDimensionFormat&quot;: {<br>          &quot;type&quot;: &quot;string&quot;,<br>          &quot;description&quot;: &quot;Vector dimension format for VECTOR columns (optional)&quot;<br>        },<br>        &quot;columnDefinition&quot;: {<br>          &quot;type&quot;: &quot;string&quot;,<br>          &quot;description&quot;: &quot;The column definition that matches what SQL*Plus returns when describing a table with this column&quot;<br>        }<br>      },<br>      &quot;additionalProperties&quot;: false<br>    }<br>  }<br>}</pre><h4><strong>OracleCommand.SetParametersForExecuteStatements Method</strong></h4><p>This method binds a JSON stream of parameters for SQL script execution.</p><p><strong>Declaration</strong></p><pre>public static void SetParametersForExecuteStatements(this OracleCommand cmd, StreamReader jsonParameters)</pre><p><strong>Parameter</strong></p><ul><li><em>cmd</em> — The OracleCommand instance</li><li><em>jsonParameters — </em>The parameter information to bind to the script’s SQL and PL/SQL statements.</li></ul><p><strong>Remarks</strong></p><p>Parameters can only be bound by name using this method.</p><p><strong>Bind Parameter Format</strong></p><p>SQL script execution parameters are bound using the following JSON schema format:</p><pre>{<br>  &quot;$schema&quot;: &quot;https://json-schema.org/draft/2020-12/schema&quot;,<br>  &quot;$id&quot;: &quot;urn:oracle:manageddataaccess:sqlscriptexecution:schemas:bind-parameters:1.0&quot;,<br>  &quot;title&quot;: &quot;BindParameters&quot;,<br>  &quot;type&quot;: &quot;array&quot;,<br>  &quot;items&quot;: {<br>    &quot;type&quot;: &quot;object&quot;,<br>    &quot;required&quot;: [<br>      &quot;name&quot;,<br>      &quot;type&quot;,<br>      &quot;direction&quot;<br>    ],<br>    &quot;properties&quot;: {<br>      &quot;name&quot;: {<br>        &quot;type&quot;: &quot;string&quot;<br>      },<br>      &quot;type&quot;: {<br>        &quot;$ref&quot;: &quot;#/$defs/oracleNativeType&quot;<br>      },<br>      &quot;direction&quot;: {<br>        &quot;type&quot;: &quot;string&quot;,<br>        &quot;enum&quot;: [<br>          &quot;Input&quot;,<br>          &quot;Output&quot;,<br>          &quot;InputOutput&quot;,<br>          &quot;ReturnValue&quot;<br>        ]<br>      },<br>      &quot;value&quot;: {<br>        &quot;description&quot;: &quot;Value for the bind parameter.&quot;<br>      },<br>      &quot;size&quot;: {<br>        &quot;type&quot;: &quot;integer&quot;,<br>        &quot;minimum&quot;: 1<br>      },<br>      &quot;precision&quot;: {<br>        &quot;type&quot;: &quot;integer&quot;,<br>        &quot;minimum&quot;: 0<br>      },<br>      &quot;scale&quot;: {<br>        &quot;type&quot;: &quot;integer&quot;,<br>        &quot;minimum&quot;: -127<br>      }<br>    },<br>    &quot;allOf&quot;: [<br>      {<br>        &quot;if&quot;: {<br>          &quot;properties&quot;: {<br>            &quot;direction&quot;: {<br>              &quot;enum&quot;: [<br>                &quot;Output&quot;,<br>                &quot;InputOutput&quot;,<br>                &quot;ReturnValue&quot;<br>              ]<br>            },<br>            &quot;type&quot;: {<br>              &quot;enum&quot;: [<br>                &quot;VARCHAR2&quot;,<br>                &quot;NVARCHAR2&quot;,<br>                &quot;CHAR&quot;,<br>                &quot;NCHAR&quot;,<br>                &quot;RAW&quot;,<br>                &quot;LONG RAW&quot;<br>              ]<br>            }<br>          },<br>          &quot;required&quot;: [<br>            &quot;direction&quot;,<br>            &quot;type&quot;<br>          ]<br>        },<br>        &quot;then&quot;: {<br>          &quot;required&quot;: [<br>            &quot;size&quot;<br>          ]<br>        }<br>      },<br>      {<br>        &quot;if&quot;: {<br>          &quot;properties&quot;: {<br>            &quot;direction&quot;: {<br>              &quot;enum&quot;: [<br>                &quot;Input&quot;,<br>                &quot;InputOutput&quot;<br>              ]<br>            }<br>          },<br>          &quot;required&quot;: [<br>            &quot;direction&quot;<br>          ]<br>        },<br>        &quot;then&quot;: {<br>          &quot;required&quot;: [<br>            &quot;value&quot;<br>          ]<br>        }<br>      }<br>    ],<br>    &quot;additionalProperties&quot;: false<br>  },<br>  &quot;$defs&quot;: {<br>    &quot;oracleNativeType&quot;: {<br>      &quot;type&quot;: &quot;string&quot;,<br>      &quot;description&quot;: &quot;Oracle native data type or PL/SQL data type.&quot;,<br>      &quot;enum&quot;: [<br>        &quot;NUMBER&quot;,<br>        &quot;INT&quot;,<br>        &quot;INTEGER&quot;,<br>        &quot;LONG&quot;,<br>        &quot;BINARY_FLOAT&quot;,<br>        &quot;BINARY_DOUBLE&quot;,<br>        &quot;VARCHAR2&quot;,<br>        &quot;NVARCHAR2&quot;,<br>        &quot;CHAR&quot;,<br>        &quot;NCHAR&quot;,<br>        &quot;CLOB&quot;,<br>        &quot;NCLOB&quot;,<br>        &quot;BLOB&quot;,<br>        &quot;BFILE&quot;,<br>        &quot;DATE&quot;,<br>        &quot;TIMESTAMP&quot;,<br>        &quot;TIMESTAMP WITH TIME ZONE&quot;,<br>        &quot;TIMESTAMP WITH LOCAL TIME ZONE&quot;,<br>        &quot;INTERVAL DAY TO SECOND&quot;,<br>        &quot;INTERVAL YEAR TO MONTH&quot;,<br>        &quot;RAW&quot;,<br>        &quot;LONG RAW&quot;,<br>        &quot;BOOLEAN&quot;,<br>        &quot;JSON&quot;,<br>        &quot;VECTOR&quot;,<br>        &quot;REFCURSOR&quot;,<br>        &quot;SYS_REFCURSOR&quot;,<br>        &quot;XMLTYPE&quot;<br>      ]<br>    }<br>  }<br>}</pre><h4><strong>OracleStatementExecutionSettings Class</strong></h4><p>An OracleStatementExecutionSettings object provides options for configuring the execution mode of SQL and PL/SQL statements from a script and output size limits for LONG/LOB columns.</p><p><strong>Class Inheritance</strong></p><p>System.Object</p><ul><li>Oracle.ManagedDataAccess.SQLScriptExecution.OracleStatementExecutionSettings</li></ul><p><strong>Declaration</strong></p><pre>public sealed class OracleStatementExecutionSettings</pre><p><strong>Thread Safety</strong></p><p>Instance members are not guaranteed to be thread-safe.</p><h4>OracleStatementExecutionSettings.<strong>ExecutionOption Property</strong></h4><p>This property specifies ODP.NET SQL script execution exception behavior.</p><p><strong>Declaration</strong></p><pre>public OracleExecutionOption ExecutionOption {get; set;}</pre><p><strong>Property Value</strong></p><p>An OracleExecutionOption enumeration value</p><p><strong>Property Type</strong></p><p>OracleExecutionOption</p><p><strong>Remarks</strong></p><p>This property determines ODP.NET behavior when SQL script execution encounters an error. Either the execution aborts on error, OracleExecutionOption.AbortOnError<em>, </em>(default) or continues to execute the remaining statements after the error is encountered, OracleExecutionOption.ContinueOnError.</p><h4>OracleStatementExecutionSettings.<strong>MaxLOBFetchSize Property</strong></h4><p>This property specifies the maximum amount of LOB data included in the JSON result.</p><p><strong>Declaration</strong></p><pre>public long MaxLOBFetchSize { get; set; }</pre><p><strong>Property Value</strong></p><p>The maximum number of characters for CLOB/NCLOB data or the maximum number of bytes for BLOB/BFILE data to include in the result JSON.</p><p><strong>Property Type</strong></p><p>Long</p><p><strong>Remarks</strong></p><p>This property limits how much CLOB/NCLOB/BLOB/BFILE data is fetched and serialized into the JSON result for each LOB column value. If the LOB value exceeds this limit, only up to MaxLOBFetchSize characters for CLOB/NCLOB or bytes for BLOB/BFILE are included. The default is 65536.</p><h4>OracleStatementExecutionSettings.<strong>MaxLONGFetchSize Property</strong></h4><p>This property specifies the maximum amount of LONG data (LONG/LONG RAW) included in the JSON result.</p><p><strong>Declaration</strong></p><pre>public long MaxLONGFetchSize { get; set; }</pre><p><strong>Property Value</strong></p><p>The maximum number of characters for LONG data or the maximum number of bytes for LONG RAW data to include in the result JSON.</p><p><strong>Property Type</strong></p><p>Long</p><p><strong>Remarks</strong></p><p>This property limits how much LONG/LONG RAW data is fetched and serialized into the JSON result for each LONG column value. If the value exceeds this limit, only up to MaxLONGFetchSize characters for LONG or bytes for LONG RAW are included. The default is 65536.</p><h4>OracleExecutionOption Enumeration</h4><p>This enumeration values specifies the SQL script execution behavior after encountering an exception.</p><p><strong>OracleExecutionOption Enumeration Values</strong></p><p>AbortOnError — When ODP.NET encounters an exception, SQL script execution stops with no further statements executed. This is the default value.</p><p>ContinueOnError — When ODP.NET encounters an exception, SQL script execution continues on.</p><h3><strong>Get Started with ODP.NET SQL Scripting</strong></h3><p>After this whirlwind tour, try ODP.NET SQL scripting for yourself. Download the <a href="https://www.nuget.org/packages/Oracle.ManagedDataAccess.SQLScriptExecution">SQL scripting package from NuGet</a> and share feedback with the Oracle team on <a href="https://github.com/oracle/dotnet-db-samples/issues">GitHub</a> or <strong>in</strong> the <a href="https://forums.oracle.com/ords/apexds/domain/dev-community/category/odp-dot-net">ODP.NET discussion forum</a>.</p><p>#OracleDotNet</p><h3>FAQs</h3><p><strong>How can developers access the feature?</strong><br>It is available as a preview release through the <a href="https://www.nuget.org/packages/Oracle.ManagedDataAccess.SQLScriptExecution">Oracle.ManagedDataAccess.SQLScriptExecution</a> package on NuGet.</p><p><strong>What syntax can a script contain?</strong><br>Scripts may contain SQL and PL/SQL, including / as a PL/SQL block terminator. Other SQL*Plus and SQLcl commands are unsupported.</p><p><strong>What happens when a statement fails?</strong><br>Execution stops by default, but it can be configured to continue. Each error includes its number, message, and location in the statement.</p><p><strong>How are database changes committed?</strong><br>Statements are auto-committed by default. Transactions are supported, although a DDL statement implicitly commits the current transaction and causes the remaining statements to run in auto-commit mode.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=bd4e950ff104" width="1" height="1" alt=""><hr><p><a href="https://medium.com/oracledevs/run-oracle-sql-scripts-in-net-bd4e950ff104">Run Oracle SQL Scripts in .NET</a> was originally published in <a href="https://medium.com/oracledevs">Oracle Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Continuous Data Movement in Oracle Autonomous AI Database with Data Pipelines]]></title>
            <link>https://medium.com/oracledevs/continuous-data-movement-in-oracle-autonomous-database-with-data-pipelines-95b750312a69?source=rss----749dcac244ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/95b750312a69</guid>
            <category><![CDATA[oracle-autonomous-db]]></category>
            <category><![CDATA[oracle-database]]></category>
            <category><![CDATA[oracle-cloud]]></category>
            <category><![CDATA[vector-database]]></category>
            <category><![CDATA[oracle]]></category>
            <dc:creator><![CDATA[vijay balebail]]></dc:creator>
            <pubDate>Fri, 06 Nov 2026 13:01:16 GMT</pubDate>
            <atom:updated>2026-11-06T13:01:15.053Z</atom:updated>
            <content:encoded><![CDATA[<h4>Load new files from object storage into tables, and export tables or query results back to object storage in full or incrementally, all on a database-managed schedule. Includes runnable SQL for both directions.</h4><figure><img alt="Continuous Data Movement in Oracle Autonomous AI Database with Data Pipelines" src="https://cdn-images-1.medium.com/max/1024/1*Xo2NcC8-b67n0lRhZZ6B-Q.png" /></figure><h3>Key takeaways</h3><ul><li><strong>Two directions, one package.</strong> DBMS_CLOUD_PIPELINE provides <strong>load pipelines</strong> (object storage → table) and <strong>export pipelines</strong> (table or query → object storage). The database schedules, tracks and monitors both.</li><li><strong>Loads track files by object name.</strong> Each object name is loaded once. Changing a file’s content under the same name does not reload it, and a copy under a new name is treated as a new file.</li><li><strong>Incremental exports use a time watermark.</strong> With a date or timestamp key_column, each run exports rows whose key falls after the previous run and up to the current run. This suits append-style data. Rows that arrive late with older key values, and updates to rows already exported, are not picked up.</li><li><strong>Freshness follows the schedule, not a fixed delivery time. </strong>The interval attribute sets how often a pipeline runs (in minutes, default 15). Data arrives on the next run after it lands.</li><li><strong>Credentials depend on where the data lives.</strong> In OCI, the database’s resource principal avoids stored keys. Amazon S3, Azure Blob Storage and Google Cloud Storage need their own credentials, and databases on private endpoints may need outbound network routing.</li></ul><figure><img alt="Load pipelines poll object storage for new files and load them into a table with per-file status; the data can then be used with OML, Graph, Vector, Select AI, APEX, and ORDS. Export pipelines write incremental or full table or query results to object storage for consumers such as Spark, partners, SIEM, and archives." src="https://cdn-images-1.medium.com/max/1024/1*VQeoO6jPnPm5dIrdYz3sVQ.png" /><figcaption>Oracle Autonomous Database diagram comparing scheduled load and export pipelines managed by DBMS_CLOUD_PIPELINE.</figcaption></figure><h3>Why this feature matters</h3><p>Machine learning models, property graphs, dashboards and natural-language queries are only as useful as the data underneath them. In most architectures that data arrives as files in object storage: device telemetry, application logs, partner extracts, change-data-capture output, exports from other systems. Getting those files into the database continuously usually takes a chain of services:</p><blockquote><em>a file lands → an event rule fires → a serverless function starts → it calls a loader → custom code records which files were loaded → more code retries the failures</em></blockquote><p>Each link has to be built, secured and monitored. The same is true in the other direction, when fresh results must flow <em>out</em> of the database to a data lake, a partner or an archive.</p><p>Oracle Autonomous AI Database has a built-in alternative that is still little known: <strong>Data Pipelines</strong>, delivered through the DBMS_CLOUD_PIPELINE package. A pipeline is a database object. It continuously <strong>loads new files</strong> from object storage into a table, or continuously <strong>exports new data</strong> from a table or query to object storage. Scheduling, file tracking, parallelism and retries are handled by the database, without event rules, functions or an external scheduler.</p><p>This post explains what Data Pipelines do, why they matter, how to use them for loading and for full and incremental exports, how they behave at the edges, and where they fit.</p><h3>What Data Pipelines are</h3><p>A Data Pipeline is a scheduled, self-tracking data mover that runs inside Autonomous AI Database. There are two types.</p><p><strong>Load pipeline: object storage → table</strong></p><ul><li><strong>Each run moves</strong> the files it has not loaded before.</li><li><strong>Progress is tracked</strong> by object name, in a per-pipeline status table.</li><li><strong>Formats:</strong> CSV, JSON, XML, Avro, ORC, Parquet.</li><li><strong>Failures</strong> are retried on later runs. A file that does not match the table is marked FAILED, and the remaining files keep loading.</li></ul><p><strong>Export pipeline: table or query → object storage</strong></p><ul><li><strong>Each run moves</strong> either the rows whose key_column value falls in the window since the last run (incremental), or the complete result (full snapshot).</li><li><strong>Progress is tracked</strong> by the time of the last run, compared with a date or timestamp key_column.</li><li><strong>Formats:</strong> CSV, JSON, Parquet, XML.</li><li><strong>Failures</strong> are repeated by the next run.</li></ul><p>Both types share the same lifecycle: <strong>create → configure → test → start</strong>, then monitor, stop, restart or drop as needed. Everything is done in SQL and PL/SQL.</p><p>Pipelines read from and write to <strong>OCI Object Storage</strong>, and also <strong>Amazon S3, Azure Blob Storage and Google Cloud Storage</strong>. For OCI Object Storage, the database’s <strong>resource principal</strong> removes the need to store keys. The other providers need a credential of their own (see the FAQs).</p><h3>Why this feature matters</h3><p><strong>The database manages the schedule.</strong> A pipeline runs on the interval you set and checks for new data itself. Nothing has to notify it, so there is no event infrastructure to build or operate.</p><p><strong>File tracking is built in.</strong> A load pipeline records every object it has seen, with its status, row count and timing, in a status table. There is no bookkeeping table to design and no custom logic to stop a file loading twice.</p><p><strong>Retries and error isolation are built in.</strong> Failures are retried automatically on later runs. A malformed file does not stop the pipeline: it is marked FAILED for review while the other files keep loading.</p><p><strong>Incremental exports need one attribute.</strong> Point an export pipeline at a table or query and name a timestamp column. Each run exports only the rows in the window since the last run. Leave the column out and each run exports a complete snapshot instead.</p><p><strong>Monitoring is in the data dictionary.</strong> Pipelines have views for status, history and per-file results, and follow the same security, monitoring and change-control practices as the rest of the database.</p><p><strong>Fewer moving parts.</strong> The data movement runs on the database’s own scheduler and compute, so there are no event rules, functions or external schedulers to deploy. Resource use is controlled with the priority attribute and the run interval.</p><p><strong>It feeds everything else in the converged database.</strong> Once data lands in a table, it is available to Oracle Machine Learning, Property Graph, AI Vector Search, Select AI, Spatial, JSON, APEX and ORDS, without another copy or another pipeline.</p><h3>Load pipelines</h3><h4>How a load pipeline works</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*WmRaAN_HqXC00NgXn6Ok3A.png" /></figure><p>The producer does not need to know the database exists, and the database does not need the producer to tell it anything. Any process that can put a file into the folder participates.</p><h4>Getting files into object storage</h4><p>A load pipeline only cares that a new object appears under its folder. Common ways to get it there:</p><ul><li><strong>Write-only pre-authenticated request (PAR) with an HTTP PUT</strong> from curl, PowerShell, Python, any language or an IoT device. The producer needs only the URL: no OCI account, SDK or IAM policy. Ideal for partners, devices, scripts and external systems.</li><li><strong>OCI Console upload.</strong> Needs an OCI user. Good for ad-hoc files and testing.</li><li><strong>OCI CLI</strong> (oci os object put, bulk-upload). Needs an OCI user and API key. Good for batch jobs, servers and CI/CD.</li><li><strong>OCI SDKs</strong> (Python, Java, Go, .NET, JavaScript and more). Use an API key, or an instance or resource principal. Good for applications in or out of OCI.</li><li><strong>Amazon S3 Compatibility API</strong> (AWS CLI, boto3, rclone, most ETL tools). Needs a customer secret key. Lets existing S3 tooling work unchanged.</li><li><strong>From a database</strong> with DBMS_CLOUD.PUT_OBJECT, DBMS_CLOUD.EXPORT_DATA or an export pipeline. Uses a credential, resource principal or write PAR. Good for database-to-database feeds.</li><li><strong>OCI Connector Hub</strong> from Streaming, Logging or Monitoring. Configuration only. Turns streams and logs into files.</li><li><strong>Kafka or OCI Streaming</strong> with an S3-compatible sink connector. Needs a customer secret key. Lands event streams as files.</li><li><strong>OCI GoldenGate, Data Integration or Data Flow.</strong> Service configuration. Lands CDC and ETL output.</li><li><strong>OCI Data Transfer</strong> (disk or appliance). For very large one-time loads.</li></ul><p>The <strong>write-only PAR</strong> deserves a special mention because it removes the identity problem for external producers. A PAR scoped to one folder with <em>write</em> access lets the holder upload objects with a single HTTP request:</p><pre>curl -X PUT --data-binary @readings.csv &quot;&lt;upload-link&gt;readings_20261024_101500.csv&quot;</pre><pre>Invoke-WebRequest -Method Put -InFile .\readings.csv -Uri &quot;&lt;upload-link&gt;readings_20261024_101500.csv&quot;</pre><p>The holder can add files, but cannot list, read or delete anything, and cannot write outside the folder. Access ends when the PAR expires or is deleted. Treat the URL as a secret, issue one per producer, and renew it before it expires. A PAR can be created in the console (<strong>Bucket → Pre-Authenticated Requests</strong>) or from SQL with the OCI PL/SQL SDK:</p><pre>DECLARE<br>  r dbms_cloud_oci_obs_object_storage_create_preauthenticated_request_response_t;<br>BEGIN<br>  r := dbms_cloud_oci_obs_object_storage.create_preauthenticated_request(<br>         namespace_name  =&gt; &#39;&lt;namespace&gt;&#39;,<br>         bucket_name     =&gt; &#39;&lt;bucket&gt;&#39;,<br>         create_preauthenticated_request_details =&gt;<br>           dbms_cloud_oci_object_storage_create_preauthenticated_request_details_t(<br>             name         =&gt; &#39;meter-readings-upload&#39;,<br>             object_name  =&gt; &#39;meter_readings/&#39;,   -- folder scope<br>             access_type  =&gt; &#39;AnyObjectWrite&#39;,    -- write-only<br>             time_expires =&gt; SYSTIMESTAMP + INTERVAL &#39;90&#39; DAY),<br>         region          =&gt; &#39;&lt;region&gt;&#39;,<br>         credential_name =&gt; &#39;OCI$RESOURCE_PRINCIPAL&#39;);<br>  DBMS_OUTPUT.PUT_LINE(r.response_body.full_path || &#39;meter_readings/&#39;);<br>END;<br>/</pre><h4>Creating a load pipeline</h4><p><strong>1. Allow the database to read the bucket.</strong> For OCI Object Storage, the resource principal avoids stored keys:</p><pre>EXEC DBMS_CLOUD_ADMIN.ENABLE_RESOURCE_PRINCIPAL();   -- as ADMIN, once</pre><p>with an OCI policy such as Allow dynamic-group &lt;adb-dg&gt; to read objects in compartment &lt;compartment&gt;. For other providers, create the matching credential instead (see the FAQs).</p><p><strong>2. Create the target table.</strong></p><pre>CREATE TABLE meter_readings (<br>  meter_id    VARCHAR2(20),<br>  region      VARCHAR2(20),<br>  reading_ts  TIMESTAMP,<br>  kwh         NUMBER(10,3),<br>  voltage     NUMBER(6,2),<br>  status      VARCHAR2(10)<br>);</pre><p><strong>3. Create and configure the pipeline.</strong> Attribute values are passed as strings, including the format JSON. Attributes such as location, table_name and query are fixed once the pipeline has run (RUN_PIPELINE_ONCE or START_PIPELINE); changing them afterwards requires RESET_PIPELINE.</p><pre>BEGIN<br>  DBMS_CLOUD_PIPELINE.CREATE_PIPELINE(<br>    pipeline_name =&gt; &#39;METER_LOAD&#39;,<br>    pipeline_type =&gt; &#39;LOAD&#39;,<br>    description   =&gt; &#39;Load smart-meter CSV files as they arrive&#39;);<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;METER_LOAD&#39;, &#39;credential_name&#39;, &#39;OCI$RESOURCE_PRINCIPAL&#39;);<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;METER_LOAD&#39;, &#39;location&#39;,<br>    &#39;https://objectstorage.&lt;region&gt;.oraclecloud.com/n/&lt;namespace&gt;/b/&lt;bucket&gt;/o/meter_readings/&#39;);<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;METER_LOAD&#39;, &#39;table_name&#39;, &#39;METER_READINGS&#39;);<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;METER_LOAD&#39;, &#39;format&#39;,<br>    &#39;{&quot;type&quot;:&quot;csv&quot;,&quot;skipheaders&quot;:1,&quot;timestampformat&quot;:&quot;YYYY-MM-DD HH24:MI:SS&quot;}&#39;);<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;METER_LOAD&#39;, &#39;interval&#39;, &#39;1&#39;);   -- run every minute (default 15)<br>END;<br>/</pre><p><strong>4. Test, then start.</strong></p><pre>EXEC DBMS_CLOUD_PIPELINE.RUN_PIPELINE_ONCE(&#39;METER_LOAD&#39;);   -- one run, in the current session<br>EXEC DBMS_CLOUD_PIPELINE.START_PIPELINE(&#39;METER_LOAD&#39;);      -- scheduled from now on</pre><p>From this point, each new object name under meter_readings/ is loaded by the next scheduled run.</p><h3>Export pipelines: incremental and full</h3><p>Export pipelines move data the other way: from a table or query to object storage, on a schedule, in parallel, with retries. Output can be CSV, JSON, Parquet or XML. Each run writes one or more files under the location prefix, named &lt;prefix&gt;_&lt;n&gt;_&lt;timestamp&gt;.&lt;ext&gt;. For CSV, JSON and XML, a large result is split into several files of up to 10 MB each by default; the maxfilesize format option raises the limit to as much as 1 GB.</p><h4>Incremental export: a time-based watermark</h4><p>Name a date or timestamp column as the key_column. The pipeline tracks the time of each run. On every run it exports the rows whose key falls <strong>after the previous run and up to the current run</strong>. A run with no rows in that window writes no file.</p><p>The example exports an events table that records when the database received each row:</p><pre>CREATE TABLE voltage_alarms (<br>  meter_id    VARCHAR2(20),<br>  region      VARCHAR2(20),<br>  reading_ts  TIMESTAMP,                            -- when the event happened<br>  voltage     NUMBER(6,2),<br>  load_ts     TIMESTAMP DEFAULT SYSTIMESTAMP        -- when the row arrived: the export key<br>);</pre><pre>BEGIN<br>  DBMS_CLOUD_PIPELINE.CREATE_PIPELINE(<br>    pipeline_name =&gt; &#39;ALARMS_EXPORT&#39;,<br>    pipeline_type =&gt; &#39;EXPORT&#39;,<br>    description   =&gt; &#39;Publish new voltage alarms to the data lake&#39;);</pre><pre>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;ALARMS_EXPORT&#39;, &#39;credential_name&#39;, &#39;OCI$RESOURCE_PRINCIPAL&#39;);<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;ALARMS_EXPORT&#39;, &#39;location&#39;,<br>    &#39;https://objectstorage.&lt;region&gt;.oraclecloud.com/n/&lt;namespace&gt;/b/&lt;bucket&gt;/o/exports/alarms&#39;);<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;ALARMS_EXPORT&#39;, &#39;query&#39;,<br>    &#39;SELECT meter_id, region, reading_ts, voltage, load_ts FROM voltage_alarms&#39;);<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;ALARMS_EXPORT&#39;, &#39;key_column&#39;, &#39;LOAD_TS&#39;);   -- incremental<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;ALARMS_EXPORT&#39;, &#39;format&#39;, &#39;{&quot;type&quot;:&quot;json&quot;}&#39;);<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;ALARMS_EXPORT&#39;, &#39;interval&#39;, &#39;15&#39;);<br>END;<br>/<br>EXEC DBMS_CLOUD_PIPELINE.START_PIPELINE(&#39;ALARMS_EXPORT&#39;);</pre><p>Because the watermark is time-based, choose the key column carefully:</p><ul><li><strong>Prefer an insert timestamp</strong> set by the database (for example load_ts TIMESTAMP DEFAULT SYSTIMESTAMP) over a business event time. An event time can be older than the last run when a row arrives late, and such a row falls outside every future window. An insert timestamp always lands in the next window.</li><li><strong>Rows dated in the future</strong> are exported by the first run after their timestamp is reached, not before.</li><li><strong>Updates are not tracked.</strong> A row that has already been exported is not exported again when its other columns change. For change capture, maintain an “updated at” column and export on it, or use a CDC tool such as GoldenGate.</li><li>RESET_PIPELINE clears the tracked time, so the next run exports from the beginning again.</li></ul><h4>Full (snapshot) export</h4><p>Leave key_column unset and each run exports the <strong>entire</strong> result of the table or query. This suits reference tables, aggregates and &quot;current state&quot; extracts. Adding the run time as a column makes each snapshot self-describing:</p><pre>BEGIN<br>  DBMS_CLOUD_PIPELINE.CREATE_PIPELINE(<br>    pipeline_name =&gt; &#39;REGION_SUMMARY_EXPORT&#39;,<br>    pipeline_type =&gt; &#39;EXPORT&#39;,<br>    description   =&gt; &#39;Hourly snapshot of regional consumption&#39;);<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;REGION_SUMMARY_EXPORT&#39;, &#39;credential_name&#39;, &#39;OCI$RESOURCE_PRINCIPAL&#39;);<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;REGION_SUMMARY_EXPORT&#39;, &#39;location&#39;,<br>    &#39;https://objectstorage.&lt;region&gt;.oraclecloud.com/n/&lt;namespace&gt;/b/&lt;bucket&gt;/o/exports/region_summary&#39;);<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;REGION_SUMMARY_EXPORT&#39;, &#39;query&#39;,<br>    &#39;SELECT region, COUNT(*) readings, ROUND(AVG(voltage),2) avg_voltage, ROUND(SUM(kwh),1) kwh,<br>            SYSTIMESTAMP AS snapshot_ts<br>       FROM meter_readings GROUP BY region&#39;);<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;REGION_SUMMARY_EXPORT&#39;, &#39;format&#39;, &#39;{&quot;type&quot;:&quot;csv&quot;,&quot;header&quot;:true}&#39;);<br>  DBMS_CLOUD_PIPELINE.SET_ATTRIBUTE(&#39;REGION_SUMMARY_EXPORT&#39;, &#39;interval&#39;, &#39;60&#39;);<br>END;<br>/<br>EXEC DBMS_CLOUD_PIPELINE.START_PIPELINE(&#39;REGION_SUMMARY_EXPORT&#39;);</pre><p><strong>Choosing between them</strong></p><ul><li><strong>Incremental export:</strong> set key_column to a date or timestamp column, ideally an insert timestamp. Each run writes only the rows in its time window. Best for events, logs, transactions and other append-only facts. Consumers process every file in order.</li><li><strong>Full export:</strong> no key_column. Each run writes the complete result, possibly as several files. Best for reference data, aggregates and current-state extracts. Consumers read the complete set of files from the latest run (see the FAQ on identifying a run&#39;s files).</li></ul><h4>Oracle-maintained export pipelines</h4><p>Autonomous AI Database ships with two preconfigured export pipelines that ADMIN can start:</p><ul><li><strong>ORA$AUDIT_EXPORT</strong> exports database audit records to object storage as JSON.</li><li><strong>ORA$APEX_ACTIVITY_EXPORT</strong> exports the APEX workspace activity log as JSON.</li></ul><p>Both run every 15 minutes once started and use the resource principal by default. They are a ready-made way to feed a SIEM, a log analytics service or long-term retention without writing any code.</p><h3>Use cases</h3><h4><strong>With load pipelines</strong></h4><ul><li><strong>IoT and telemetry analytics.</strong> Devices upload readings through a write-only PAR, and each file is queryable after the next scheduled run, ready for dashboards and anomaly detection.</li><li><strong>Continuous ML scoring and retraining.</strong> Fresh rows feed OML PREDICTION queries and periodic retraining without a separate ingestion job.</li><li><strong>Graphs that stay current.</strong> Property graphs defined over continuously loaded tables reflect relationships as of the latest run.</li><li><strong>Partner and supplier data drops.</strong> Each partner gets its own upload link, with no accounts and no shared credentials.</li><li><strong>CDC and ETL landing zones.</strong> GoldenGate, Data Integration or other tools write files, and the pipeline loads each new file with retries and per-file tracking.</li><li><strong>Migration from other databases.</strong> Export source tables to CSV, upload them, and run one pipeline per table for fault-tolerant, resumable loading.</li><li><strong>Multicloud ingestion.</strong> Read directly from S3, Azure Blob or GCS without copying files into OCI first.</li></ul><h4><strong>With export pipelines</strong></h4><ul><li><strong>Feeding a data lake or lakehouse</strong> (incremental). New rows land as Parquet or JSON for Spark, Data Flow or other engines.</li><li><strong>Sharing results with partners</strong> (full or incremental). Publish curated extracts to a bucket the partner can read.</li><li><strong>Security and compliance</strong> (Oracle-maintained). ORA$AUDIT_EXPORT sends audit records to object storage for a SIEM or long-term retention.</li><li><strong>Archiving aged data</strong> (incremental). Use a scheduled job to set an archive_ready_ts timestamp when rows pass the retention threshold. Set archive_ready_ts as the export key_column so newly eligible rows enter a later export window. Purge source rows only after the corresponding export run succeeds; Data Pipelines do not delete source rows.</li></ul><h4><strong>With both</strong></h4><ul><li><strong>Database-to-database feeds.</strong> One Autonomous AI Database exports new rows, and another loads them, each on its own schedule.</li></ul><h3>Operating pipelines</h3><p><strong>Monitoring.</strong> Each pipeline has a row in USER_CLOUD_PIPELINES (status, last run, status table name), run history in USER_CLOUD_PIPELINE_HISTORY, and its settings in USER_CLOUD_PIPELINE_ATTRIBUTES. A load pipeline also has a <strong>status table</strong> listing every file with its status, rows loaded and timings, which makes it straightforward to build an operational dashboard.</p><pre>SELECT pipeline_name, status, last_execution, status_table<br>FROM   user_cloud_pipelines;<br><br>SELECT pipeline_name, status, start_date, end_date, error_message<br>FROM   user_cloud_pipeline_history<br>ORDER  BY start_date DESC FETCH FIRST 20 ROWS ONLY;</pre><p><strong>Control.</strong> STOP_PIPELINE and START_PIPELINE pause and resume. RESET_PIPELINE clears the pipeline&#39;s tracking (optionally purging loaded data with purge_data =&gt; TRUE), and also allows fixed attributes such as location to be changed. DROP_PIPELINE removes the pipeline. A reset gives a load pipeline a new status table, so views over the status table are best written against a synonym.</p><p><strong>Design recommendations:</strong></p><ul><li><strong>Choose the interval for the freshness you need, and budget for it.</strong> interval is a scheduling setting in minutes (default 15). Shorter intervals mean data lands sooner, but each run lists the folder and consumes database resources, so a one-minute interval suits near-real-time feeds rather than every pipeline.</li><li><strong>Size </strong><strong>priority deliberately.</strong> HIGH processes files (or export fetches) in parallel based on the database&#39;s ECPU count and finishes sooner, but consumes more database resources than MEDIUM (the default) or LOW (serial).</li><li><strong>Name files uniquely</strong>, for example with a timestamp (readings_YYYYMMDD_HH24MISS.csv). Load pipelines track files by name.</li><li><strong>Prefer fewer, larger files</strong> for high-volume feeds. Each run lists the folder, so batching rows keeps listing fast.</li><li><strong>Plan retention.</strong> Pipelines do not delete anything. Use an Object Storage lifecycle policy or a scheduled job for old files, and partitioning or a scheduled DELETE for old rows.</li><li><strong>Scope and rotate upload links.</strong> Use write-only, folder-scoped PARs with an expiry, one per producer, and issue the replacement before the old one expires.</li></ul><h3>FAQs</h3><p><strong>What happens if a file is replaced, or uploaded again?</strong> Load pipelines track files by object name. Overwriting an already-loaded object with new content under the same name does not reload it. Uploading the same content under a different name loads it again as a new file, which can duplicate its rows. To correct or deliberately reload one file, upload it under a new name and reconcile the rows from the earlier version. Resetting is not a single-file reprocess: it clears tracking, so the next run loads every object in the pipeline location. With purge_data =&gt; TRUE, the reset also truncates the target table before reloading.</p><p><strong>What happens when a file fails to load?</strong> The file is marked FAILED in the pipeline&#39;s status table with the error, and the remaining files continue to load. Failed operations are retried on subsequent scheduled runs. A file that cannot succeed as it is (for example, a column mismatch) keeps failing until the file or the table definition is corrected.</p><p><strong>Does an incremental export pick up rows that arrive late, or rows updated after they were exported?</strong> No. Each run exports rows whose key_column value lies after the previous run&#39;s time and up to the current run&#39;s time. A row inserted later with an older key value falls outside every future window and is not exported. An update to a row that has already been exported is not exported again. Using an insert timestamp set by the database (DEFAULT SYSTIMESTAMP) as the key avoids missing late arrivals. Exporting updates requires an &quot;updated at&quot; column maintained on every change. For full change data capture, including updates and deletes, see <a href="https://medium.com/oracledevs/oracle-26ai-autonomous-database-now-supports-change-data-capture-with-logminer-6e78b1d1bd9e">Change Data Capture with LogMiner on Autonomous AI Database</a>.</p><p><strong>What about rows dated in the future?</strong> They are exported by the first run that starts after their key value is reached.</p><p><strong>What credentials and network access does non-OCI storage need?</strong> OCI Object Storage can use the database’s resource principal with an OCI policy. Amazon S3, Azure Blob Storage and Google Cloud Storage need a credential for that provider: a credential object created with DBMS_CLOUD.CREATE_CREDENTIAL (access keys), or a keyless option such as an AWS Amazon Resource Name (ARN), an Azure service principal or a Google service account. Databases that use a private endpoint may also need an outbound network route to the provider&#39;s storage endpoint, for example through a NAT gateway.</p><p><strong>How does a consumer identify all files that belong to one snapshot run?</strong> A large export run writes several files, and each file name carries its own timestamp rather than a shared run identifier. Two reliable approaches: include the run time in the data (SYSTIMESTAMP AS snapshot_ts in the export query gives every row of a run the same value, even across multiple files), or read every file created since the previous run, using the object creation times. Runs are separated by the pipeline interval, which makes the boundaries easy to find.</p><p><strong>Can the location, table or query be changed after a pipeline has run?</strong> Not directly. These attributes are fixed once the pipeline has run, including through RUN_PIPELINE_ONCE. To change them, stop the pipeline, run RESET_PIPELINE, set the new values, and start it again. A reset also clears the pipeline&#39;s tracking, so plan how existing files or rows should be handled.</p><p><strong>How fresh is the data?</strong> Data is processed on a scheduled run after it lands. The interval attribute sets how often runs are scheduled (default 15 minutes); actual delivery time also depends on when the file arrives in that cycle, run duration, and database workload.</p><h3>See it running</h3><p>A live example shows a load pipeline at work. Smart-meter files of varying size are uploaded through a write-only link every five minutes, and the pipeline runs every minute. In the example, files are typically loaded between a few seconds and about 40 seconds after upload, depending on where the upload falls in the pipeline’s one-minute cycle. The example also includes a self-renewing upload link, 90-day file retention and an automatic reset when the table reaches 4 million rows. Visitors can upload a file themselves and watch it move from the bucket into the table. The page is a single static HTML file in Object Storage, reading ORDS endpoints on the database.</p><p><strong>👉 </strong><a href="https://oraclepartnersas.objectstorage.us-phoenix-1.oci.customer-oci.com/p/Na1xxCk_aECnU1CKawAhUF0Sf-_sqvh0uIMkWxMQAXJpafG4MKjfbV4MMZgD8ycs/n/oraclepartnersas/b/sqlobs-poc/o/pipeline-demo/dashboard/pipeline-live.html"><strong>Live pipeline monitor</strong></a></p><figure><img alt="Left-to-right pipeline status diagram: meters and apps send files through a pre-authenticated, PUT-only link to the meter_readings object storage folder. Pipeline PLDEMO_METER_PL polls every minute and loads files into table PLDEMO_METER_READINGS, which contains 72,655 rows." src="https://cdn-images-1.medium.com/max/1024/1*JYQIQ-IFc8S0ZgEXok4RMw.png" /><figcaption>A meter-data load pipeline view shows files moving from producers through object storage into an Oracle table.</figcaption></figure><h3>Summary</h3><p>Data Pipelines make continuous data movement a database feature. Load pipelines bring each new file in on a schedule, tracked by name and retried on failure. Export pipelines publish the rows in each run’s time window, or full snapshots, on a schedule. With write-only upload links for producers and the converged capabilities of Autonomous AI Database, they keep analytics, machine learning and graph workloads working on current data, with the scheduling, tracking and monitoring managed by the database itself.</p><h3>References</h3><ul><li><a href="https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/pipelines-about.html">About Data Pipelines on Autonomous AI Database</a></li><li><a href="https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/autonomous-pipeline.html">DBMS_CLOUD_PIPELINE package and attributes</a></li><li><a href="https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/pipelines-monitor.html">Monitor and troubleshoot pipelines</a></li><li><a href="https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/pipeline-oracle-maintained.html">Use Oracle-maintained pipelines</a></li><li><a href="https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/sec-access-cloud-resources-configuring-policies-and-roles.html">Configure policies and roles to access resources (resource principal, ARNs, Azure service principal, Google service account)</a></li><li><a href="https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/format-options-json.html">DBMS_CLOUD format options for EXPORT_DATA</a></li><li><a href="https://docs.oracle.com/en/cloud/paas/autonomous-database/serverless/adbsb/dbms-cloud-subprograms.html">DBMS_CLOUD subprograms and REST APIs</a></li><li><a href="https://docs.oracle.com/en-us/iaas/Content/Object/Tasks/usingpreauthenticatedrequests.htm">Using pre-authenticated requests (OCI Object Storage)</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=95b750312a69" width="1" height="1" alt=""><hr><p><a href="https://medium.com/oracledevs/continuous-data-movement-in-oracle-autonomous-database-with-data-pipelines-95b750312a69">Continuous Data Movement in Oracle Autonomous AI Database with Data Pipelines</a> was originally published in <a href="https://medium.com/oracledevs">Oracle Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building a Canary Release Pipeline with OCI DevOps (Part I)]]></title>
            <link>https://medium.com/oracledevs/building-a-canary-release-pipeline-with-oci-devops-part-i-cfa6253c3705?source=rss----749dcac244ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/cfa6253c3705</guid>
            <category><![CDATA[oracle-kubernetes-engine]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[oraclecloudinfrastructure]]></category>
            <category><![CDATA[oracle-cloud]]></category>
            <dc:creator><![CDATA[Kailasj]]></dc:creator>
            <pubDate>Wed, 04 Nov 2026 14:45:41 GMT</pubDate>
            <atom:updated>2026-11-04T14:45:40.455Z</atom:updated>
            <content:encoded><![CDATA[<h4>Part I: Build a private Python API foundation on OCI</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*waKxpydtdo5yEl23aLWLtA.png" /></figure><h3>Key Takeaways</h3><ul><li>Build a private Python API foundation using FastAPI, OCI Kubernetes Engine, Terraform, and OCI DevOps.</li><li>Track the serving release through version, commit, and environment metadata, preparing for canary verification.</li><li>Package repeatable containers with pinned dependencies, unique release identifiers, and restricted runtime permissions.</li><li>Use OCI Bastion for temporary administrative access while keeping the cluster and application private.</li><li>Connect source, images, deployment manifests, and delivery permissions ahead of the canary rollout in Part II.</li></ul><p>A successful container build is only one part of releasing an application. We also need somewhere to run it, a controlled way to reach it, and a way to identify the version answering our requests.</p><p>We built a small Python API and developer portal on Oracle Cloud Infrastructure (OCI), with a private Oracle Kubernetes Engine (OKE) cluster and OCI DevOps delivery services. This post explains the foundation. <a href="https://medium.com/02-oci-devops-canary-release.md">Part 2</a> follows a commit through testing, a 10% canary rollout, approval, and production promotion.</p><p><strong>Naming note:</strong> Resource names such as demo-compartment, demo-production, and demo-canary are illustrative pseudonyms. Actual compartment, region, tenancy, repository, cluster, identity, network-address, and release identifiers are omitted.</p><h3>Choose a small, private deployment</h3><p>We placed the resources in a dedicated compartment, represented here as demo-compartment, within one OCI region. A compartment groups resources and provides a scope for access policies. Keeping regional resources together simplifies the demo&#39;s connectivity.</p><p>Our OKE Basic cluster used one <strong>VM.Standard.E4.Flex worker with 1 OCPU and 4 GB RAM</strong>, plus a 50 GB boot volume. This was sufficient for our stateless application’s production, candidate, and router pods.</p><p>That is a demonstration footprint, not a general sizing rule. The workloads share one worker, so a worker failure affects all of them. Namespace separation does not provide host redundancy.</p><p>We kept the Kubernetes API, worker, and application private. OCI Bastion provided temporary administrative access from an allowlisted client address.</p><h3>Make the application identify itself</h3><p>We used <strong>FastAPI</strong> to handle HTTP requests and <strong>Uvicorn</strong> to run the application server. The same application serves the portal page and API endpoints, keeping the demo to one application image.</p><p>The portal describes the delivery path and displays the serving revision. Pipeline execution and approval records remain in OCI DevOps.</p><p>EndpointWhat it tells us/The portal is reachable/api/helloThe example API contract behaves correctly/healthzThe application process responds/readyzThe application is ready for traffic/api/releaseWhich version, commit, and environment answered/docsThe API contract can be explored interactively</p><p>Liveness and readiness serve different purposes. Repeated liveness failures can cause Kubernetes to restart a container. Readiness failures exclude an unready pod from normal Service traffic.</p><p>Our readiness signal follows application startup and shutdown. There is no database or other application dependency to check. If we added one, we would need to reconsider the readiness contract.</p><p>Release metadata becomes especially useful when two versions run simultaneously. Both may return HTTP 200, but only one is the candidate. The release endpoint reports version, commit, and environment, allowing us to attribute each response without exposing credentials.</p><p>The image records the source and version; deployment configuration supplies the environment. This creates a traceable connection from source to the request we observe.</p><h3>Package a repeatable container</h3><p>A container image packages the application, runtime, and dependencies. A container is a running instance of that image. Kubernetes runs containers inside pods.</p><p>Our Dockerfile starts with a digest-pinned Python 3.12 base image and installs dependencies with package-hash verification. It copies in the application, selects a non-root user, and starts Uvicorn on port 8080.</p><p>Pinning versions and hashes controls what enters the build. It also means updates must be deliberate: a pinned dependency does not update itself when a fix becomes available.</p><p>Each build creates a version using this structure:</p><pre>&lt;source-commit-prefix&gt;-&lt;generated-build-suffix&gt;</pre><p>The version connects the image tag, manifest artifact, and application metadata. Separate builds receive distinct identifiers, and deployment avoids latest.</p><p>We verified execution with a read-only root filesystem, dropped Linux capabilities, and no privilege escalation. These restrictions reduce what the process can change and are also reflected in the OKE workload configuration.</p><p>A tag names an image reference; a digest identifies image content. We later compared the running production digest with OCIR metadata. This provided evidence beyond the application’s own version string.</p><p>The container repository did not accept enforced immutability in this exercise. We used unique tags and restricted writers; immutable versions were enforced for the separate manifest repository.</p><h3>Build the private foundation with Terraform</h3><p>Terraform describes both the infrastructure and delivery services. A plan previews proposed changes, an apply carries them out, and later plans reveal differences between configuration and deployed resources.</p><p>We separated the network and OKE configuration from the DevOps configuration so their responsibilities remained clear.</p><p>The private network used an isolated Virtual Cloud Network (VCN), with subnets for the Kubernetes API, workers, and access resources. Outbound connectivity came from two gateways:</p><p>ComponentRoleNAT gatewayAllows private resources to initiate internet connections without public worker addressesService gatewayProvides access to supported OCI services through the OCI service network</p><p>Network rules constrain those paths. Outbound connectivity supports image retrieval and service communication without publishing the application for inbound internet traffic.</p><p>OKE manages the Kubernetes control plane, while the worker supplies CPU and memory for pods. We organized workloads into namespaces represented here as demo-production, demo-canary, and demo-router. These names will also appear in the release discussion.</p><h3>Reach OKE through OCI Bastion</h3><p>The private Kubernetes API cannot be reached directly from a laptop across the public internet. We created a time-limited OCI Bastion session restricted to one allowlisted client address.</p><p>An SSH tunnel forwarded a laptop port to the private Kubernetes API. Through that connection, kubectl port-forward forwarded a second local port to the router Service.</p><pre>Laptop browser<br>      ↓ local forwarded port<br>kubectl port-forward<br>      ↓ API connection through Bastion<br>Private OKE router Service<br>      ↓<br>Application Service and pod</pre><p>The browser used localhost, but the application ran in OCI. A dedicated kubeconfig kept the connection separate from existing Kubernetes contexts, and API certificate verification remained enabled.</p><p>Access requires an active Bastion session and forwarding processes. This is a temporary administrative access path rather than a permanent application URL.</p><h3>Connect the OCI delivery services</h3><p>The delivery foundation gives each artifact and operation a home:</p><p>OCI resourceResponsibilityDevOps project and Code RepositoryGroup delivery resources and store sourceManaged build pipelineTest source and build release artifactsPrivate OCI Container Registry (OCIR)Store application imagesGeneric Artifact repositoryStore versioned deployment manifestsOKE deployment environmentIdentify the target cluster and connectivityBaseline and canary pipelinesEstablish production and release later candidates</p><p>The image contains the application; the manifest tells Kubernetes how to run it. It describes the image reference, replicas, probes, security settings, and routing resources. Delivering both lets promotion consume the artifact evaluated during the canary.</p><p>OCI dynamic groups identify authorized pipeline resources, and IAM policies grant their required access. Image pulling uses a separate auth-token-only identity with read access restricted to the application repository. Kubernetes pull secrets provide that credential in both application namespaces, outside the image, source control, and Terraform state.</p><p>The foundation now connects identifiable application code, a versioned image, private compute, controlled access, and delivery permissions. The next step is to prove that a source change can move through all of them.</p><p><strong>Continue to </strong><a href="https://medium.com/oracledevs/building-a-canary-release-pipeline-with-oci-devops-part-ii-4e707b9b619e"><strong>part 2</strong></a><strong>: Release through an OCI DevOps canary</strong></p><h3>FAQs</h3><h3>What does Part I cover?</h3><p>Part I establishes the Python application, container image, private OKE infrastructure, administrative access, and OCI delivery services. Part II follows a source change through canary deployment, approval, and production promotion.</p><h3>Is this architecture production-ready?</h3><p>It is a demonstration foundation. All workloads share one worker, so a worker failure affects the application and router. Production use requires additional resilience, capacity planning, and operational validation.</p><h3>How do I access the private application?</h3><p>A time-limited OCI Bastion session provides an SSH tunnel to the private Kubernetes API. From there, kubectl port-forward makes the router Service accessible through localhost while the application remains in OCI.</p><h3>Why does the application expose release metadata?</h3><p>Successful HTTP responses do not identify which version served them. The release endpoint reports the version, commit, and environment so requests can be attributed to the baseline or candidate during a canary rollout.</p><h3>How are application images and deployment manifests different?</h3><p>The image contains the application, runtime, and dependencies. The manifest defines how Kubernetes runs it, including replicas, probes, security settings, and routing. Versioning both supports promotion of the release evaluated during the canary.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=cfa6253c3705" width="1" height="1" alt=""><hr><p><a href="https://medium.com/oracledevs/building-a-canary-release-pipeline-with-oci-devops-part-i-cfa6253c3705">Building a Canary Release Pipeline with OCI DevOps (Part I)</a> was originally published in <a href="https://medium.com/oracledevs">Oracle Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building a Canary Release Pipeline with OCI DevOps (Part II)]]></title>
            <link>https://medium.com/oracledevs/building-a-canary-release-pipeline-with-oci-devops-part-ii-4e707b9b619e?source=rss----749dcac244ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/4e707b9b619e</guid>
            <category><![CDATA[oracle-container-registry]]></category>
            <category><![CDATA[devops]]></category>
            <category><![CDATA[oracle-kubernetes-engine]]></category>
            <category><![CDATA[oraclecloudinfrastructure]]></category>
            <category><![CDATA[oracle-cloud]]></category>
            <dc:creator><![CDATA[Kailasj]]></dc:creator>
            <pubDate>Tue, 03 Nov 2026 22:28:23 GMT</pubDate>
            <atom:updated>2026-11-04T14:50:12.311Z</atom:updated>
            <content:encoded><![CDATA[<h4>Part II: Release a Python API through an OCI DevOps canary</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*5NeegX2XRUsTIGk7gwc59Q.png" /></figure><h3>Key Takeaways</h3><ul><li>Build a commit-triggered release pipeline with OCI DevOps: test, package, publish artifacts, and deploy a canary to private OKE.</li><li>Verify which release actually serves requests; healthy pods and a configured traffic weight do not prove the traffic split works.</li><li>Evaluate the candidate with 10% of traffic, then approve promotion using the same release artifacts without rebuilding.</li><li>Retain known-good artifacts for recovery. Deployment-stage rollback does not detect every application regression.</li></ul><p>A delivery pipeline becomes useful when we can follow a source change into a running application, check its behavior, and control its promotion. A green build is a starting point; the serving revision and traffic path provide the next evidence.</p><p>In <a href="https://medium.com/@kailasj/cfa6253c3705">part 1</a>, we built a Python API, packaged it, and established private OKE infrastructure with OCI delivery services. Here we follow the release: baseline deployment, commit-triggered build, 10% canary, approval, and production verification.</p><p><strong>Naming note:</strong> Namespace names such as demo-production and demo-canary are pseudonyms. Actual environment names, locations, network addresses, and release identifiers are omitted. Results describe the exercise verified on October 4, 2026, rather than current cloud health.</p><h3>Establish production before introducing a candidate</h3><p>A canary rollout needs a working production version to receive most requests. We first ran the managed build with automatic canary delivery disabled, then deployed its artifacts through the separate baseline pipeline into demo-production.</p><p>Several Kubernetes objects work together:</p><p>ObjectResponsibilityDeploymentMaintains application pods and coordinates updatesPodRuns the application containerServiceGives ready pods a stable destinationIngressDescribes HTTP routing rulesRouterImplements those rules and forwards requests.</p><p>An Ingress needs a controller to implement its rules. A Service remains stable as individual pods are replaced and their addresses change.</p><p>The private demo runs one application replica per application namespace. Startup, liveness, and readiness probes check the application, and the Service selects ready pods.</p><p>Through the private router, we verified health, readiness, the example API response, and the expected release identity. This established a known working baseline before we enabled the complete commit-triggered path.</p><h3>Turn a source push into a release</h3><p>We enabled the trigger for pushes to main in OCI Code Repository and the build stage that starts canary delivery.</p><p>The resulting path connected source, artifacts, and deployment:</p><pre>Push to main<br>↓<br>Test the API<br>↓<br>Build and smoke-test the container<br>↓<br>Publish image and manifest<br>↓<br>Deploy candidate to OKE<br>↓<br>Shift 10 percent of traffic<br>↓<br>Wait for approval<br>↓<br>Promote the tested release</pre><p>The build specification directed the runner to:</p><ol><li>Install hash-verified dependencies and run API tests.</li><li>Create a unique release version and build the amd64 image.</li><li>Start the image with the intended runtime restrictions.</li><li>Run smoke checks and verify the reported version.</li><li>Render a manifest containing the release’s image reference and metadata.</li></ol><p>OCI pipeline stages then delivered the image to OCIR, delivered the manifest to Artifact Registry, and triggered the deployment pipeline with that version. The verified source push started this process automatically, and every build stage succeeded.</p><p>This connects continuous integration with continuous delivery. Tests and packaging happen automatically, while production promotion retains an approval point.</p><h3>Use native OCI DevOps canary stages</h3><p>The deployment pipeline has four stages:</p><p>StageBehaviorCandidate deploymentApplies the candidate into demo-canaryTraffic shiftAssigns 10% of normal traffic to itApprovalWaits for one recorded approvalProduction deploymentApplies the evaluated release into demo-production</p><p>The established version continues serving most requests while the candidate is checked. The configured traffic-shift batch delay is not an automated error-rate or latency gate; observation remains necessary.</p><p>OCI manages NGINX-style canary annotations. Our private router used Traefik v3.7.13’s NGINX compatibility provider to interpret them. This integration needed verification with live requests.</p><h3>Diagnose the traffic path before approving</h3><p>Our first sample revealed a mismatch: the candidate was healthy and its ingress showed a 10% weight, yet every sampled request reached the baseline.</p><p>The annotation described the desired behavior. The release endpoint revealed the behavior actually occurring.</p><p>Router logs and its implementation showed the cause: this provider matches primary and canary ingress rules within the same namespace. Our initial cross-namespace arrangement did not meet that requirement.</p><p>We changed the routing layout while preserving separate application namespaces:</p><pre>Router watches demo-canary<br>    │<br>    ├── Primary ingress<br>    │       ↓<br>    │   ExternalName Service alias<br>    │       ↓<br>    │   Production Service in demo-production<br>    │<br>    └── Candidate ingress<br>            ↓<br>        Candidate Service in demo-canary</pre><p>An ExternalName Service supplies a DNS alias. Here it points to the production Service’s internal Kubernetes name. It does not expose a public endpoint or move production pods.</p><p>The matching ingress rules now reside together in demo-canary, while OCI manages the candidate ingress and traffic weight. The router&#39;s workload permissions are scoped to that watched namespace.</p><p>This is why ingress compatibility must be tested beyond checking that deployment resources exist. Controller matching rules can determine whether the configured split takes effect.</p><h3>Evaluate the candidate using release identity</h3><p>After correcting the route, we checked candidate health, readiness, API behavior, release identity, and updated portal content. We then sampled the router’s release endpoint:</p><p>Response before approvalCountCandidate20Baseline180HTTP errors0Total requests200</p><p>The observed split matched the configured 10% share. Future samples need not divide exactly the same way.</p><p>We also verified that the redirect-to-canary: always header selected the candidate, allowing deliberate checks through the router without waiting for normal traffic selection.</p><p>Counting successful responses alone would have missed the original problem. Identifying which revision answered demonstrated that the candidate was actually receiving traffic.</p><h3>Approve and promote without rebuilding</h3><p>After the checks passed, we recorded approval in OCI DevOps with the verification reason. OCI reset normal candidate traffic to zero during the transition and continued to production deployment.</p><p>The production stage consumed the delivered release manifest. No new application build occurred between canary evaluation and promotion.</p><p>We verified the resulting production workload through the private router:</p><p>CheckRecorded resultAll four deployment stagesSucceededProduction podReady, with zero restarts at verificationHealth and readinessPassedPost-promotion sample50 of 50 requests served the new production releaseHTTP errors in that sample0Running image digestMatched OCIR metadata</p><p>The digest comparison supplemented the application-reported version with a check of the running image content.</p><h3>Keep recovery and future delivery ready</h3><p>The production stage has automatic rollback configured for deployment-stage failure. Retained baseline artifacts also allow a known-good release to be redeployed without rebuilding.</p><p>These controls have limits. A ready application can contain a business-logic defect. This demo has no automated error-rate or latency analysis gate, and stage rollback cannot detect every regression after a successful deployment. Its traffic samples demonstrate functionality rather than production capacity. The single worker also provides no host redundancy.</p><p>For a later regression, the recovery path is to remove candidate traffic and redeploy a retained known-good release. Deployment runs should not overlap.</p><p>We saved the routing fix and verification record, briefly pausing the trigger for that audit commit. After restoring it, we checked that the trigger was active and Terraform reported no infrastructure drift.</p><p>The verified recurring workflow is <strong>edit → commit → push → OCI build and artifact delivery → private OKE canary → review → approval → production verification</strong>. The key operational habit is to check the revision actually serving requests before deciding a release is ready to promote.</p><h3>FAQs</h3><h3>What do I need before following Part II?</h3><p>Part I establishes the Python API, container packaging, private OKE infrastructure, and OCI delivery services. A working production baseline is required before introducing the canary.</p><h3>Does a 10% canary weight guarantee exactly 10% of requests?</h3><p>No. Individual samples can vary. Check the release identity returned by each request to confirm that both versions receive traffic and the observed split is consistent with the configured weight.</p><h3>Why did the ingress routing need adjustment?</h3><p>The Traefik NGINX compatibility provider used in this exercise matched primary and canary ingress rules within the same namespace. An ExternalName Service alias allowed those rules to sit together while the application workloads remained in separate namespaces.</p><h3>Is the application rebuilt after approval?</h3><p>No. The production stage uses the release manifest and image already evaluated during the canary phase.</p><h3>What happens if a release fails?</h3><p>Automatic rollback is configured for production deployment-stage failure. For a regression discovered after successful deployment, the recovery path is to remove candidate traffic and redeploy retained known-good artifacts. This demo does not include automated error-rate or latency gates.</p><p><strong>Read </strong><a href="https://medium.com/oracledevs/building-a-canary-release-pipeline-with-oci-devops-part-i-cfa6253c3705"><strong>part 1</strong></a><strong>: Build the private OCI foundation for the application and infrastructure design.</strong></p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4e707b9b619e" width="1" height="1" alt=""><hr><p><a href="https://medium.com/oracledevs/building-a-canary-release-pipeline-with-oci-devops-part-ii-4e707b9b619e">Building a Canary Release Pipeline with OCI DevOps (Part II)</a> was originally published in <a href="https://medium.com/oracledevs">Oracle Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Event-Driven Configuration Refresh for Oracle AI Database 26ai with python-oracledb, Amazon S3, and…]]></title>
            <link>https://medium.com/oracledevs/event-driven-configuration-refresh-for-oracle-ai-database-26ai-with-python-oracledb-amazon-s3-and-d4d3609f9c01?source=rss----749dcac244ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/d4d3609f9c01</guid>
            <category><![CDATA[oracle-database]]></category>
            <category><![CDATA[aws-ccp-notifications]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[cloud-computing]]></category>
            <category><![CDATA[aws]]></category>
            <dc:creator><![CDATA[Samia El Amarti]]></dc:creator>
            <pubDate>Thu, 29 Oct 2026 11:30:32 GMT</pubDate>
            <atom:updated>2026-11-12T14:47:57.908Z</atom:updated>
            <content:encoded><![CDATA[<h3>Event-Driven Configuration Refresh for Oracle AI Database 26ai with python-oracledb, Amazon S3, and AWS Secrets Manager</h3><h4>With python-oracledb 26.1.0, your Python applications connecting to Oracle Database can pick up connection configuration changes in Amazon S3 or AWS Secrets Manager shortly after they are made, without shortening cache lifetimes or waiting for the normal refresh cycle.</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*mRKCb3D2OcmSSAEMPg7taA.png" /></figure><p>With <a href="https://medium.com/@sharad-chandran/python-oracledb-26-1-0-fdeecebc81be">python-oracledb 26.1.0</a>, your Python applications can pick up connection configuration changes in Amazon S3 or AWS Secrets Manager shortly after they are made, without shortening cache TTLs or waiting for the normal refresh cycle.</p><h3>Key Takeaways</h3><ul><li><strong><em>Configuration changes reach your application without waiting for the cache to expire. </em></strong><a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#configuration-cache-lifetime">python-oracledb caches centralized configuration for 24 hours by default</a>. Starting with python-oracledb 26.1.0, a change in Amazon Simple Storage Service (Amazon S3) or AWS Secrets Manager expires the cached copy when it happens.</li><li><strong><em>One connection string option turns it on. </em></strong>Add the URL-encoded Amazon Simple Queue Service (Amazon SQS) queue URL as the queueurl parameter of a config-awss3:// or config-awssecretsmanager:// Data Source Name (DSN). Your application code doesn’t change.</li><li><strong><em>Updates are safe, and deletions are strict</em></strong>. After an update, the new configuration is retrieved on the next use, and the grace period still covers a brief AWS outage. After a deletion, the cached configuration is removed, so the next connection fails instead of using stale settings.</li><li><strong><em>Existing connections are left alone. </em></strong>Notifications only update the cache. New connections and pools use the new configuration, and open ones keep working as they are.</li><li><strong><em>Your secrets stay where they are. </em></strong>Notifications only name the changed resource. The configuration is still retrieved directly from Amazon S3 or AWS Secrets Manager with your existing AWS credentials.</li></ul><p>Starting with <a href="https://python-oracledb.readthedocs.io/">python-oracledb</a> 26.1.0,<a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#configuration-change-notification"> configuration change notifications</a> take the python-oracledb AWS configuration provider plugin a step further. <a href="https://medium.com/oracledevs/oracle-ai-database-26ai-connectivity-with-python-oracledb-using-amazon-s3-and-aws-secrets-manager-8c6a3f8874d1">In our earlier post,</a> you saw how python-oracledb can read <a href="https://www.oracle.com/database/free/">Oracle AI Database 26ai</a> connection configuration from <a href="https://docs.aws.amazon.com/s3/">Amazon S3</a> or <a href="https://docs.aws.amazon.com/secretsmanager/">AWS Secrets Manager</a> through a single DSN. Now, when that configuration changes in AWS, your application finds out without waiting for its cache to expire.</p><p>This post walks you through why this matters, how the feature works, and how to set it up for Amazon S3 and AWS Secrets Manager.</p><h3>Why Wait for the Cache to Expire?</h3><p>To avoid calling AWS on every connection, python-oracledb caches the configuration it reads. The <a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#configuration-cache-lifetime">config_time_to_live</a> setting controls how long, and the default is 86,400 seconds, or 24 hours.</p><p>Picture your team moving a service to a new database endpoint. You upload the new config.json to Amazon S3, and the source of truth is updated in seconds. Your running applications keep using the old cached endpoint until their Time-To-Live (TTL) runs out, up to a day later.</p><p>You have two workarounds, and both have a cost. You can restart every application that uses the configuration, which turns a configuration change into a deployment task. Or you can shorten the TTL, which means more requests to AWS, while your applications still refresh on a timer rather than when something actually changed.</p><p>Configuration should be refreshed when it changes, not when a timer runs out. That’s what configuration change notifications do.</p><h3>How Configuration Change Notifications Work</h3><p>Configuration change notifications let the AWS configuration provider plugin tell python-oracledb that a cached configuration is out of date. They rely on a one-time setup in your AWS account: an <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/welcome.html">Amazon Simple Queue Service (Amazon SQS)</a> queue and an <a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-what-is.html">Amazon EventBridge</a> rule that routes change events to it. For Amazon S3, you also enable EventBridge notifications on the bucket. For AWS Secrets Manager, <a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-user-guide.html">AWS CloudTrail</a> delivers the secret’s change events. You then enable notifications for each connection string with one option: queueurl, <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-queue-message-identifiers.html">the URL of your queue</a>.</p><p>Once this is set up, one change flows like this:</p><ol><li>You update or delete the configuration in Amazon S3 or AWS Secrets Manager.</li><li>Amazon S3, or AWS Secrets Manager through CloudTrail, reports the change as an event.</li><li>Your EventBridge rule matches the event and sends it to your SQS queue.</li><li>The plugin, listening on that queue, finds the connection strings that use the changed configuration and tells python-oracledb to expire their cached copy, or to remove it if the configuration was deleted.</li><li>Once the plugin has processed the notification, the next oracledb.connect() or oracledb.create_pool() call with that connection string retrieves the new configuration.</li></ol><p>The plugin never downloads configuration from a notification, and notifications about other resources leave your cached entry untouched. The AWS resources remain the source of truth: the notification is only a signal that says “look again”.</p><p>The plugin uses SQS long polling to receive notifications. It runs a background thread that keeps asking the queue for new messages. There is no fixed interval: each request waits up to 5 seconds, and returns immediately when a message arrives, so a change is picked up within seconds.</p><figure><img alt="Diagram: changes in Amazon S3 or AWS Secrets Manager reach an Amazon SQS queue through Amazon EventBridge. The python-oracledb plugin then expires or removes the cached configuration." src="https://cdn-images-1.medium.com/max/1024/1*OC3pIEWPymf2SUnBc0A3wg.png" /><figcaption>How a change in Amazon S3 or AWS Secrets Manager reaches the python-oracledb configuration cache.</figcaption></figure><p>The figure shows the full path of a change. The queueurl option in the connection string tells the plugin which queue to listen on. Whatever the event, the plugin only updates the cache: it expires the entry for an update and removes it for a deletion. For an update, the new configuration is retrieved on the first connection after the notification has been processed.</p><h3><strong>Before You Start</strong></h3><p>You need:</p><ul><li><a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/installation.html#install-modules-for-the-amazon-web-services-centralized-configuration-providers">python-oracledb 26.1.0 or later, installed with the AWS configuration provider:</a> python -m pip install oracledb[aws_config] (<a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/installation.html#install-modules-for-the-amazon-web-services-centralized-configuration-providers">installation guide</a>).</li><li>A standard Amazon SQS queue whose policy allows Amazon EventBridge to send messages to it (<a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-use-resource-based.html#eb-sqs-permissions">Amazon SQS permissions for EventBridge</a>).</li><li>Permission for the application’s AWS identity to call sqs:ReceiveMessage and sqs:DeleteMessage on that queue, in addition to the read access it already has to the configuration. You grant these by adding them to an IAM policy attached to the user or role your application runs as. See <a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-basic-examples-of-iam-policies.html">Amazon SQS identity-based policy examples</a>.</li></ul><h4>Set Up Notifications for Amazon S3</h4><ol><li><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-event-notifications-eventbridge.html">Enable Amazon EventBridge notifications</a> on the bucket.</li><li>Create an EventBridge rule that matches <a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ev-events.html">Object Created and </a><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/ev-events.html">Object Deleted</a> events for the bucket and targets your SQS queue.</li><li><a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#aws-simple-storage-service-s3-centralized-configuration-provider">Add the URL-encoded queue URL as the </a><a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#aws-simple-storage-service-s3-centralized-configuration-provider">queueurl parameter of the connection string.</a></li></ol><pre>import oracledb<br>import oracledb.plugins.aws_config_provider<br>dsn = (<br>    &quot;config-awss3://my-bucket/config.json&quot;<br>    &quot;?aws_region=us-east-1&quot;<br>    &quot;&amp;queueurl=https%3A%2F%2Fsqs.us-east-1.amazonaws.com%2F123456789012%2Fconfig-updates&quot;<br>)<br>with oracledb.connect(dsn=dsn) as connection:<br>    with connection.cursor() as cursor:<br>        cursor.execute(&quot;select user from dual&quot;)<br>        print(cursor.fetchone())</pre><p>The only change from a regular AWS configuration DSN is the queueurl parameter, the URL of your SQS queue. Characters such as : and / have a special meaning in a URL, so inside a query parameter they must be <a href="https://developer.mozilla.org/en-US/docs/Glossary/Percent-encoding">percent-encoded</a>: %3A stands for : and %2F stands for /. In Python, urllib.parse.quote(queue_url, safe=&quot;&quot;) produces the encoded value for you. When someone uploads a new version of config.json, the first oracledb.connect() call with this DSN after the notification has been processed uses the new configuration.</p><h4>Set Up Notifications for AWS Secrets Manager</h4><p>For a configuration stored in a secret, the change events come through AWS CloudTrail:</p><ol><li>Make sure a <a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-create-and-update-a-trail.html">CloudTrail trail</a> records Secrets Manager management events.</li><li>Create an EventBridge rule that matches Secrets Manager events from CloudTrail and targets your SQS queue. Include both detail-type values, AWS API Call via CloudTrail and AWS Service Event via CloudTrail. AWS’s guide to <a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/monitoring-eventbridge.html">matching Secrets Manager events with EventBridge</a> shows example rule patterns.</li><li>Add the URL-encoded queue URL as queueurl:</li></ol><pre>dsn = (<br>    &quot;config-awssecretsmanager://app-config?aws_region=us-east-1&quot;<br>    &quot;&amp;queueurl=https%3A%2F%2Fsqs.us-east-1.amazonaws.com%2F123456789012%2Fconfig-updates&quot;<br>)</pre><p>The plugin reacts to events that change a secret’s value or lifecycle, such as <a href="https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_PutSecretValue.html">PutSecretValue</a> and <a href="https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_UpdateSecret.html">UpdateSecret</a>, by expiring the cached configuration. A <a href="https://docs.aws.amazon.com/secretsmanager/latest/apireference/API_DeleteSecret.html">DeleteSecret</a> event removes it instead. <a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#aws-secrets-manager-centralized-configuration-provider">The python-oracledb documentation lists every supported event.</a></p><h3>What Happens When the Configuration Changes</h3><p>A notification changes only the cache. It never opens a connection, retrieves configuration, or touches a connection that is already open. The table shows the two cases.</p><figure><img alt="Table: when the configuration is updated, the cached entry is expired and the next use retrieves the new configuration, with the grace period as a fallback. When it is deleted, the cached entry is removed and the next use raises an error." src="https://cdn-images-1.medium.com/max/1024/1*2lYy8HHz9jnxEg1fnjkkzw.png" /><figcaption>What happens to the cached configuration when it is updated or deleted.</figcaption></figure><p>The difference is deliberate. An update should not cause an outage, so the grace period still covers a brief AWS problem. A deletion means the configuration should no longer be used, so stale settings are not kept.</p><p>The grace period starts when the notification is processed<strong>.</strong> From then on, the expired configuration is used only if AWS can’t supply the new one, for up to 30 minutes by default (config_time_to_live_grace_period). After that, the next connection attempt raises an error.</p><p>Existing connections and pools keep the settings they were created with. To pick up a new connect descriptor or pool size, the application creates a new pool or new connections.</p><p>TTL-based refresh still applies. Notifications make a change visible sooner; they do not replace config_time_to_live as a safety net if a notification is missed.</p><h3>Things to Know Before Production</h3><ul><li><strong>Give each application process its own queue.</strong> Amazon SQS delivers each message to only one reader, and the plugin deletes messages after reading them. If two processes share a queue, each one misses some of the other’s notifications. One process can use several queues, and one queue can serve many connection strings in that process.</li><li><strong>Route only relevant events to the queue.</strong> Each queue should receive notifications only for the connection strings that use it. The plugin ignores unrelated events, but they still cost a delivery.</li><li><strong>Keep caching on.</strong> If config_time_to_live is set to 0, nothing is cached, so the plugin doesn’t listen for notifications even when queueurl is set.</li><li><strong>Watch for warnings.</strong> If the plugin can’t process a message, it issues a Python RuntimeWarning and keeps listening.</li></ul><h3>Conclusion</h3><p>Centralized configuration moved connection details out of your code. Configuration change notifications close the remaining gap: when the configuration in Amazon S3 or AWS Secrets Manager changes, your application uses it on its next connection after the notification is processed, without a shorter TTL, a restart, or extra code.</p><p>Start with one connection string and one queue, keep your TTL as a safety net, and let AWS tell python-oracledb when to look again.</p><h3>FAQs</h3><p><strong>1. Do I need to change my application code?</strong><br>No. After the one-time AWS setup (an SQS queue and an EventBridge rule), you only add the queueurl parameter to the connection string. The plugin handles the rest: it polls the queue in the background and updates the cache when a notification arrives.</p><p><strong>2. Does a notification reconnect my application?</strong><br>No. A notification only updates the cache. Open connections and pools keep working with the settings they were created with, and new connections and pools use the new configuration.</p><p><strong>3. What if AWS is unavailable when the next connection is made?</strong><br>After an update, python-oracledb keeps using the expired configuration until the grace period ends, 30 minutes by default, counted from when the notification was processed. After a deletion, there is no fallback, and the connection attempt raises an error.</p><p><strong>4. Do I still need </strong><strong>queueurl and a TTL?</strong><br>queueurl is optional: without it, the configuration is cached and refreshed by config_time_to_live alone, as before. With it, keep a sensible config_time_to_live as a safety net in case a notification is not delivered.</p><p><strong>5. Can several connection strings share one queue?</strong><br>Yes, within one application process. The plugin matches each notification to the connection strings that use the changed configuration.</p><h3>python-oracledb Resources</h3><p>python-oracledb is the open source Python driver for <a href="https://www.oracle.com/cloud/free/">Oracle AI Database 26ai.</a></p><ul><li><a href="https://python-oracledb.readthedocs.io/en/latest/">Documentation</a></li><li><a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#using-amazon-web-services-centralized-configuration-providers">AWS centralized configuration providers</a></li><li><a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#caching-configuration-information">Caching configuration information</a></li><li><a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/installation.html">Installation</a></li><li><a href="https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#amazon-web-services-aws-configuration-provider-plugin">AWS configuration provider plugin</a></li><li><a href="https://github.com/oracle/python-oracledb">Source code on GitHub</a></li><li><a href="https://github.com/oracle/python-oracledb/discussions">GitHub discussions</a></li><li><a href="https://pypi.org/project/oracledb/">PyPI</a></li></ul><h3>AWS Resources</h3><ul><li><a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventBridge.html">Amazon S3 and Amazon EventBridge</a></li><li><a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/monitoring-eventbridge.html">Matching AWS Secrets Manager events with Amazon EventBridge</a></li><li><a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/welcome.html">Amazon SQS Developer Guide</a></li><li><a href="https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-rules.html">Amazon EventBridge rules</a></li><li><a href="https://docs.aws.amazon.com/awscloudtrail/latest/userguide/cloudtrail-user-guide.html">AWS CloudTrail User Guide</a></li><li><a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html">IAM security best practices</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=d4d3609f9c01" width="1" height="1" alt=""><hr><p><a href="https://medium.com/oracledevs/event-driven-configuration-refresh-for-oracle-ai-database-26ai-with-python-oracledb-amazon-s3-and-d4d3609f9c01">Event-Driven Configuration Refresh for Oracle AI Database 26ai with python-oracledb, Amazon S3, and…</a> was originally published in <a href="https://medium.com/oracledevs">Oracle Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Change-Aware Configuration Caching for Oracle AI Database 26ai with python-oracledb and Google…]]></title>
            <link>https://medium.com/oracledevs/change-aware-configuration-caching-for-oracle-ai-database-26ai-with-python-oracledb-and-google-5033e9386781?source=rss----749dcac244ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/5033e9386781</guid>
            <category><![CDATA[cloud-computing]]></category>
            <category><![CDATA[python]]></category>
            <category><![CDATA[gcp]]></category>
            <category><![CDATA[oracle-database]]></category>
            <category><![CDATA[google-cloud]]></category>
            <dc:creator><![CDATA[Samia El Amarti]]></dc:creator>
            <pubDate>Thu, 29 Oct 2026 10:50:19 GMT</pubDate>
            <atom:updated>2026-11-11T13:11:27.079Z</atom:updated>
            <content:encoded><![CDATA[<h3>Change-Aware Configuration Caching for Oracle AI Database 26ai with python-oracledb and Google Cloud</h3><h4>Starting with python-oracledb 26.1.0, your applications can pick up configuration changes in Google Cloud Storage or Secret Manager shortly after they are made, by adding one connection string parameter, with no restarts and no shorter cache lifetimes.</h4><figure><img alt="Featued image" src="https://cdn-images-1.medium.com/max/1024/1*zLRGwNnSE2ZFpkOrPptJgA.png" /></figure><p>The <a href="https://medium.com/@sharad-chandran/python-oracledb-26-1-0-fdeecebc81be">python-oracledb 26.1.0</a> release adds configuration change notifications for the Google Cloud configuration provider plugin.</p><h3>Key Takeaways</h3><ul><li><strong><em>Configuration changes no longer need to wait up to 24 hours.</em></strong><em> </em>With python-oracledb 26.1.0, Google Cloud Pub/Sub notifications expire affected cached configurations when the configuration in Cloud Storage or Secret Manager changes, so the next use retrieves the updated settings instead of waiting for the default 24-hour cache.</li><li><strong><em>One connection string parameter turns it on</em>.</strong> Add subscriptionname, the full name of your Pub/Sub pull subscription, to a config-gcpstorage:// or config-gcpsecretmanager:// Data Source Name (DSN). No additional application code is needed.</li><li><strong><em>Your configuration stays secure in Google Cloud.</em></strong> Pub/Sub notifications identify the changed resource without exposing its contents. python-oracledb retrieves the configuration directly from Cloud Storage or Secret Manager using your existing Google Cloud credentials.</li><li><strong><em>Updates bring fresh settings; deletions keep outdated ones out</em>.</strong> An updated configuration file or a new secret version is used on the next connection. A deleted configuration file, or a disabled or destroyed secret version, is removed from the cache once the notification is processed, so outdated settings are not reused.</li><li><strong><em>Existing connections keep running unchanged.</em></strong> Notifications only affect cached configuration. New connections and pools use the updated settings, while existing connections and pools keep their current configuration.</li></ul><p><a href="https://medium.com/@hamza.boutlih/oracle-ai-database-connectivity-with-python-oracledb-using-google-cloud-platform-gcp-9f1fc651d1af">In our earlier post</a>, you saw how the <a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html">python-oracledb Google Cloud Platform (GCP) configuration provider plugin</a> lets your application connect to <a href="https://www.oracle.com/cloud/free/">Oracle AI Database 26ai</a> through a single DSN, with the connection details stored in <a href="https://www.oracle.com/cloud/free/">Google Cloud Storage</a> or <a href="https://docs.cloud.google.com/secret-manager/docs/overview">Google Cloud Secret Manager</a>. Your code stays the same; the configuration lives in Google Cloud.</p><p>There was one catch. <em>python-oracledb caches that configuration.</em><strong><em> </em>So even when the configuration changes, your application keeps the old copy until the cache expires. </strong>python-oracledb 26.1.0 closes that gap: the plugin now listens to <a href="https://docs.cloud.google.com/pubsub/docs/overview">Google Cloud Pub/Sub</a> and <a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#configuration-change-notification">refreshes the cached configuration when it changes.</a></p><p>In this post, you’ll see why that matters, how it works, and how to turn it on with one connection string parameter.</p><h3>Your Configuration Changed. Why Hasn’t Your Application?</h3><p>Imagine your team updates a database connection configuration stored in Google Cloud Secret Manager. The new version is available, but your application continues using the old settings. Nothing is broken. It’s simply using a cached copy.<br>To avoid contacting Google Cloud on every connection, python-oracledb caches the configuration it retrieves. The <a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#caching-configuration-information">config_time_to_live</a> setting controls how long it stays cached, with a default of 86,400 seconds, or 24 hours.<br>Until now, you had two common ways to pick up changes sooner: restart every application that uses it, or shorten the Time-To-Live (TTL). Neither is ideal. Restarts add operational work, while shorter TTLs mean more requests to Google Cloud, even when nothing has changed.</p><p><em>Your application shouldn’t have to wait for a timer. Google Cloud can tell it when something changes.</em></p><p>That’s the idea behind event-driven configuration change notifications.</p><h3>How a Change Finds Its Way to Your Application</h3><p>Instead of waiting for the cache to expire, python-oracledb can receive a notification from the <a href="https://cloud.google.com/pubsub/docs/overview">Google Cloud Pub/Sub</a> messaging service when your configuration changes.</p><p>The setup connects three components: Google Cloud Storage, where the configuration is stored as a JSON object; Google Cloud Secret Manager, where it is stored as a versioned secret; and Pub/Sub, which delivers change notifications to the python-oracledb GCP configuration provider plugin. Pub/Sub uses a topic to receive events and a <a href="https://docs.cloud.google.com/pubsub/docs/pull">pull subscription</a> to deliver them. You enable notifications by adding subscriptionname, the full name of that subscription, to the connection string.</p><p>Here’s what happens when a configuration changes:</p><ol><li><strong>A resource changes in Google Cloud.</strong> You update or delete a configuration file in Cloud Storage, or add, disable, or destroy a secret version, or delete the secret, in Secret Manager.</li><li><strong>Google Cloud publishes a notification.</strong> The service sends an event to your Pub/Sub topic, naming the affected resource.</li><li>Pub/Sub delivers the event to your application’s pull subscription. The plugin keeps a streaming pull connection open on the subscription, so the event arrives as soon as Pub/Sub delivers it. There is no polling interval to configure.</li><li><strong>The plugin updates the matching cache entries.</strong> It finds the connection strings that use the changed resource and expires their cached configuration, or removes it after a deletion. Unrelated configurations remain untouched.</li><li><strong>The next use retrieves the configuration.</strong> The next oracledb.connect() or oracledb.create_pool() call reads the configuration from Cloud Storage or Secret Manager.</li></ol><figure><img alt="Flow diagram showing how Google Cloud Storage and Secret Manager send configuration change events through Google Cloud Pub/Sub to the python-oracledb GCP configuration provider. The provider matches notifications to connection strings and expires cached configurations after updates or removes them after deletions." src="https://cdn-images-1.medium.com/max/1024/1*7KjxHgOQ2hJsVsJTC3_7YQ.png" /><figcaption>How a change in Google Cloud Storage or Secret Manager reaches the python-oracledb configuration cache.</figcaption></figure><p>The figure traces one change from start to finish. The <a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#setting-up-google-cloud-storage-configuration-change-notification">subscriptionname</a> parameter in your DSN points the plugin to its Pub/Sub subscription. Each event names only the resource that changed. The plugin expires the matching cache entry after an update or removes it after a deletion, and python-oracledb reads the configuration again on the next connection.</p><p><em>Google Cloud signals the change. python-oracledb retrieves the configuration when it’s needed.</em></p><h3><strong>Before You Start</strong></h3><p>You need:</p><ul><li>python-oracledb 26.1.0 or later, installed with the GCP configuration provider: python -m pip install oracledb[gcp_config] (<a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/installation.html">installation guide</a>).</li><li>A <a href="https://cloud.google.com/pubsub/docs/create-topic">Pub/Sub topic</a> that receives the change events, and a <a href="https://cloud.google.com/pubsub/docs/create-subscription">pull subscription</a> on it for your application.</li><li>The Pub/Sub Subscriber role (roles/pubsub.subscriber) on the subscription for the application’s identity, in addition to its read access to the configuration, and the Pub/Sub Publisher role (roles/pubsub.publisher) on the topic for your project’s Cloud Storage service agent, so the bucket can publish notifications (<a href="https://cloud.google.com/storage/docs/pubsub-notifications">Pub/Sub notifications for Cloud Storage</a>).</li></ul><h4>Set Up Notifications for Cloud Storage</h4><ol><li>Allow the Cloud Storage service agent to publish to your topic.</li><li><a href="https://cloud.google.com/storage/docs/reporting-changes">Create a Pub/Sub notification</a> on the bucket that sends object events to your topic, using the <a href="https://cloud.google.com/storage/docs/pubsub-notifications">JSON payload format</a>.</li><li>Add the full subscription name as the subscriptionname parameter of the connection string.</li></ol><pre>import oracledb<br>import oracledb.plugins.gcp_config_provider<br><br>dsn = (<br>    &quot;config-gcpstorage://project=my-project;bucket=my-bucket;object=config.json&quot;<br>    &quot;?subscriptionname=projects/my-project/subscriptions/config-updates-app1&quot;<br>)<br><br>with oracledb.connect(dsn=dsn) as connection:<br>    with connection.cursor() as cursor:<br>        cursor.execute(&quot;select sys_context(&#39;userenv&#39;, &#39;service_name&#39;) from dual&quot;)<br>        print(cursor.fetchone())</pre><p>Compared with a regular Cloud Storage DSN, only subscriptionname is new. It takes the full name of your subscription, in the form projects/&lt;project&gt;/subscriptions/&lt;subscription&gt;, exactly as Google Cloud shows it. The query prints the database service the application is connected to, so after you upload a new config.json that points elsewhere, the next oracledb.connect() call shows the change.</p><h4>Set Up Notifications for Secret Manager</h4><p>Secret Manager publishes <a href="https://cloud.google.com/secret-manager/docs/event-notifications">event notifications</a> directly to Pub/Sub, so there is no extra service to configure:</p><ol><li><a href="https://cloud.google.com/secret-manager/docs/event-notifications">Allow the Secret Manager service agent to publish</a> to your topic.</li><li><a href="https://cloud.google.com/secret-manager/docs/event-notifications">Attach the topic to your secret</a>.</li><li>Add the subscription name to the connection string:</li></ol><pre>dsn = (<br>    &quot;config-gcpsecretmanager://projects/my-project/secrets/app-config/versions/latest&quot;<br>    &quot;?subscriptionname=projects/my-project/subscriptions/config-updates-app1&quot;<br>)</pre><p>Secret Manager stores configuration in versions, and <a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#google-cloud-secret-manager-centralized-configuration-provider">the plugin follows them</a>. When you add a version (<a href="https://docs.cloud.google.com/secret-manager/docs/event-notifications">SECRET_VERSION_ADD</a>), connection strings that read versions/latest get the new version on their next connection. When you disable or destroy a version (SECRET_VERSION_DISABLE, SECRET_VERSION_DESTROY), the cached configuration that used it is removed. When you delete the secret (SECRET_DELETE), it is removed for every connection string that reads that secret.</p><h3>Update or Retire: How python-oracledb Reacts</h3><p>Not every change means the same thing. Uploading a new configuration file or adding a secret version says “use this instead”. Deleting the file or disabling a version says “stop using this”. python-oracledb treats the two differently, and only ever touches its cache, never your open connections.</p><figure><img alt="Table: uploading a new configuration file or adding a secret version expires the cached configuration, so the next connection uses the new settings; deleting the configuration file, disabling or destroying a secret version, or deleting the secret removes the cached copy, so the next connection fails if the configuration is gone." src="https://cdn-images-1.medium.com/max/1024/1*yfm3JshJvtqM8PE7Z7hgGw.png" /><figcaption>How python-oracledb reacts to each kind of change in Google Cloud</figcaption></figure><p><strong>An update doesn’t cause an immediate outage.</strong> If Google Cloud is briefly unavailable when the next connection needs the new settings, python-oracledb keeps using the expired copy during the grace period (config_time_to_live_grace_period, 30 minutes by default). The grace period starts when the notification is processed. After it ends, the connection attempt raises an error.</p><p><strong>A retirement is final.</strong> Once a configuration file is deleted, a secret version is disabled or destroyed, or the secret is deleted, the cached copy is gone, so retired settings can’t be reused by mistake.</p><p>Two things don’t change:</p><ul><li><strong>Open connections and pools keep running</strong> with the settings they were created with. New connections and new pools pick up the change.</li><li><strong>Your </strong><strong>config_time_to_live still applies.</strong> Notifications make changes arrive sooner; the TTL is the safety net if one is ever missed.</li></ul><h3>Ready for Production: A Short Checklist</h3><p>Before you turn on notifications for a production service, check these points:</p><ul><li><strong>Each application process has its own subscription.</strong> Pub/Sub hands each message to only one of the clients pulling from a subscription. If two processes share one, each sees only part of the changes. Create one subscription per process on the same topic; within a process, one subscription serves all its connection strings.</li><li><strong>The subscription is a pull subscription.</strong> The plugin pulls the events itself, so a <a href="https://docs.cloud.google.com/pubsub/docs/subscription-overview">push subscription</a> won’t deliver anything to it.</li><li><strong>Only configuration events reach the topic.</strong> <a href="https://docs.cloud.google.com/storage/docs/reporting-changes">Filter the Cloud Storage notification by object name prefix</a> so that only your configuration files publish events. Unrelated events are ignored, but each one still costs a delivery.</li><li><strong>Caching is on.</strong> With config_time_to_live set to 0, nothing is cached, so there is nothing to refresh, and the plugin doesn’t listen even when subscriptionname is set.</li><li><strong>The application can wait out a short outage.</strong> If the connection to Pub/Sub drops, the plugin reconnects on its own with increasing delays, and the TTL keeps refreshing the configuration in the meantime.</li></ul><h4>Conclusion</h4><p>Centralized configuration took connection details out of your code. Configuration change notifications take the waiting out of changing them. Upload a new configuration file to Cloud Storage or add a secret version in Secret Manager, and your application uses it on its next connection: no restart, no shorter TTL, no new code.</p><p>Start with one subscription and one connection string, keep your TTL as a safety net, and let Google Cloud tell python-oracledb when something changes.</p><h4>FAQs</h4><p><strong>1. What do I change in my application?</strong><br> Only the connection string: add the subscriptionname parameter. The plugin receives the notifications and updates the cache for you.</p><p><strong>2. Will a notification interrupt my running connections?</strong><br> No. It only expires or removes the cached configuration. Open connections and pools keep working; new ones use the updated configuration.</p><p><strong>3. My connection string reads a fixed secret version. Is it refreshed when I add a new version?</strong><br> No. Only connection strings that read versions/latest follow new versions. A fixed version is removed from the cache only if that version is disabled or destroyed.</p><p><strong>4. Can Cloud Storage and Secret Manager use the same subscription?</strong><br> Yes. One topic and one subscription can carry both, and every connection string in the process can share that subscription.</p><p><strong>5. What happens if Google Cloud is unavailable at the next connection?</strong><br>After an update, python-oracledb keeps using the expired configuration during the grace period, 30 minutes by default, counted from when the notification is processed. After a deletion, there is no fallback, and the connection attempt raises an error.</p><p><strong>6. Do I still need </strong><strong>config_time_to_live?</strong><br> Yes. Keep a sensible value as a safety net in case a notification is ever missed. With 0, nothing is cached and notifications have no effect.</p><h4>python-oracledb Resources</h4><p>python-oracledb is the open source Python driver for <a href="https://www.oracle.com/cloud/free/">Oracle AI Database 26ai</a>.</p><ul><li><a href="https://python-oracledb.readthedocs.io/en/latest/">Documentation</a></li><li><a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#using-google-cloud-platform-centralized-configuration-providers">GCP centralized configuration providers</a></li><li><a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/connection_handling.html#caching-configuration-information">Caching configuration information</a></li><li><a href="https://python-oracledb.readthedocs.io/en/latest/user_guide/installation.html">Installation</a></li><li><a href="https://python-oracledb.readthedocs.io/en/latest/api_manual/module.html#google-cloud-platform-configuration-provider-plugin">GCP configuration provider plugin</a></li><li><a href="https://github.com/oracle/python-oracledb">Source code on GitHub</a></li><li><a href="https://github.com/oracle/python-oracledb/discussions">GitHub discussions</a></li><li><a href="https://pypi.org/project/oracledb/">PyPI</a></li></ul><h4>Google Cloud Resources</h4><ul><li><a href="https://cloud.google.com/storage/docs/pubsub-notifications">Pub/Sub notifications for Cloud Storage</a></li><li><a href="https://cloud.google.com/secret-manager/docs/event-notifications">Secret Manager event notifications</a></li><li><a href="https://cloud.google.com/pubsub/docs/overview">Pub/Sub overview</a></li><li><a href="https://cloud.google.com/pubsub/docs/subscription-overview">Pub/Sub subscriptions</a></li><li><a href="https://cloud.google.com/pubsub/docs/access-control">Pub/Sub access control</a></li><li><a href="https://cloud.google.com/iam/docs/best-practices-service-accounts">IAM best practices for service accounts</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5033e9386781" width="1" height="1" alt=""><hr><p><a href="https://medium.com/oracledevs/change-aware-configuration-caching-for-oracle-ai-database-26ai-with-python-oracledb-and-google-5033e9386781">Change-Aware Configuration Caching for Oracle AI Database 26ai with python-oracledb and Google…</a> was originally published in <a href="https://medium.com/oracledevs">Oracle Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Building a Document Knowledge Base with Oracle APEX and Oracle AI Vector Search]]></title>
            <link>https://medium.com/oracledevs/building-a-document-knowledge-base-with-oracle-apex-and-oracle-ai-vector-search-394009d991fe?source=rss----749dcac244ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/394009d991fe</guid>
            <category><![CDATA[agentic-rag]]></category>
            <category><![CDATA[aivectorsearch]]></category>
            <category><![CDATA[oracle-apex]]></category>
            <category><![CDATA[oracle-ai-database]]></category>
            <category><![CDATA[ai-vector-databases]]></category>
            <dc:creator><![CDATA[Kailasj]]></dc:creator>
            <pubDate>Thu, 22 Oct 2026 11:04:59 GMT</pubDate>
            <atom:updated>2026-10-22T11:04:58.239Z</atom:updated>
            <content:encoded><![CDATA[<h4><strong>A document knowledge base with Oracle APEX, Oracle AI Database, AI Vector Search, and RAG</strong></h4><p><em>By Kailas Jawadekar</em></p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*GxHepBaMysjkeLEFlM7uKw.png" /></figure><p>A collection of documents can hold valuable knowledge while still being difficult to use. Finding an answer may require opening several files, searching for related terms, and reconciling different versions. We built a Document Knowledge Base to make that content searchable and reusable through natural-language questions, generated drafts, and document comparisons.</p><p>The Document Knowledge Base combines Oracle APEX, PL/SQL, Oracle AI Database vector storage, and AI inference. Its core workflow is independent of the subject matter: organize a document collection, retrieve relevant passages, and use them to answer questions or create new content. Support articles, developer guides, operational runbooks, and product documentation are all examples of collections suited to this pattern.</p><h3>Key takeaways</h3><ul><li>Ingest DOCX documents into stored files, metadata, text chunks, and embeddings.</li><li>Use Oracle AI Vector Search and RAG to retrieve evidence for each question.</li><li>Ask for source-grounded answers with filenames and headings to support review.</li><li>Generate editable content from retrieved passages and prompt templates.</li><li>Compare selected documents within a bounded context; check coverage before interpreting omissions.</li></ul><p><strong>Review generated results:</strong> tables, long passages, missing embeddings, or limited retrieval can leave evidence out. Check answers, drafts, and comparisons against the originals.</p><h3>Implementation scope</h3><p><strong>Document Knowledge Base is a simple internal project, not a production deployment.</strong> It demonstrates a reusable workflow for finding answers, drafting content, and comparing documents. The collection supplies the subject matter: support guidance, developer documentation, operational procedures, onboarding material, or product knowledge. These broader examples illustrate how the pattern can be adapted; each needs suitable documents, prompts, and coverage checks.</p><figure><img alt="Document Knowledge Base brings document upload, search, content generation, and comparison into one application." src="https://cdn-images-1.medium.com/max/1024/1*twoyTlVVMKT50gXvh267wA.png" /><figcaption><em>`Document Knowledge Base brings document upload, search, content generation, and comparison into one application.</em></figcaption></figure><h3>What users can do</h3><p>The Document Knowledge Base organizes work around three capabilities:</p><ul><li><strong>Ask a question.</strong> Semantic search retrieves relevant passages and generates an answer with source references. For a support collection, a question might be “How do I troubleshoot a connection timeout?”</li><li><strong>Create a draft.</strong> Retrieved content and template instructions guide an output such as a summary, knowledge article, or response draft. The document collection and prompt determine the subject and format.</li><li><strong>Compare documents.</strong> A selected document and baseline provide the evidence for a structured comparison. In a documentation collection, the task might be to summarize changes between two versions.</li></ul><p>The current uploader adds DOCX files and metadata. Administration views show document coverage, ingestion status, and missing embeddings.</p><h3>Use cases for a Document Knowledge Base</h3><p>The collection and task give the knowledge base its purpose. These examples illustrate applications of the same architecture; each requires suitable source content and prompts.</p><figure><img alt="Use cases for a Document Knowledge Base" src="https://cdn-images-1.medium.com/max/1024/1*fwjCV-nDU3pbs187eneZXQ.png" /><figcaption>Use cases for a Document Knowledge Base</figcaption></figure><h3>How the architecture works</h3><p>Oracle APEX supplies the browser interface, forms, and reports. PL/SQL packages extract text, retrieve passages, assemble prompts, and call the AI service. Oracle AI Database provides native vector storage and AI Vector Search alongside relational data. In this implementation, the schema stores original DOCX BLOBs, filenames, dates, hashes, processing status, extracted text, ordered chunks, and embeddings. It also stores search questions and query vectors, generated answers, reusable prompt templates, and assembled prompts and outputs for generated reports and comparisons. The answer package stores answer text, but does not persist its assembled prompt.</p><p>Keeping these records in Oracle AI Database lets SQL join evidence and metadata directly, apply category filters, and retrieve saved generation prompts and outputs without synchronizing a separate vector store. This makes the workflow easier to inspect and maintain; it does not by itself guarantee complete extraction or accurate answers.</p><p>The application has two main flows:</p><pre>Document → Extract text → Split into chunks → Create embeddings → Store<br>Question → Create embedding → Find relevant chunks → Generate answer</pre><p>This is retrieval augmented generation, or RAG. The model receives selected source passages with each request; we do not train it on the uploaded documents. Keeping vectors alongside relational data lets SQL combine semantic ranking with metadata filters and explicit document selection.</p><p>Our AI wrapper is configured for OCI Generative AI endpoints using OpenAI-compatible embedding and chat request formats. Endpoint URLs and model identifiers are configuration values. API secrets belong in APEX Web Credentials and are referenced by the REST call, keeping them out of application code. The checked-in endpoint defaults and model placeholders are setup values, not proof that those combinations are currently supported. Confirm both embedding and chat access in the target environment using the service’s current documentation.</p><h3>Prerequisites and setup</h3><ul><li><strong>Oracle APEX and database support.</strong> Use an APEX workspace and parsing schema on an Oracle Database environment that supports VECTOR, TO_VECTOR, and VECTOR_DISTANCE. Verify the installed APEX and database versions; the source does not establish a tested minimum APEX release. The custom packages use APEX_ZIP, APEX_JSON, and APEX_WEB_SERVICE.</li><li><strong>OCI Generative AI access.</strong> Configure reachable HTTPS embedding and chat endpoints, authorized model IDs, regional availability, and the required service permissions. Set EMBEDDINGS_URL, EMBEDDING_MODEL, CHAT_URL, and CHAT_MODEL. Test both calls; an OpenAI-compatible chat endpoint does not automatically imply compatible embedding support. The current vector columns and conversion expect 1,536 dimensions, so check the embedding-dimension diagnostic before ingestion.</li><li><strong>APEX Web Credentials.</strong> For the project’s bearer-token setup, create an HTTP Header credential with header Authorization and secret value Bearer &lt;API key&gt;, restricted with <strong>Valid for URLs</strong> to the intended service prefixes. Set WEB_CREDENTIAL_STATIC_ID to that credential’s Static ID. The later project build instructions use credentials-for-standards-ai, while the original seed uses openai-api-key; match the active workspace credential rather than copying either blindly. Configure outbound network access as required by the database environment.</li><li><strong>Schema, pages, and a small DOCX trial.</strong> Install the schema and ingestion, AI, search, answer, template, report, and comparison packages, check compilation, and connect the APEX page processes. Start with a representative DOCX and inspect extracted text, chunks, and embeddings before testing answers. The supplied source is not a complete, verified one-click APEX deployment. The current uploader is DOCX-only; PDF, HTML, Markdown, and images need additional parsers.</li></ul><p>See Oracle’s <a href="https://docs.oracle.com/en/database/oracle/apex/26.1/htmdb/understanding-web-credentials.html">Web Credentials guidance</a> and <a href="https://docs.oracle.com/en-us/iaas/Content/generative-ai/api-keys.htm">OCI Generative AI API key and endpoint documentation</a> for the supported authentication and service configuration.</p><h3>Turning documents into searchable content</h3><p>When a user uploads a DOCX file, the application retains the original file and calculates a SHA-256 hash to detect an identical upload. It uses APEX_ZIP and XMLTABLE to extract body paragraphs, detects headings, and groups text into chunks of approximately 5,000 characters. Each chunk retains its heading and connection to the source document.</p><p><strong>Extraction limitation:</strong> the XPath reads direct body paragraphs, not paragraphs inside table cells. The helper takes at most 200 characters per XML text run and caps the assembled paragraph at 3,000 bytes. Long passages can therefore be incomplete before chunking; the original DOCX remains the reference for checking coverage. Chunks have no overlap.</p><p>The application creates an embedding for each chunk and stores it as VECTOR(1536, FLOAT32). An embedding is a list of numbers representing the text in a form that supports similarity comparisons. Related passages can be close together even when they use different words. For example, “unable to sign in” may match a passage about “authentication failures.”</p><p>The embedding function sends text to the configured model, then converts the returned JSON array into an Oracle vector. This excerpt shows the conversion inside our PL/SQL package:</p><pre>l_response := embedding_response(p_text);<br>l_embedding_json := json_query(<br>  l_response, &#39;$.data[0].embedding&#39; returning clob);</pre><pre>select to_vector(l_embedding_json, 1536, float32)<br>  into l_vector<br>  from dual;</pre><p>embedding_response builds the JSON request with the configured embedding model and input text. Document chunks and questions use the same embedding model so their vectors can be compared.</p><h3>Finding evidence and generating an answer</h3><p>For each search, we embed the question once and store its vector. SQL ranks document chunks by cosine distance: a smaller distance means greater semantic similarity. The simplified query below shows the core operation using generic knowledge-base table names. Metadata filters can narrow the candidate documents before ranking.</p><pre>select c.chunk_id, c.heading, c.chunk_text,<br>       vector_distance(c.embedding,<br>                       q.query_embedding, cosine) distance<br>  from kb_doc_chunks c<br>  join kb_search_queries q on q.search_id = :search_id<br> where c.embedding is not null<br> order by distance<br> fetch first :top_n rows only;</pre><p>We assemble the selected passages, filenames, headings, and document dates into a prompt. The instructions ask the model to answer the specific question, cite its sources, and say when the supplied material is insufficient. A shared function handles generation:</p><pre>l_output := kb_ai_pkg.generate_text(<br>  p_system_prompt =&gt;<br>    &#39;Ground every claim in provided source material.&#39;,<br>  p_prompt =&gt; l_prompt<br>);</pre><p>Inside this function, APEX_JSON constructs the chat request and APEX_WEB_SERVICE.MAKE_REST_REQUEST sends it using the configured Web Credential. The response text is extracted from choices[0].message.content and displayed in APEX.</p><h3>Reusing the evidence for content and comparisons</h3><p>Content generation adds a topic, output type, and template instructions to the retrieved context. A template defines the audience, structure, and tone while the source passages supply the evidence. Our implementation saves the assembled prompt and generated text and offers a Markdown download.</p><figure><img alt="A topic, content type, category, and template guide content generation. This screenshot shows the input form; generated drafts still need source review." src="https://cdn-images-1.medium.com/max/1024/1*8vJ0tiy3BR7b7JRDaGFqEA.png" /><figcaption><em>A topic, content type, category, and template guide content generation. This screenshot shows the input form; generated drafts still need source review.</em></figcaption></figure><p>Document comparison answers a different question: “What changed between these two documents?” Users select a current document and a baseline, then generate a structured comparison with source references. The screenshot shows the current document and baseline selectors, an optional category filter, and the comparison action.</p><figure><img alt="Select a current document and a baseline to compare their content. The documents currently loaded in this internal project provide the example selection." src="https://cdn-images-1.medium.com/max/1024/1*m7-8WRCW5M_L-VuCR8r4Yg.png" /><figcaption><em>Select a current document and a baseline to compare their content. The documents currently loaded in this internal project provide the example selection.</em></figcaption></figure><p>Unlike semantic search, this workflow takes a bounded set of chunks from each selected document in document order. The prompt labels the two sets as current and baseline and asks for supported changes. The comparison defaults to the first 10 embedded chunks per document, with a maximum of 15, subject to the optional category filter. Because context is bounded, an omitted item is not automatically evidence that it was resolved.</p><p>The reusable pieces are document storage, extraction, chunking, embeddings, retrieval, and generation. The domain determines the metadata, filters, and prompts. A support repository might filter by product and version; developer documentation might use API, language, and release. Adapting the application also requires parsers for the source formats and access controls appropriate to the collection. Our current uploader handles DOCX files.</p><p>Answer quality depends on the evidence supplied. Our current extractor focuses on body paragraphs, so table content and long passages need attention when assessing coverage. Similarity scores do not establish factual accuracy or freshness, and generated answers still need review against their sources.</p><p>The Document Knowledge Base brings these pieces into one workflow: add documents, ask questions, create drafts, and compare sources. Keeping documents, metadata, vectors, reusable prompts, and generated artifacts together in Oracle Database supports that workflow with familiar joins and filters. The next step is to validate extraction and retrieval against representative documents, then review generated outputs against their sources before broader use.</p><p><em>The images show the live Document Knowledge Base application. Code snippets are simplified examples using generic </em><em>kb_ names; they are not standalone installation scripts.</em></p><p>Further reading: <a href="https://docs.oracle.com/en/database/oracle/apex/24.2/aeapi/APEX_WEB_SERVICE.html">Oracle APEX web service API</a> and <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/vecse/vector-distance-functions-and-operators.html">Oracle vector distance functions</a>.</p><h3>Frequently asked questions</h3><h3>What is this app for?</h3><p>Document Knowledge Base makes a DOCX collection searchable and reusable through questions, generated drafts, and selected-document comparisons. For example, a support team could retrieve troubleshooting guidance, a developer team could draft an API summary, or an onboarding team could help employees find procedures. These uses depend on the documents and prompts supplied.</p><h3>Which file types are supported?</h3><p>The current uploader and extractor handle DOCX. They do not implement PDF, HTML, Markdown, scanned images, or complete DOCX table extraction. Adding another format requires a parser and coverage validation.</p><h3>What does RAG do here?</h3><p>Retrieval augmented generation embeds a question, ranks stored chunks by semantic similarity, and supplies selected passages to the model as context. Instructions request source-grounded answers and source references. The comparison workflow instead takes bounded, ordered chunks from two explicitly selected documents.</p><h3>Do uploaded documents train the model?</h3><p>The application code makes embedding and generation calls; it does not train or fine-tune a model. Selected source text is sent to the configured inference service. That implementation fact is not a blanket statement about provider retention or data use: verify the chosen service, model, and applicable terms before uploading sensitive documents.</p><h3>What limits coverage and answer accuracy?</h3><p>Table cells are skipped, long XML runs and paragraphs can be truncated, and chunks have no overlap. Missing embeddings, metadata filters, and bounded retrieval or comparison can leave relevant evidence out. Similarity and source citations do not prove correctness or freshness. Check extracted coverage and review every generated answer, draft, and comparison against the original documents.</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=394009d991fe" width="1" height="1" alt=""><hr><p><a href="https://medium.com/oracledevs/building-a-document-knowledge-base-with-oracle-apex-and-oracle-ai-vector-search-394009d991fe">Building a Document Knowledge Base with Oracle APEX and Oracle AI Vector Search</a> was originally published in <a href="https://medium.com/oracledevs">Oracle Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Stop Making AI Guess Your Database: Oracle SQLcl MCP Server, Agent Memory, and Oracle AI Database]]></title>
            <link>https://medium.com/oracledevs/stop-making-ai-guess-your-database-oracle-sqlcl-mcp-server-agent-memory-and-oracle-ai-database-3c7c6bc079c8?source=rss----749dcac244ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/3c7c6bc079c8</guid>
            <category><![CDATA[ai-agent]]></category>
            <category><![CDATA[mcp-server]]></category>
            <category><![CDATA[oracle]]></category>
            <category><![CDATA[ai]]></category>
            <category><![CDATA[database]]></category>
            <dc:creator><![CDATA[Srinidhi Sathyamurthy]]></dc:creator>
            <pubDate>Wed, 21 Oct 2026 15:15:07 GMT</pubDate>
            <atom:updated>2026-10-21T15:15:05.827Z</atom:updated>
            <content:encoded><![CDATA[<p><strong>A developer guide to controlled Oracle AI Database workflows with the Oracle SQLcl MCP server, Codex, Cursor, Antigravity, Claude, and Oracle AI Agent Memory.</strong></p><p><strong>Companion articles and notebooks:</strong></p><ul><li>Codex workflow: <a href="https://blogs.oracle.com/developers/how-to-build-a-controlled-mcp-workflow-for-codex-and-oracle-ai-database">How to Build a Controlled MCP Workflow for Codex and Oracle AI Database</a>, <a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/codex_mcp_oracle_ai_database_memory_langchain.ipynb">Companion Codex notebook</a></li><li>Cursor workflow: <a href="https://blogs.oracle.com/developers/build-a-controlled-cursor-mcp-workflow-with-oracle-ai-database">Build a Controlled Cursor MCP Workflow with Oracle AI Database,</a> <a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/cursor_mcp_oracle_ai_database.ipynb">Companion Cursor notebook</a></li><li>Antigravity workflow: <a href="https://blogs.oracle.com/developers/how-to-build-an-antigravity-workflow-with-the-oracle-sqlcl-mcp-server-and-oracle-ai-database">How to Build an Antigravity Workflow with the Oracle SQLcl MCP Server and Oracle AI Database</a>, <a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/antigravity_mcp_oracle_ai_database.ipynb">Companion Antigravity notebook</a></li><li>Claude workflow: <a href="https://blogs.oracle.com/developers/when-to-use-claude-memory-oracle-ai-agent-memory-and-langchain-together">When to use Claude memory, Oracle AI Agent Memory, and LangChain together</a>, <a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/claude_mcp_oracle_ai_database_memory_langchain.ipynb">Companion Claude notebook</a></li></ul><figure><img alt="Stop Making AI Guess Your Database: Oracle SQLcl MCP Server, Agent Memory, and Oracle AI Database" src="https://cdn-images-1.medium.com/max/1024/1*GaJ7gwGNc7UkG0LF_DF5pQ.png" /></figure><h3>Key takeaways</h3><ul><li>The <a href="https://docs.oracle.com/en/database/oracle/sql-developer-command-line/25.2/sqcug/sqlcl-mcp-server.html">Oracle SQLcl MCP</a> server gives an AI client an explicit tool path to <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/">Oracle AI Database</a>, so the assistant can inspect approved database context instead of relying only on schema details copied into a prompt.</li><li>Codex, Cursor, Antigravity, and Claude offer different developer experiences, but the database boundary can stay consistent: approved SQLcl connections, database privileges, inspectable tool calls, and validation before broader access.</li><li><a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.6/guide/api/agentmemory.html">Oracle AI Agent Memory</a> is a separate application layer. Add it when a workflow needs durable threads, scoped retrieval, reusable context, or continuity across sessions. Do not add memory simply because <a href="https://modelcontextprotocol.io/docs/2026-07-28/getting-started/intro">MCP </a>is present.</li><li>The companion notebooks are build and validation harnesses. They help prove configuration, storage, retrieval, and memory patterns, but they are not a substitute for live client validation or production telemetry.</li><li>Start small, validate the database path, and expand access only when the workflow is explainable.</li></ul><p>Your AI assistant can write SQL. But does it know your database?</p><p>A developer pastes a table definition into a prompt and asks for a query. The assistant returns something that looks right. Then the query fails because a column was renamed last week, the object lives in another schema, the current database user cannot see it, or the application has a rule that never made it into the prompt.</p><p>The issue is not that the assistant cannot generate SQL. The issue is that the assistant is reasoning from the context it was given. If the context is incomplete, stale, or simplified, the answer can be perfectly plausible and still be wrong for the environment in front of you.</p><p>That is what the title of this guide means by guessing. The goal is not to remove reasoning from the assistant. The goal is to stop making prompt text carry responsibilities that belong to tools, database permissions, and runtime validation.</p><p>Across this Oracle Database MCP series, the same pattern appeared in several developer environments. Codex, Cursor, Antigravity, and Claude can all sit at the front of a workflow. The Oracle SQLcl MCP server provides the tool boundary to Oracle AI Database, using approved SQLcl connection profiles instead of database details copied into a prompt. <a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.6/guide/api/agentmemory.html">Oracle AI Agent Memory</a> becomes relevant when an application needs durable context beyond the current interaction. LangChain can help with retrieval orchestration when that extra application layer is useful.</p><p>This final article brings those pieces together. Think of it as the map for the series: what each layer is responsible for, what changes between clients, what should stay the same, and how to decide when a simple MCP workflow has grown into a memory and retrieval architecture.</p><p>This article is the final guide in the Oracle Database MCP series. The earlier posts cover the hands on workflows for Codex, Cursor, Antigravity, and Claude with Oracle AI Agent Memory. Use this guide as the map, then follow the individual article and notebook links when you are ready to run a specific workflow.</p><h3>What Changes When AI Can Inspect the Database Instead of Relying on the Prompt?</h3><p>There is a practical difference between describing a database to an assistant and letting the assistant inspect approved database context through a tool. In the first case, the model sees whatever the developer remembered to paste into the prompt. In the second case, the model can ask a declared tool for information at runtime, within the permissions and connection boundaries you configured.</p><p>That distinction matters during normal development. Schemas evolve. Grants differ between environments. Object names are similar. Views hide implementation details. Data quality problems show up only when a query actually runs. Once a workflow can check those facts instead of assuming them, the assistant becomes easier to use for real database work and easier to debug when something goes wrong.</p><p>This is where context engineering for AI agents becomes practical. The goal is not to paste more and more database detail into the prompt. The goal is to decide which context should come from runtime database inspection, which context should come from retrieved memory, and which context should stay outside the model call entirely.</p><p>MCP does not make the model authoritative, and it does not replace database security. It gives the client a standard way to call tools. The Oracle SQLcl MCP server turns that protocol into a tool path for Oracle AI Database. The database still decides what the configured user can do, and the developer still decides which workflows should be automated, reviewed, or blocked.</p><h3>What MCP Does, and What It Does Not Do</h3><p>MCP gives an AI client a standard way to discover and call tools, and the Oracle SQLcl MCP server applies that pattern to Oracle Database workflows.</p><p>MCP does not replace database privileges. It does not automatically provide durable memory. It does not decide which operations an application should allow. Oracle AI Database still controls what the configured database identity can access and execute.</p><p>Agent Memory solves a different problem. It becomes useful when an application needs useful context to survive beyond the current interaction. Keeping tool access, database security, and durable memory as separate concerns makes the architecture easier to test and easier to operate.</p><h3>One architecture, multiple developer experiences</h3><p>The most useful way to read the series is to separate the developer experience from the shared foundation. Codex, Cursor, Antigravity, and Claude differ in how they fit into a developer workflow, but the architecture underneath does not need to be reinvented for every client.</p><figure><img alt="Table with columns Layer, Primary job, and Developer validation. Rows show an AI client planning and requesting tools; SQLcl MCP exposing Oracle tools through approved connections; Oracle AI Database executing SQL, enforcing privileges, and storing application data, vectors, memories, and traces; Oracle AI Agent Memory providing scoped retrieval APIs; and LangChain coordinating retrieval. Checks cover tool visibility, connections, permissions, identity, memory scope, and orchestration value." src="https://cdn-images-1.medium.com/max/1024/1*THnQtPqEzu3AdnNRrxACKQ.png" /><figcaption>The Oracle AI Agent Memory architecture places tool use, database execution, and durable retrieval in distinct layers.</figcaption></figure><p>Tool discovery and operation control are related, but they are not the same thing. A client may discover a tool, while SQLcl MCP configuration and Oracle Database privileges still determine which operations are allowed to execute</p><figure><img alt="Codex, Cursor, Antigravity, and Claude connect to an Oracle SQLcl MCP Server with explicit tools and an approved connection. The server links to Oracle AI Database for SQL, data, vectors, privileges, and activity tracking. Separately, an application memory layer connects through Oracle AI Agent Memory to Oracle AI Database for durable context, threads, scoped retrieval, and storage." src="https://cdn-images-1.medium.com/max/1024/1*zdTzHiy4tMy8vWD2y9cgvQ.png" /><figcaption>Figure 1. One controlled Oracle AI Database foundation for multiple AI Workflows</figcaption></figure><p><strong>Core idea: </strong>SQLcl MCP controls how assistants act through tools. <a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.6/guide/api/agentmemory.html">Oracle AI Agent Memory</a> controls what applications remember over time. They can both use Oracle AI Database, but they solve different parts of the architecture.</p><h3>The Oracle SQLcl MCP Server Is the Execution Boundary</h3><p>In these workflows, the Oracle SQLcl MCP server is the bridge between an MCP capable client and Oracle AI Database. SQLcl runs in MCP mode and exposes a declared set of tools. The client does not need direct database credentials in the prompt. It uses the MCP server, and the server works through SQLcl connection profiles that the developer has already created and validated.</p><p>That sounds like a small implementation detail, but it changes how you reason about the workflow. The assistant is no longer being trusted because a prompt says to behave safely. The database path has an identity, a set of privileges, a known connection, and observable actions. If a query is denied, that can be the correct outcome. If a tool is not exposed, the assistant cannot simply wish it into existence.</p><p>For developers, this turns a vague safety discussion into a set of concrete questions. Which saved connection is in use? Which database user owns that connection? What can that user read or change? Which SQLcl MCP capabilities are available? What activity can be inspected afterward? Those questions are much easier to test than a general instruction such as “only do safe things.”</p><p>The earlier articles walk through the concrete setup for each client. The important point for this capstone is that the control model belongs below the client. You should be able to swap the developer interface without rebuilding the database permission model from scratch.</p><h3>One Foundation, Four Developer Experiences</h3><p>The series uses several clients because developers do not all work in the same place. Some want an agent in the terminal. Some want the workflow inside an editor. Some are evaluating a newer agentic development environment. Some are exploring a general assistant together with application memory. The client changes the ergonomics, not the underlying responsibility model.</p><p><strong>Codex: useful when the agent sits close to the implementation loop</strong></p><p><a href="https://blogs.oracle.com/developers/how-to-build-a-controlled-mcp-workflow-for-codex-and-oracle-ai-database">The Codex workflow</a> is a good fit when the agent is already working with files, commands, configuration, notebooks, and implementation details. Database access then becomes one more tool in a broader development loop. That proximity is useful because the agent can relate database findings back to code changes, but it also raises the importance of an explicit database boundary.</p><p><a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/codex_mcp_oracle_ai_database_memory_langchain.ipynb">The companion Codex notebook</a> acts as a validation harness for prerequisites, the SQLcl connection path, memory storage, retrieval, and Agent Memory integration. Keeping those checks separate makes it easier to identify whether a failure comes from MCP discovery, the database connection, privileges, memory, or retrieval.</p><p><strong>Cursor: useful when database context belongs inside the editor loop</strong></p><p><a href="https://blogs.oracle.com/developers/build-a-controlled-cursor-mcp-workflow-with-oracle-ai-database">Cursor</a> keeps the database workflow close to the code editing experience. That can be especially useful when a developer is changing application code and needs to inspect the schema or validate assumptions without leaving the working loop. The same caution applies: convenience should not turn into broad, implicit access.</p><p><a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/cursor_mcp_oracle_ai_database.ipynb">The Cursor notebook</a> also makes an important distinction: its operational rows are simulated teaching data, not live Cursor telemetry. The notebook demonstrates the database backed pattern without presenting sample data as evidence of live client activity.</p><p><strong>Antigravity: useful for testing the same pattern in another agentic environment</strong></p><p><a href="https://blogs.oracle.com/developers/how-to-build-an-antigravity-workflow-with-the-oracle-sqlcl-mcp-server-and-oracle-ai-database">The Antigravity article</a> is valuable because it shows that the architecture is portable. The client interface changes, but the workflow still benefits from the same separation: developer facing agent, SQLcl MCP tool boundary, Oracle AI Database, Oracle AI Agent Memory when needed, and optional retrieval orchestration.</p><p><a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/antigravity_mcp_oracle_ai_database.ipynb">The Antigravity notebook </a>follows the same principle. It can validate the memory substrate, retrieval, and Agent Memory path, while live MCP behavior still needs to be verified through the actual client and SQLcl MCP workflow.</p><p><strong>Claude: useful for seeing where assistant context ends and application memory begins</strong></p><p><a href="https://blogs.oracle.com/developers/when-to-use-claude-memory-oracle-ai-agent-memory-and-langchain-together">The Claude article</a> adds a different question to the series. Once the assistant can use database tools, what should happen when useful context needs to survive the current interaction? The answer is not to make MCP carry memory responsibilities. Tool access and durable application memory are separate concerns, even when they use the same Oracle platform underneath.</p><p>That separation is one of the most important ideas in the whole series. An assistant can have useful working context and still need an application memory layer. Likewise, an application can maintain durable memory without giving an assistant unrestricted database access. Keeping those responsibilities separate gives developers clearer tests, clearer ownership, and fewer hidden assumptions.</p><h3>AI Agent Memory Belongs After the Workflow Has Something Worth Remembering</h3><p>It is tempting to add memory as soon as an agent demo starts working. In practice, that is often too early. Memory adds value when the workflow has a real continuity problem: a user returns later and expects the application to recall a preference, a developer wants prior tool outcomes available in a new session, or an agent needs to retrieve relevant context from earlier work without replaying an entire conversation.</p><p>In this series, AI agent memory refers to Oracle AI Agent Memory, which provides durable memory APIs backed by Oracle AI Database. Oracle AI Agent Memory sits in the application layer on top of Oracle AI Database. It gives Python applications an API for threads, durable memory records, scoped retrieval, and context assembly. Oracle AI Database remains the storage and enforcement layer underneath. That means memory can participate in the same kinds of engineering decisions developers already understand: identity, retention, access control, observability, data quality, and lifecycle.</p><p>For Python applications, the oracleagentmemory package provides the application API used to create users, agents, threads, memory records, and retrieval flows.</p><p>A useful rule is to ask what failure you are solving. If the problem is “the client cannot inspect the current schema,” add or fix the MCP tool path. If the problem is “the application loses useful context between sessions,” evaluate Agent Memory. If the problem is “we need a reusable retrieval pipeline around that memory,” then LangChain or another orchestration layer may help. Each addition should answer a concrete need.</p><h3>Thinking in two execution loops</h3><p>The architecture becomes easier to reason about when you separate the live interaction loop from the durable context loop. They can share Oracle AI Database, but they run for different reasons.</p><p>The live interaction loop is immediate. A developer asks a question or requests a task. The client decides that a database tool is needed. SQLcl MCP invokes the approved database path. Oracle AI Database returns a result. The client uses that result to continue the task.</p><p>The durable context loop is different. Application code stores selected conversation history, tool outcomes, memory records, metadata, or embeddings. Later, the application retrieves only the context that is useful for the current user or task. That context can then be assembled before the next model call. The point is not to replay everything. The point is to make useful context queryable and scoped.</p><p>Scoped retrieval for agents is what keeps this useful. A memory system should retrieve records for the right user, agent, thread, or workflow rather than treating all stored context as equally available. That scope is what helps memory stay relevant and prevents old or unrelated context from being added to the next model call.</p><figure><img alt="Table comparing live interaction and durable context. It describes clients calling SQLcl MCP tools for current database results, and applications storing or retrieving memory records, traces, metadata, or vectors. Verification checks cover tool discovery, database identity and privileges, retrieval quality, retention, correction, deletion, and whether retrieved context fits the next task." src="https://cdn-images-1.medium.com/max/1024/1*wHOXBHJ1d0-Tn1r05Vwxig.png" /><figcaption>The guide separates live SQLcl MCP interactions from durable context storage and lists checks for each loop.</figcaption></figure><h3>The notebooks are validation harnesses, not the product</h3><p>Every hands-on article in the series includes a companion notebook because a notebook is a practical place to make the moving parts visible. It can check local prerequisites, show sanitized configuration, create teaching tables, insert sample records, exercise lexical and vector retrieval, initialize Agent Memory, and finish with a validation snapshot.</p><p>That is useful, but it is important to keep the notebook in the right role. The final user experience happens in Codex, Cursor, Antigravity, or Claude. The notebook helps prove the surrounding database and memory path before or alongside that client. It is not automatically the source of live telemetry, and simulated teaching data should not be presented as proof that a live client action occurred.</p><h3>Keeping the Database Boundary Consistent</h3><p>The client can change. The database rules should not.</p><p>Codex, Cursor, Antigravity, and Claude all provide different ways for a developer to work with an AI assistant, but changing the client should not mean redesigning database access every time. The SQLcl connection, database identity, permissions, and validation path should remain predictable underneath.</p><p>A good starting point is a purpose specific database identity with only the privileges required for the workflow. During development, that usually means beginning with a small, read focused surface and expanding access only when there is a clear reason to do so.</p><p>The saved SQLcl connection also becomes part of the application architecture. Developers should know which connection alias is being used, which database user it represents, and which environment that user can access. When something behaves unexpectedly, those details are often more useful than changing the prompt.</p><p>The same principle applies to MCP tools. Expose the capabilities the workflow actually needs. A smaller tool surface is easier to understand, test, and troubleshoot than a large collection of tools that nobody has reviewed closely.</p><p>Database activity should also remain inspectable. When a client invokes a tool, the team should have a way to understand what was requested, which database identity executed it, and what happened as a result. Write operations and other higher impact actions deserve stronger controls than ordinary inspection or read queries.</p><p>Agent Memory introduces another boundary. Memory can help an application retain useful context, but it should not become a second source of truth for information that already belongs in an authoritative system. As the amount of memory grows, retrieval quality also becomes part of the application contract. Returning confident but irrelevant context is still a failure, even if the underlying retrieval system technically worked.</p><p>The goal is simple: let developers choose the client experience that works for them while keeping the database access model understandable, controlled, and consistent underneath.</p><h3>A developer friendly rollout path</h3><p>The easiest way to make an agent workflow hard to debug is to enable too much at once. A better path is to grow capability only after the previous layer has a clear acceptance check.</p><ol><li>Prove the runtime. Start SQLcl in MCP mode locally and confirm the expected client can discover the SQLcl tools.</li><li>Prove the connection. Use one approved saved SQLcl connection and run a bounded read query against the expected schema.</li><li>Prove the permission boundary. Confirm that allowed queries succeed and that disallowed operations fail because of the database role or configured restrictions.</li><li>Prove observability. Check the available database side activity trail and document what the team will inspect when a workflow fails.</li><li>Only then add durable memory. Store a small set of useful records, retrieve them in a second session, and verify identity and scope.</li><li>Add semantic retrieval when exact filters are no longer enough. Evaluate retrieval quality with representative developer questions, not only a single happy path.</li><li>Expand tool or write access last. Treat each new capability as a change to the operational surface, not as a prompt tweak.</li></ol><h3>Which workflow should you start with?</h3><p>There is no universal “best” client in this series. The better question is which developer loop you already use and what you need to validate next. Use the table below as the quick route into the detailed articles and notebooks.</p><figure><img alt="Table with columns Your goal, Start here, What this path emphasizes, and Next hands on step. Rows route terminal-oriented coding to Codex, editor-native work to Cursor, portability testing to Antigravity, assistant memory to Claude plus Agent Memory, and architecture learning to this guide." src="https://cdn-images-1.medium.com/max/1024/1*Sxvu_EuVaPxAl2RqWGZHWg.png" /><figcaption>A decision guide maps developer goals to a starting client and hands-on steps for the Oracle SQLcl MCP and AI agent memory paths.</figcaption></figure><h3>The recommended path through the series</h3><p>If you are discovering the series from this article, do not try to reproduce every client. Pick the environment that matches your work, prove the shared SQLcl MCP path once, and then add complexity only when the use case demands it.</p><figure><img alt="A left-to-right sequence: read the guide and choose a path; pick Codex, Cursor, or Antigravity; run the hands-on notebook; verify safely with bounded database tasks; then add Agent Memory when continuity matters." src="https://cdn-images-1.medium.com/max/1024/1*Q4h6CY2zsSJIG5ErdMpFzw.png" /><figcaption>A simple path through the Oracle MCP series</figcaption></figure><p>The order matters because each step narrows the next problem. If SQLcl MCP cannot start, memory is irrelevant. If the connection is wrong, retrieval is irrelevant. If the database identity is too broad, a better prompt will not fix the permission model. When the lower layers are proven first, the remaining failures become easier to diagnose.</p><h3>What I would carry from demo to production</h3><p>The articles in this series are developer examples, but the architectural lessons are broader. Production readiness comes from making the workflow explainable when everything is not going well. A team should be able to answer simple questions quickly: what did the client ask for, which tool ran, which database identity executed it, what result came back, what memory was retrieved, and why a sensitive action was allowed or blocked.</p><p>That means keeping environment boundaries clear, reviewing tool surfaces, and treating write access as a deliberate capability. Failure paths matter too. A denied query can show that least privilege is working. An empty retrieval result can be safer than inventing context. A client that cannot see an unapproved tool may be behaving exactly as intended.</p><p>Memory needs its own lifecycle decisions. What should be remembered, for how long, and under which identity? Can stale memory be corrected or removed? What should remain in an authoritative system instead of becoming memory?</p><p>Keep the architecture boring where possible. A controlled connection, a small tool surface, clear logs, scoped memory, and strong validation checks are usually more useful than adding frameworks before the team understands the failure modes.</p><h3>The big idea: Give AI the Right Path to the Database</h3><p>AI coding assistants are good at reasoning over the context they receive. The quality of a database workflow therefore depends heavily on how that context is obtained. Copying schema fragments into a prompt is useful for small examples, but it does not scale as a substitute for current, permission aware, inspectable access to the environment.</p><p>The Oracle SQLcl MCP server gives developers a practical way to make database access explicit. Oracle AI Database keeps execution, security, durable records, and retrieval close to the data layer. Oracle AI Agent Memory adds continuity when the application needs it. Codex, Cursor, Antigravity, and Claude can then sit on top as different developer experiences without forcing a different database architecture every time.</p><p>That is the real theme of this series. Do not ask the assistant to memorize the database in a prompt. Give it the right tool boundary, let Oracle Database enforce the rules, verify what happened, and add durable memory only when the workflow has something worth carrying forward.</p><h3>Frequently asked questions</h3><p><strong>Does SQLcl MCP tool discovery mean every operation is allowed?</strong> <br>No. Tool discovery and operation control are different concerns. A client may discover available MCP tools, but SQLcl MCP configuration, SQLcl command restrictions, and Oracle Database privileges still determine what can actually run. <br> <br><strong>Is MCP the same thing as durable agent memory?</strong> <br>No. MCP gives an AI client a way to call tools. Oracle AI Agent Memory is a separate application layer for durable context, scoped retrieval, threads, and memory records backed by Oracle AI Database. <br> <br><strong>Do the notebooks show live client telemetry?</strong> <br>No. The notebooks are validation harnesses. They help prove prerequisites, storage, retrieval, memory setup, and database patterns. Live client behavior still needs to be validated in Codex, Cursor, Antigravity, or Claude with the configured SQLcl MCP workflow. <br> <br><strong>Which versions should I use?</strong> <br>Use the versions and prerequisites listed in each hands-on article and companion notebook. Before running the workflow, confirm the current supported versions for SQLcl, Java, the chosen client, and the Oracle AI Agent Memory package.</p><p><strong>Can I use the same Oracle SQLcl MCP server pattern across different AI clients?</strong><br>Yes. That is the point of the shared foundation in this series. Codex, Cursor, Antigravity, and Claude can provide different developer experiences, while the database boundary stays consistent through approved SQLcl connections, configured tools, database privileges, and validation checks. The client can change without requiring a completely new database access model.</p><p><strong>When should I add Oracle AI Agent Memory to an MCP workflow?</strong><br>Add Oracle AI Agent Memory when the workflow needs continuity beyond the current interaction. Good examples include remembering user preferences, carrying useful project context into a later session, retrieving prior tool outcomes, or assembling scoped context for a recurring workflow. Do not add memory only because MCP is present. First prove the Oracle SQLcl MCP server path, then add memory when there is something worth carrying forward.</p><h3>References</h3><ul><li><a href="https://blogs.oracle.com/developers/how-to-build-a-controlled-mcp-workflow-for-codex-and-oracle-ai-database">How to Build a Controlled MCP Workflow for Codex and Oracle AI Database</a></li><li><a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/codex_mcp_oracle_ai_database_memory_langchain.ipynb">End-to-end Companion Codex notebook</a></li><li><a href="https://blogs.oracle.com/developers/build-a-controlled-cursor-mcp-workflow-with-oracle-ai-database">Build a Controlled Cursor MCP Workflow with Oracle AI Database</a></li><li><a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/cursor_mcp_oracle_ai_database.ipynb">End-to-end Companion Cursor notebook</a></li><li><a href="https://blogs.oracle.com/developers/how-to-build-an-antigravity-workflow-with-the-oracle-sqlcl-mcp-server-and-oracle-ai-database">How to Build an Antigravity Workflow with the Oracle SQLcl MCP Server and Oracle AI Database</a></li><li><a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/antigravity_mcp_oracle_ai_database.ipynb">End-to-end Companion Antigravity notebook</a></li><li><a href="https://blogs.oracle.com/developers/when-to-use-claude-memory-oracle-ai-agent-memory-and-langchain-together">When to use Claude memory, Oracle AI Agent Memory, and LangChain together</a></li><li><a href="https://github.com/oracle-devrel/oracle-ai-developer-hub/blob/main/notebooks/claude_mcp_oracle_ai_database_memory_langchain.ipynb">End-to-end Companion Claude notebook</a></li><li><a href="https://docs.oracle.com/en/database/oracle/agent-memory/26.4/">Oracle AI Agent Memory documentation</a></li><li><a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/">Oracle Database 26ai documentation</a></li><li><a href="https://pypi.org/project/oracleagentmemory/">oracleagentmemory on PyPI</a></li><li><a href="https://docs.oracle.com/en/database/oracle/sql-developer-command-line/25.2/sqcug/sqlcl-mcp-server.html">SQLcl MCP Server</a></li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=3c7c6bc079c8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/oracledevs/stop-making-ai-guess-your-database-oracle-sqlcl-mcp-server-agent-memory-and-oracle-ai-database-3c7c6bc079c8">Stop Making AI Guess Your Database: Oracle SQLcl MCP Server, Agent Memory, and Oracle AI Database</a> was originally published in <a href="https://medium.com/oracledevs">Oracle Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[New ODP.NET Releases for AI Agent Development and AI Skills]]></title>
            <link>https://medium.com/oracledevs/new-odp-net-releases-for-ai-agent-development-and-ai-skills-75cf9c6e4946?source=rss----749dcac244ef---4</link>
            <guid isPermaLink="false">https://medium.com/p/75cf9c6e4946</guid>
            <category><![CDATA[deep-data-security]]></category>
            <category><![CDATA[microsoft-agent-framework]]></category>
            <category><![CDATA[odpnet]]></category>
            <category><![CDATA[oracle-ai-database]]></category>
            <category><![CDATA[ai-skills]]></category>
            <dc:creator><![CDATA[Alex Keh]]></dc:creator>
            <pubDate>Tue, 20 Oct 2026 16:06:58 GMT</pubDate>
            <atom:updated>2026-10-20T18:32:45.363Z</atom:updated>
            <content:encoded><![CDATA[<h4>Introducing the Oracle AI Database Vector Store Connector, Oracle .NET AI Skills, and new Deep Data Security features</h4><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*CLqcCzjqQUtcf4x0ji-m0A.png" /></figure><h3>Key Takeaways</h3><ul><li>ODP.NET 26ai (23.26.3) and the Oracle AI Database Vector Store Connector are now available for .NET developers.</li><li>The connector lets applications store, search, update, and delete vectors in Oracle AI Database and supports Microsoft Agent Framework.</li><li>Expanded Oracle Deep Data Security features allow applications to manage access for human users and AI agents through end-user names and lookup keys.</li><li>Oracle .NET AI skills provide reusable guidance that helps coding agents create ODP.NET applications using Oracle best practices.</li></ul><p>I’m excited to announce ODP.NET 26ai (23.26.3) and the production release of the Oracle AI Database Vector Store Connector, expanding support for AI agent development.</p><p>These releases add new AI agent, skills, and security capabilities for Oracle .NET developers. With the Oracle AI Database Vector Store Connector, .NET applications can search, create, read, update, and delete vectors directly in Oracle AI Database.</p><p>The connector also integrates with Microsoft AI technologies, including Microsoft Agent Framework, to support AI agents, large language model (LLM) applications, Model Context Protocol (MCP) solutions, and workflows.</p><p>ODP.NET 23.26.3 extends Oracle Deep Data Security support for agentic AI by adding end-user names and lookup keys to security contexts. These additions give developers more flexible ways to create and manage security contexts for both users and AI agents.</p><p>Finally, we’ve released <a href="https://github.com/oracle/skills/blob/main/db/appdev/dotnet-oracle.md">Oracle .NET AI developer skills</a> to help AI coding agents build robust ODP.NET applications faster while following Oracle best practices.</p><p>Oracle AI Database and ODP.NET offer a broad set of capabilities for C# data applications, including performance, high availability, security, data management, scalability, and AI. The challenge is that developers need time to learn these capabilities and apply the right practices.</p><p>Oracle AI skills help agents apply that guidance directly when generating application code, improving developer productivity while helping teams avoid common implementation pitfalls.</p><p>Let’s take a closer look at these new capabilities and the benefits they provide.</p><h3>Oracle AI Database Vector Store Connector</h3><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*KJgT8__OuNiflZ6Yy8o70w.png" /><figcaption>Oracle Vector Store Connector integrates Microsoft Agent Framework and Oracle AI Database</figcaption></figure><p>The Oracle AI Database Vector Store Connector for .NET is now generally available in the Oracle.VectorData NuGet package. It provides out-of-the-box support for standard .NET vector operations, so existing C# AI applications can use Oracle AI Database to store and retrieve vectors with minimal code changes.</p><p>The connector also exposes Oracle AI Database 26ai capabilities beyond basic vector storage, including multiple vector distance functions and vector index types that most databases don’t offer. This gives .NET developers access to a broader set of vector search options directly from their applications.</p><p>The new Oracle connector also supports Microsoft Agent Framework, an SDK and runtime for building, orchestrating, and deploying multi-agent AI systems. It combines simple agent abstractions with enterprise-ready capabilities.</p><p>Microsoft Agent Framework applications can use Oracle AI Database as a vector store for retrieval-augmented generation. Because the framework uses the same vector store libraries as .NET, existing connectors such as Oracle’s can integrate with little or no additional customization.</p><p>To learn more about the Oracle AI Database Vector Store Connector, explore the documentation or try the sample applications.</p><ul><li><a href="https://learn.microsoft.com/en-us/semantic-kernel/concepts/vector-store-connectors/out-of-the-box-connectors/oracle-connector?pivots=programming-language-csharp">Documentation: Using the Oracle AI Database Vector Store Connector</a></li><li><a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/odpnt/VSConnector4SKClasses.html#GUID-6E3FFDD3-17B3-42E1-BA14-902E011C6FDA">Documentation: Oracle AI Database Vector Store Connector Classes</a></li><li><a href="https://github.com/oracle/dotnet-db-samples/tree/master/samples/ai-vector">Samples: AI </a><a href="https://github.com/oracle/dotnet-db-samples/tree/master/samples/ai-vector">Agent Framework Chat App </a><a href="https://github.com/oracle/dotnet-db-samples/tree/master/samples/ai-vector">and AI Vector Hotel Search App</a></li></ul><p>The connector supports both .NET and .NET Framework and uses ODP.NET for database access.</p><p>While the connector supports Oracle Database 19c and later, I recommend Oracle AI Database 26ai for its broader vector search and AI agent capabilities, with additional features delivered in quarterly releases. One of its most important capabilities is Deep Data Security, designed to help secure applications for the agentic AI era.</p><p>That brings us to the second major Oracle .NET enhancement in this release: expanded Deep Data Security integration.</p><h3>ODP.NET Deep Data Security End-user Names and Lookup Keys</h3><figure><img alt="Oracle Deep Data Security is a database-enforced data authorization framework for developers and security architects to define and enforce application-level security requirements for all applications." src="https://cdn-images-1.medium.com/max/1024/1*lbchAmJppWLK_SnXyqeXSQ.avif" /><figcaption>Oracle Deep Data Security is a database-enforced data authorization framework for developers and security architects to define and enforce application-level security requirements for all applications.</figcaption></figure><p>Agentic AI can perform complex tasks, but it often requires broad, dynamic access to data, increasing security and compliance risk. Enforcing authorization in the database reduces duplicated application logic and keeps access rules consistent across applications, agents, and tools.</p><p>Oracle Deep Data Security provides unified, database-native policy enforcement for both conventional and AI applications. ODP.NET 23.26.2 first added support for passing Deep Data Security end-user security contexts from .NET applications. I covered this integration in more detail in a <a href="https://medium.com/oracledevs/database-enforced-authorization-for-agentic-ai-net-applications-b383408453f6">previous blog post</a>.</p><p>Deep Data Security plays a critical role in AI by helping prevent autonomous agents from exposing sensitive information or making unauthorized data changes. Enforcing security policies in the database provides a consistent control point instead of relying on every application to govern AI agents correctly and stay up to date on new practices.</p><p>In the vibe-coding era, AI can generate applications quickly, cheaply, and with extensive customization. That can produce many variations of the same application, some of which may be rarely updated. Centralized database security helps keep access policies consistent across all of them.</p><p>With so many applications, thorough code and security reviews may become less consistent. AI coding agents also may not understand an organization’s specific end-user authorization rules unless those rules are explicitly provided.</p><h4>Oracle Deep Data Security Usage Scenario</h4><p>Consider a human resources database. Employees should see only their own salary and, where appropriate, the salaries of their direct reports. Sensitive identifiers, such as Social Security numbers, should be restricted to the individual they belong to. Employees may be allowed to update their home address, but not their salary.</p><p>Building all of these business, security, and legal rules into every AI application is difficult to maintain. Centralizing authorization in the database helps prevent unintended data exposure and unauthorized changes across applications and agents.</p><p>That’s where Oracle Deep Data Security comes in. By enforcing security at the database level, organizations can simplify policy setup, maintenance, and auditing. Developers can also place less trust in individual applications, which actually makes it easier to build and deploy securely.</p><h4>ODP.NET Deep Data Security End-user Names and Lookup Keys</h4><p>In the latest release, ODP.NET Deep Data Security adds support for end-user names and lookup keys in end-user security contexts. This gives applications more flexible ways to manage user and AI agent access. An external application identity can now be mapped to a database session without requiring a corresponding account in an external identity and access management system, such as Microsoft Entra ID.</p><p>A Deep Data Security end-user name can represent a human or AI agent authenticated by the application, rather than a traditional database user account. The end-user name can be used in a security context instead of a user access token.</p><p>A lookup key gives Oracle Deep Data Security a unique identifier for an application-managed end user. It maps the authenticated identity to the appropriate database policies, data roles, and data grants, enabling fine-grained access enforcement.</p><p>The lookup key is automatically generated when an end-user security context is created with an end-user name. The .NET security context then contains the lookup key, database access token, and end-user name.</p><p>Here’s a C# example that creates the security context using an end-user name. You can see the context created with the CreateWithTokenAndName method and end-user parameter.</p><pre>/* Setup an end user for the security context in lieu of a user access token.<br>* Create the user:<br>  CREATE END USER deepsec_end_user;<br>* Create data grant to give limited HR schema access to the end user:<br>  CREATE DATA GRANT enduser_grant AS SELECT (ALL COLUMNS EXCEPT salary) ON hr.employees TO deepsec_end_user;<br>*/<br><br>// Read database token from file. <br>string midtierToken = GetMidtierToken(); <br>// Create security context with deepsec_end_user.<br>OracleEndUserSecurityContext securityCtx = OracleEndUserSecurityContext.CreateWithTokenAndName(midtierToken, &quot;deepsec_end_user&quot;); <br><br>// Create and open connection. Deep Data Security requires a TCPS connection. <br>// low_privilege_user only has connection privileges. <br>OracleConnection conn = new OracleConnection(&quot;user id=low_privilege_user;password=&lt;PASSWORD&gt;;data source=oracle&quot;);<br>conn.Open(); <br>// Set security context on connection <br>conn.SetEndUserSecurityContext(securityCtx); <br><br>// Execute SQL <br>using (var command = new OracleCommand(&quot;SELECT FIRST_NAME, SALARY from hr.employees&quot;, conn)) <br>{<br>  using (var reader = command.ExecuteReader()) <br>  {<br>    while (reader.Read()) <br>    {<br>      // Deepsec_end_user has access to employee first names but not salaries based on data grant.  <br>      // Salary data will return as null values.<br>      Console.WriteLine(&quot;Name: &quot; + (reader.IsDBNull(0) ? &quot;&lt;NULL&gt;&quot; : reader.GetString(0)));<br>      Console.WriteLine(&quot;Salary: &quot; + (reader.IsDBNull(1) ? &quot;&lt;NULL&gt;&quot; : reader.GetString(1)));<br>    }<br>  }<br>}</pre><p>Managed ODP.NET and ODP.NET Core 23.26.3 both support Oracle Deep Data Security end-user names and lookup keys.</p><p>Learn more in the <a href="https://docs.oracle.com/en/database/oracle/oracle-database/26/odpnt/featConnecting.html#ODPNT-GUID-5A35E30D-8252-4ED1-9903-80C28E9DE011">ODP.NET Deep Data Security documentation</a>.</p><h3>Oracle .NET AI Skills for More Productivity</h3><p>Perhaps the biggest gain in developer efficiency comes from AI skills. These reusable Markdown files contain instructions, knowledge, and workflows that extend AI agents for specific tasks or domains. Our focus here is ODP.NET and Oracle AI Database development and optimization.</p><p>My team has published an <a href="https://github.com/oracle/skills/blob/main/db/appdev/dotnet-oracle.md">Oracle .NET AI skills file</a> for use with AI tools and coding assistants. You can use it as is or adapt it to your project. It works with new or existing managed ODP.NET, ODP.NET Core, and Oracle EF Core applications.</p><p>We plan to continue expanding the Oracle .NET AI skills over time.</p><p>These Oracle .NET AI skills are part of the <a href="https://github.com/oracle/skills">Oracle AI Skills repository</a>, which covers a broader range of Oracle products and services, including Oracle AI Database. The repository also includes an <a href="https://github.com/oracle/skills/blob/main/db/SKILL.md">Oracle AI Database skills file</a> for improving database code and design, including PL/SQL, SQL, and security.</p><p>You can use individual skill files to focus on specific areas, such as performance or high availability, or combine them to improve your application more holistically.</p><h3>Get Started with New AI Features</h3><p>You can start building AI applications with ODP.NET 23.26.3 and the Oracle AI Database Vector Store Connector today. Download them from NuGet Gallery:</p><ul><li><a href="https://www.nuget.org/packages/Oracle.VectorData/">Oracle AI Database Vector Store Connector</a></li><li><a href="https://www.nuget.org/packages/Oracle.ManagedDataAccess.Core">ODP.NET Core</a></li><li><a href="https://www.nuget.org/packages/Oracle.ManagedDataAccess">Managed ODP.NET</a></li></ul><p>You can also download the Oracle .NET AI skills from GitHub:</p><ul><li><a href="https://github.com/oracle/skills/blob/main/db/appdev/dotnet-oracle.md">Oracle .NET AI skills file</a></li></ul><p>Give them a try to accelerate Oracle .NET AI development and build better applications with less effort.</p><p>#OracleDotNet</p><h3>FAQs</h3><ul><li><strong>What is the Oracle AI Database Vector Store Connector?</strong><br> It is a .NET connector that lets applications use Oracle AI Database to store and retrieve vector data with minimal code changes. It’s available in production.</li><li><strong>Which AI technologies does the connector support?</strong><br> It works with .NET vector libraries and Microsoft Agent Framework for AI agents, LLM applications, MCP solutions, and related workflows.</li><li><strong>How does Deep Data Security help AI applications?</strong><br> It enforces authorization in the database, helping prevent agents and applications from accessing sensitive data or making unauthorized changes.</li><li><strong>What are Oracle .NET AI skills?</strong><br> They are reusable instruction files that help AI coding assistants build and optimize ODP.NET, ODP.NET Core, and Oracle EF Core applications.</li></ul><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=75cf9c6e4946" width="1" height="1" alt=""><hr><p><a href="https://medium.com/oracledevs/new-odp-net-releases-for-ai-agent-development-and-ai-skills-75cf9c6e4946">New ODP.NET Releases for AI Agent Development and AI Skills</a> was originally published in <a href="https://medium.com/oracledevs">Oracle Developers</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>