You are on page 1of 34
‘aioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject 009) CODE PROJECT The Oracle Call Interface (OCI) and ODPNet - Part 1 aR Espen Hartinn Proven techniques for fast Oracle Database access using NET 50 and native C++ This article demonstrates efficient techniques for fast Oracle Database access using both ODP.NET with .NET 5.0 and the Oracle Call Interface (OCD from native C++ Download the updated source code for this article (14"" of March, 2021) - 1.9 MB Introduction ‘The ability to efficiently create, retrieve, update and delete data in 2 database from a client or server application is often the most performance ctitical part of a solution, and according to 03-Engines.com, the Oracle Database is the most popular database server inthe world, ‘The Oracle Database server is widely used by companies in the: + Flectric utility industry + Oil and gas industry ‘© Finance and investment industry | believe the primary reasons for Oracle's popularity are: + Reliability + Capacity + Flexibility + Performance ‘The first three points are something you get out of the box, while the fourth is something that can prove to be somewhat elusive. Imagine that your task is to create a real-time analytics application for the stock market, and that the traders will make decisions ‘that will make or break the company based on the timely results provided by your application. ‘The total number of trades on Nasdaq for December the 1" was 30 910 548, and most of those trades where probably made in the hour after the market opened or in the haur before closing time. Large volumes of trades are probably made in bursts of activity, so this is something your solution is required to handle gracefully too. Atypical NET application, inserting one row at the time, can insert about 3 500 rows per second, which is far less than what is requires from your solution. While getting the data into the database is quite important, itis the algorithms that will be applied to ‘the data that is the real purpose of your solution. Anything that goes into the database will be read thousands of times ~ and ‘everything must be executed as fast as possible to have a practical value for the trading desk. This article will demonstrate how to insert more than 1 200 000 rows per second into an Oracle database, and how to read more ‘than 3 600 000 rows per second — without any changes to the database. How you communicate with the Oracle database really ‘matters when it comes to performance. | will explain how to se the Oracle Call Interface to + perform basic create, retrieve, update, and delete (CRUD) operations with optimistic locking, + bind data to variables in SQL statements, + pass input data efficiently, Iitps imu codeproject.comvAricles!5285225/The-Oracle-CallIntertace-OC-and-ODP-Net-Part-isplay=Print 1108 saioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject + process query results efficiently, + insert 100 000 000 rows into a table using a single database call, and + update 100 000 000 rows ina table using a single database call ‘The article also demonstrates how ODP.Net can be used with NET 5.0 to perform many of the same operations. ODP.Net can be Used to implement almost anything that can be implemented using OCI, but OCI always outperforms ODP Net. Performance can sometimes be crucial to the success of an application, and for cloud-based deployment, it has a huge impact on the cost of day-to- day operations. The article will explain how to use ODP.Net to + perform basic create, retrieve, update, and delete (CRUD) operations with optimistic locking, + bind data to variables in SQL statements, ‘pass input data efficiently, + insert 1.000 000 rows into a table using a single database call, and ‘update 1 000 000 rows in a table using a single database call “The Oracle Call Interface (OCI) is an API for creating applications in C or C+ + that interact with the Oracle database, By using the (OCI API, we get access to the full range of database operations that are possible with Oracle Database, including SQL statement processing and object manipulation ‘The Oracle Call Interface provides significant advantages over other methods for accessing an Oracle Database: ‘More fine-grained control ever all aspects of application design and program execution Faster connection pooling, session pooling, and statement caching that enable development of cost-effective and highly scalable applications + Executes dynamic SQL more efficiently ‘+ Dynamic binding and defining using callbacks functions tensive description functionality to expose the layers of server metadata ‘Efficient asynchronous event notification for registered client applications hanced array data manipulation language (OML) capability for array inserts, updates, and deletes ‘+ Optimization of queries using transparent prefetch buffers to reduce roundtrips ‘+ Ability to associate commit requests with executes to reduce server roundtrips ‘= Data type mapping and manipulation functions, for manipulating data attributes of Oracle types * Data loading functions, for loading data directly into the database without using SQL statements, ternal procedure functions, for writing C callbacks from PL/SQL. According to Oracle: race Call Interface (OC) isthe comprehensive, high performance, native C language interface to Oracle Database for custom or packaged applications OCIis highly reliable, Oracle tools such as SQL*Plus, Real Application Testing (RAT), SQL*Loader, and Data-Pump all use OCI (OCI provides the foundation on which other tanguage-specifc interfaces such as Oracle JOBC-OCI, Oracle Data Provider for Net (ODP.Net), Oracle Precompilers, Oracle ODBC, and Oracle C++ Call Interface (OCC) drivers are built. OCI is also used by leading scripting language drivers such as node-oracledb for Nodejs, PHP OCI8, ruby-oci8, Perl DBD: Oracle, Python © Oracle, and the statistical programming language R's ROracle driver. ‘To make it easier to work with the Oracle Call Interface (OCH API, [have written the HarLinn .OCI C++ library which is packaged asa windows DLL Building the Code Instructions for building the code are provided in Build.md located in the $(SolutionDir)Readme folder. Harlinn.OCl The primary advantage of using the Oracle Call Interface and C++ is performance. This advantage is not something you will get just because you are using C+ + and OCI, itis something you may achieve through a well-reasoned design. With the Harlinn.OCI library, Itry to achieve two goals: 1. Ease of use e-grained control of how data is exchanged with the Oracle RDBMS through the Oracle Call Interface. This can make a huge difference in the performance of your OCI client application, hitps:slwwn.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print 2188 saioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject Ease of Use Using C# and ODP.Net, we can easily connect to an Oracle RDBMS: static void Main(string[] args) { var connectionstring = GetConnectionstring(args); var connection = new OracleConnection(connectionString) 5 using (connection) { connection.open(); using (var command = connection.CreateConmand()) { conmand.CommandText = "SELECT * FROM ALL_USERS' using (var reader = conmand.ExecuteReader()) while (reader.Read()) t var userName = reader.Getstring(@); Console.Out-WriteLine(userName) ; + } + > y Harlin .OCT provides an API that you will ind as easy to use as ODP.Net EnvironmentOptions options; Environment environment( options ); auto server = environment .CreateServer( ); auto serviceContext = server.CreateServicecontext( Username, Password, Alias ); serviceContext .SessionBegin( ); std::wstring sql = L"SELECT * FROM ALL_USERS"; auto statement = servicecontext.Createstatement( sql ); auto reader = statement. ExecuteReader( )5 while ( reader->Read( ) ) i auto userName = reader->Ascstd: :wstring>( @ )5 auto userId = reader->As( 1 )3 auto created = reader-rAscDateTime>( 2); y serviceContext.Sessionénd( ); Calling a server-side function: wstring sql = EGIN"\ sresult END3"3 SYSTIMESTAMP() ;" \ l u u auto statement = serviceContext.Createstatement( sql ); auto result = statement.BindcDateTime>( 1 ); statenent.Execute( 1); auto dateTime = result->As( )5 or inserting a rove hitps:slwwn.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print ssa saioarz021 “The Oracle Cal Interface (OCI) and ODPNNet- Parl 1 - CodeProject std: soptional description; if ( ownedObjectType.Description( ).length( ) ) { } description = ownedobjectType.Description( ); constexpr wchar_t SQL[] = L"INSERT INTO OwnedObjectType(Id, Name, OptimisticLock, * L"created, Description) "\ LY'VALUES(:1, 52,0, 23, 24)"5 static std::wstring sql( SL )3 auto statement = serviceContext_.createStatenent( sal, owned0bjectType.1d( ), ownedob JectType.Nane( ), ownedobjJectType.Ccreated( ), description); statenent.Execute( ); are all operations that can be easily implemented with the library. HarLinn. OCT implements a thin, yet feature rich, layer around the OCI API Harlinn.OCI depends on the Har'Linn .Common .Core library, for implementations of basic datatypes such as Gui, DateTime and TimeSpan. Create, Retrieve, Update and Delete (CRUD) Basic CRUD is the heart and soul of many applications, and this a simple, yet typical, table: CREATE TABLE SimpleTest1 ( Id NUMBER(19) NOT NULL, OptimisticLock NUMBER(19) DEFAULT @ NOT NULL, Name NVARCHAR2(128) NOT NULL, Description NVARCHAR2(1024),, CONSTRAINT PK_SimpleTest1 PRIMARY KEY(Id), CONSTRAINT UNQ_SimpleTest1 UNIQUE (Name) 4 Description: 1.The Td column is the primary key forthe table, mandating that a unique value must be stored for each row inthe table. 2. The OptimisticLock column is used to implement optimistic locking, a widely used technique for guarding against inadvertent overwrites by multiple concurrent users. If an application can do the following: a. User retrieves a row of data b, User2 retrieves the same row of data User2 updates column value and updates the row in the database. d.User1 updates the same column value and updates the row in the database, overwriting the change made by User2. then this is almost always a design bug. 1. The Name column provides an alternative key to the rous in the table. 2.The Description column holds data that is of interest to the solution that uses the database to manage is data ‘A numeric primary key is often generated using an Oracle database sequence object: CREATE SEQUENCE SimpleTestiSea; ‘This assures that unique keys will be created for concurrent inserts by multiple database client applications. hitps:slwwn.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print 4134 ‘wos “he Orde Cal nerfce (OC and ODF Ne -Par 1 - CodeProject Optimistic Locking Neatly allsoftware solutions that use a database server to store data must be able to handle multiple concurrent sessions. At any point in time, you can expect multiple processes or users to retrieve and update the database, Since multiple processes or users are Updating the information in the database itis only a matter of time before two separate processes or users wil try to update the same piece of data concurrently Optimistic Locking is @ minimalistic strategy for preventing unintentional updates to a row based on a version number stored in one of the columns of the row. When the software attempts to update or delete a rou, its filtered on the version number to make sure the row has not been updated between time the row was retrieved from the database, and the update or delete, Updates must ensure that changes to the column for the version number are atomic. Its called optimistic locking because it assumes that most updates and deletes will succeed; and when they do not, the software ‘must be able to handle this appropriately How to correctly handle situations where the optimistic lock prevents an update, or a delete, depends on the use-case. An interactive solution may retrieve the updated row from the database and let the user decide whether to overwrite it or abandon her modifications, while an automated system may implement a more complex solution, or store the rejected update elsewhere for ‘manual intervention. The important thing is to maintain consistency while making sure that information is not inadvertently lot. Optimistic locking is particularly useful for high-volume solutions; and web, and other multtier-tier architectures where the software is unable to maintain a dedicated connection to the database on behalf of the user. In these situations, the dlient cannot maintain database locks as the connections are taken from a pool and the client may not be using the same connection from one server request to the next. The main downside of optimistic locking is that it is row oriented, and many real-world solutions requires synchronization that goes beyond a single row. Even when you end up using a more powerful lock management solution, optimistic locking will almost certainly help to uncover programming errors during development, deployment, and operation, ‘The alternative to optimistic locking in called pessimistic locking, itis net last write wins, Pessimistic locking requires an active system component that maintains the locks, such as table or row level locking implemented by the database server, or a dedicated distributed lock server. Database lacks are usually ted to the database session but can also be under control of a distributed transaction manager ‘The Oracle database provides the DBMS_LOCK package, which can be used to implement pessimistic locking, The maximum lifetime of a lock is limited to the lifetime of the session used to create it Basic CRUD using ODP.Net Implementing basic CRUD using ODP.Net isa straightforward process, and the code illustrates how to implement optimistic locking Using atomic operations in plain DML. The full code is located under DotNet\éxamples\ODP\Harlinn. Examples ODP Basics01. Create ‘To insert a new record into the database, we must perform the following steps: + Create an OracleCommand object. + Assign an INSERT data manipulation language (DMA) statement with variable placeholders to the CommandText propery of the OracleCommand object. + Create and Bind OracleParameter objects to each of the variable placeholders. + Callthe ExecuteNonQuery() member function of the OracleCommand object to execute the DML statement on the database server, ‘The two first placeholders 1 and +2 are created for the Name and Description column respectively; while the third placeholder is for the value of the Td column that will be generated by the call to SimpleTest1Seq .NextVal inside the DML as specified by the traling RETURNING Id INTO :3'.the OptimisticLock column gets assigned @ marking this as the initial version of the row: public SinpleTestibata Insert(string name, string description = null) { hitps slaw. codeproject.com/Antcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print 5138 ‘aioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject using (var command = _connection.CreateConmand()) t conmand.CommandText = "INSERT INTO SinpleTest1. (1d, OptimisticLock, Name, Description ) + "VALUES (SinpleTest1Seq.NextVal,®,:1,:2) RETURNING Id INTO OracleParameter nameParameter = new OracleParaneter(); naneParameter.OracleDbType = OracleDbType.NVarchar2} naneParameter.Value = name; OracleParaneter descriptionParameter = new OracleParameter(); descriptionParaneter.OracleDbType = OracleDoType.NVarchar2; descriptionParameter.Value = string.IsNullOrkhiteSpace(description) ? DBNuL1.Value : description; oracleParameter idParameter = new OracleParaneter(); idParaneter.OracleDbType = OracleDbType. Int64; conmand. Parameters Add(nameParameter) ; conmand. Parameters. Add(descriptionParameter); conmand. Parameters .Add(idParameter) ; conmand. ExecuteNonQuery() ; var idvalue = (OracleDecimal)idParaneter.Value; var result = new SimpleTestipata(idValue.ToInt64(), name, description ); return result; Retrieve ‘The steps for preparing an OracLeCommand object for retrieving the data for a particular row is nearly identical to those for we: used to insert a new record, except that this time, we must + assign a structured query language (SQU statement with a single variable placeholder to the CommandText property of the OracleConmand object + bind the value for the :1 placeholder in the WHERE clause to the value of the Id column for the row we are looking for. + call ExecuteReader(...) on the OracleCommand object to execute the query on the database server, The query results are made available tothe client application through the OracleDataReader object returned by ExecuteReader(..) Since we already know the value of the Id column, we only ask for the OptimisticLock, Name and Description columns. The value of the Td column is guaranteed to uniquely identity a single row in the SimpleTest1 table, so we only execute reader .Read() once, and ifit returns true, we know that the query was able to locate the requested row. The OptimisticLock and Name columns cannot be NULL, while the Description column can —which we check by calling TSDBNU1L. Each column for the current row is identified by its 0 based offset into the selects: public SinpleTestipata Select(long id) « using (var command = _connection.CreateConmand()) { conmand.CommandText = "SELECT OptimisticLock, Name, Description FROM SimpleTest1 "+ “WHERE Id = 11"; OracleParaneter idParameter = new OracleParaneter(); idParaneter.OraclebbType = OraclebbType. Int64; idParaneter.Value = id conmand. Parameters .Add(idParameter) ; using (var reader = conmand. ExecuteReader (System.Data. ConmandBehavior.SingleRow)) { if (reader.Read()) { hitps:slww.codeproject.com/Artcles/5286225/The-Oracle-Cal ntrface-OCl-and-ODP-Net-Part-1leplay=Print 138 ‘aioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject var optimisticlock = reader.GetInted(@); var name = reader.GetString(1); string description = null; Af (reader. TsDBNUIL(2) == False) t description = reader.etstring(2); > Var result = new SimpleTestiData(id, optimisticlock, name, description); return result; x aise « retuen null; + > ,? Update Again, we use an OracLeCommand object, assigning an UPDATE DML statement to the CommandText property. Ths time, the variable placeholders are only for input variables. The row is not updated unless the Id column matches the value bound to the third variable placeholder and the OptimisticLock column matches the value bound to the fourth variable placeholder. This will prevent the DML from updating the row ifit has been updated by another user or process; and as aside effect, we also know that the next value for the OptimisticLock column, after a successful update wll be the previous value incremented by 1 ‘The DML statement can at most update a single row in the SimpleTest1 table and since ExecuteNonQuery() returns the number of rows that was altered by the DML statement, we can safely assume that a return value greater than 0 means that the Update succeeded, while 0 means that either the row is deleted or the value for the Opt imi sticLock column has been changed by another update: public bool Update(SimpleTestiData data) { using (var command = _connection.CreateConmand()) t conmand.ConmandText = "UPDATE SimpleTest1 "+ “SET OptimisticLock=OptimisticLock+1, Name: "WHERE Id=:3 AND OptimisticLock=:4' , Description=:2 “+ var id = data. Id; var optimisticlock = data.optimisticLock; var name = data.Name; var description = data.Description; OracleParaneter nameParameter = new OracleParaneter(); naneParameter.OracleDbType = OracleDbType.NVarchar2; naneParameter.Value = name; OracleParaneter descriptionParaneter = new OracleParameter(); descriptionParaneter.OracleDbType = OracleDoType.NVarchar2; descriptionParameter.Value = string.IsNullOrkhiteSpace(description) ? DBNulL.Value : description; OracleParameter idParameter = new OracleParaneter(); idParaneter.OracleDbType = OracleDbType. Inté4; idParameter.Value = id; oracleParaneter optimisticLockParameter = new OracleParameter(); optimisticLockParameter..OracleDbType = OracleDbType. Int64; optimisticLockParameter.Value = optimisticLock; conmand. Parameters Add(nameParameter) ; conmand.Paraeters..Add(descriptionParaneter); conmand.Paraneters.Add( idParameter); hitps:slww.codeproject.com/Artcles/5286225/The-Oracle-Cal ntrface-OCl-and-ODP-Net-Part-1leplay=Print 7138 ‘aioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject conmand. Parameters .Add(optimisticLockParameter); if (conmand.ExecuteNonQuery() > @) ‘ data.Optimisticlock = optimisticock + 13 return true; + else { return false; y > y Delete ‘The logic behind the DELETE DML statement is nearly identical to the logic for updating a row: public bool Delete(SimpleTestiData data) { using (var command = _connection.CreateConmand()) { conmand.ConmandText = “DELETE FROM SinpleTesti " + "WHERE Id=:1 AND OptimisticLock=:2"; var id = data. Td; var optimisticLock = data.Optimisticlock; OracleParaneter idParaneter = new OracleParaneter(); idParameter.OracleDbType = OracleDbType. Int64; idParaneter.Value = id; OracleParameter optimisticockParaneter = new OracleParaneter(); optinisticLockParaneter.OraclebbType = OraclebbType. Int64; optimisticLockParaneter.Value = optimisticLock; conmand. Paraneters.Add(idParaneter) ; connand.Paraneters.Add(optimisticLockParameter); if (command. ExecuteNonQuery() > @) { return true; + else { return false; y , y A Simple Test ‘To put tall together, the example below creates a hundred rows in the database, retrieves the stored rows and verifies that they contain the expected data, Then it deletes a third of the rows, expecting each call to Delete(...) to return true — indicating a successful delete operation, before calling Delete (...) once more for the same objects. This time, expecting each call to return Fallse, indicating that the row was already deleted by the first iteration over the objects. public void Execute() { Clear(); var originaliters = new Dictionary( ); int RowCount = 100; using (var transaction = _connection.BeginTransaction()) { hitps:slwwu.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCland-ODP-Net-Part-1leplay=Print rsa ‘voar200t “The Orace Cal Interface (OCI and ODPNet- Part 1 - CodeProject for (int 4 = @} 4 < RowCount; ++i) { var name = $"Name(i + 1)"; var description = i % 2 == 0 ? null : $"Description{i + 1}"5 var data = Insert(name, description); originalTtens Add(data.Id, data); + ‘transaction.Conmit(); ? var databasertens = Select(); foreach (var entry in originalItens) { var originalIten = entry.Value; if (databaseltens..ContainsKey(originalIten.Id)) { var databasertem = databasertens[originalItem.Id]; Af (originalltem.Equals(databaseItem) == false) throw new Exception($"The original item {originaliten} "+ $"is not equal to the iten {databaseIten) "+ retrieved from the database."); } + else { throw new Exception($"Did not retrieve {originalItem} from "+ “the database."); + } using (var transaction = _connection.BeginTransaction()) foreach (var entry in originalItens) { var data = entry.Value; if (string-IsNul1OrWhitespace(data.Description)) { data.Description = “Updated Description"; if (Update(data) == false) var changedbata = Select (data. 1d); if (changedbata != null) throw new Exception($"Unable to update (data), the "+ $"row has been updated by another user {changedData}"); t else { throw new Exception($"Unable to update {data}, the $"row has been deleted by another user"); y y + } transaction. Commit(); ? int rowsTobeleteCount = RowCount/3; var itemsTobelete = originalItens.Values.Take(rowsToDeleteCount).ToList(); using (var transaction = _connection.BeginTransaction()) foreach (var item in itemsToelete) if (Delete(item) == false) ‘throw new Exception($ Inable to delete {item}, the row has hitps:slwwu.codeproject.com/Antcles/5286225/The-Oracle-Cal Interface-OCl-and-ODP-Net-Par-1?¢lsplay=Print ‘aioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject "been deleted by another user’ } + transaction. Commit(); d foreach (var item in itensTobelete) if ( Delete(item) ) { + throw new Exception(: Tt appears {item}, was not deleted"); Basic CRUD using Harlinn.OCl Implementing basic CRUD using Har Linn .OCT is als @ straightforward process, and the code again illustrates how to properly implement optimistic locking using atomic operations in plain DML ‘The full code is located under Examples\OCNHOCIBasics01. Create To insert a new record into the database, we must perform the following steps: * Create an OC. Statement object by calling CreateStatement(...) on the service context. The first argument is the INSERT data manipulation language (OMI) statement with variable placeholders for the Name, Description and finally the server generated value for the Id column. The second and third parameters are automatically hound to the two fir variable placeholders. + Bind the third variable placeholder to a 64-bit integer, that will receive the server generated primary key, using an Int64Bind object. + Callthe ExecuteNonQuery() member function of the OC! the database server, Statement object to execute the DML statement on ‘The two frst placeholders :1 and :2 are created for the Name and Description column, respectively; while the third placeholder is for the value of the Id column that will be generated by the call to SimpleTest1Seq .NextVal inside the DML as specified by the traling RETURNING Id INTO :3°. The OptimisticLock column gets assigned @ marking initial version of the row: isas the std: :unique_ptr Insert( const std: :wstring& name, const std::wstring description = std: :wstring( ) ) const { auto& serviceContext = ServiceContext( ); std: :optional descr if (description. size( ) ) { descr = description; > constexpr wehar_t sql[] = LSINSERT INTO SimpleTesti(Id, OptimisticLock, Name, Description ) “ L°VALUES(SimpleTest1Seq.NextVal,®,:1,:2) RETURNING Id INTO :3" auto statement = serviceContext.CreateStatement( sql, name, descr ); auto* idBind = statement.Bind( 3); auto transaction = serviceContext .SeginTransaction( ); statement ExecuteNonguery( ); transaction.Commit( ); auto id = idBind->Asintea( ); make_uniquecSinpleTestbata>( id, @, name, description ); + hitps:slwwu.codeproject.com/Antcles/5286225/The-Oracle-CalInterface-OCl-and-ODP-Net-Par-1¢leplay=Print 107s saioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject Retrieve ‘The steps for preparing an OCI ‘to insert a new record, except that statement object for retrieving the data for a particular row are nearly identical to those used is time, we must + pass the structured query language (SQL) statement with a single variable placeholder asthe frst parameter to the CreateStatement (...) function, and the value to assign to this placeholder as the second argument, + call ExecuteReader on the OCI: :Statement object to execute the query on the database server. ExecuteReader returns an OCT: :DataReader of the specified type that provides access to the query results Since we already know the value of the Td column, we only ask for the OptimisticLock, Name and Description columns. The value of the Td column is guaranteed to uniquely identity a single row in the SimpleTest table, so we only execute reader ->Read() once, and ifit returns true, we know that the query was able to locate the requested row. The Opt imisticLock and Name columns cannot be NULL, while the Description column can—which we check by calling TSDBNUL1. Each column for the current row is identified by its @ based offset into the select-list std: :unique_ptr Select( Int64 id ) const « auto& serviceContext = Servicecontext( ); constexpr wehar_t sql[] = L"SELECT OptimisticLock, Name, Description FROM SimpleTesti U'WHERE Id = 1" auto statenent = serviceContext.Createstatement( sql, id )3 auto reader = statement. ExecuteReader( ); if ( reader->Read( ) ) { auto optimisticLock = reader->GetInt64( @ ); auto name = reader->GetString( 1 ); std::string description; if ( reader->1sDaNull( 2’) == false ) ‘ description = reader-rGetString( 2) y return std: :make_uniquecSimpleTestData>( id, optimisticLock, name, description ); y else { return nullptr; , y Update Again, we use an OCI: :Statement object, passing an UPDATE DML statement asthe first parameter to the CreateStatement (...) function. This time, the variable placeholders are only for input variables and the variables holding the values are passed as the additional arguments ‘The row is not updated unless the Td column matches the value bound to the thitd variable placeholder and the Optimi sticLock column matches the value bound to the fourth variable placeholder. Ths wll prevent the DML from updating the row ifit has been updated by another user or process; and as aside effect, we also know that the next value for the OptimisticLock column, after a successful update, will be the previous value incremented by 1. ‘The DML statement can at most update a single row in the SimpleTest table and since ExecuteNonQuery() retums the umber of rows that was altered by the DML statement, we can safely assume that a retum value greater than @ means that the Update succeecled, while @ means that either the row is deleted or the value for the Optimist icLock column has been changed by another update: bool Update( SimpleTestbata& data ) { auto id = data.1d(); auto optimisticlock = data.Optimisticlock(); hitps:slwwu.codeproject.com/Antcles/5286225/The-Oracle-Cal Interface-OCl-and-ODP-Net-Pat-1?¢lsplay=Print 1008 ‘aioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject auto& name = data.Name(); auto& description = data.Description(); auto& serviceContext = Servicecontext( )5 Std: soptionalcstd: :wstring> descr; if ( description.size( ) ) t descr = description; } constexpr wchar_t sql[] = L"UPDATE SimpleTesta ” L"SET OptimisticLock=optimisticlock+1, Nant LYWHERE Id=:3 AND OptimisticLock=:4"3 1, Descriptior auto statement = serviceContext.createStatement( sql, nane, descr, id, optimistictock )} Af ( statement. ExecuteNonguery( ) > @ ) { data.Setoptimisticlock( optimisticlock + 1 ); return true; , else { return false; ) } Delete The logic behind the DELETE DML statement is nearly identical to the logic for updating a row: bool Delete( const SimpleTestData& data ) { auto id = data.td( ); auto optimisticLock auto& serviceContext data.optimisticLock( ); Servicecontext( ); constexpr wchar_t sql[] = L"DELETE FROM SimpleTest1 * LYWHERE Id=!1 AND OptimisticLock=:2"3 auto statement = serviceContext.CreateStatement( sql, id, optimisticLock ); if ( statenent.ExecuteNonQuery( ) > @ ) { d else t } return true; return false; OCI Program Initialization Just about every operation that can be done using OCI is performed through handles to OCI resources. Each application that uses (OCI must create a handle to an OC! environment, defining a context for executing OCI functions. The environment handle establishes a memory cache for fast memory access, and all memory used by the environment comes from this cache. Environment ‘The Environment class provides access to the functionality of the OCI environment handle, CI applications use an error handle as @ conduit for error information between the client application and the APL and the ErrorHand1e class provides access to the functionality for this handle type. hitps:slwwn.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print 12184 saioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject Creating an Environment is the first step performed when creating and application using the Har Linn . OCI library EnvironmentOptions options; Environment environment( options ); ‘The Environment constructor calls CreateEnvironment( ) which creates the handle for the object. void Environmen { reateEnvironnent( ) void handle = nullptrs auto re = OCIEnvCreate( (OCIEnv**)&handle, (Uint32)0eFaultenvironmentHode( ), nullptr, nullptr, nullptr, nuliptr, (size_t)@, (dvoid**)e ); if (re ¢ OCI::Result::Success ) { ThrowOracleExceptionOnError( handle, rc )3 } return handle; ErrorHandle Once the Environment object has a valid handle, it creates an ErrorHandle object that is used for all error handling, except for calls to OCTHand1eA11o¢, related to this Environment object. OCI: :ErrorHandle Environment: :CreateError( ) const { void* errorHandle = nullptr; auto re = OCIHandleAlloc( (dvoid*)Handle( ), (dvoid**)errorHandle, OCI_HTYPE_ERROR, @, (dvoid**)0 ); if (re < OCI_SUCCESS ) t ThrowracleExceptionOnérror( Handle( ), rc )5 return OCI::ErrorHandle( *this, errorHandle, true ); } ServiceContext, Server and Session Next, we need to establish a handle to the service context that is required for most operational cals through OCI ‘The service context handle contains three handles, represer 1g the server connection, the user session, and the transaction: hitps:slwwn.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print 13184 saioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject ‘The server handle represents a physical connection in a connection-oriented transport mechanism between the client and the database server ‘The user session defines the roles and privileges of the user. ‘The transaction handle represents the transaction context used to perform operations against the server. Tis includes user session state information, including fetch state and package instantiation ‘To establish a service context handle that can be used to execute SQL statement against an Oracle database, we need to perform a number of steps 1. Allocate a sever handle using OCIHandleAlloc 2 Initialize the server handle using OCIServerattach 3 Allocate the service context handle using OCTHandleAlloc 4, Assign the server handle to the service context handle using OCIAttrSet 5. Allocate the user session handle using OCTHandleAlloc 6 Assign the user session handle to the service context using OCIAttrSet 7. Assign the user name to the user session using OCIAttrSet 8. Assign the password to the user session using OCTAttrSet 9. Initialize the service context using OCISessionBegin Har Linn. OCI simples this to: auto server = environnent.CreateServer( ); auto serviceContext = server.CreateServiceContext( Username, Password, Alias ); serviceContext .SessionBegin( ); But also allows each step to be performed separately: auto server = environnent.Createserver( ); server.attach( Alias )3 auto serviceContext = environnent.Createservicecontext( ); serviceContext.SetServer( server ); auto session = environnent.createSession( ); serviceContext.SetSession( std::move( session ) ); session. SetuserNane( Usernane ); session.SetPassword( Password ); serviceContext .SessionBegin( )3 This is useful when you need better control of how you want to configure the options for the various handle types provided by OCL Executing SQL against the Oracle Database Now, that we have established a valid service context, we are ready to execute SQL statements against the Oracle database. To execute a SQL statement using OCI, the cient application performs the following steps: hitps:slwwn.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print arse ‘wroarozt “The Oracle Cal Interface (OCI) and ODPNet- Part 1 - CodeProject 1. Allocate a statement handle for the SQL statement using OCIStmtPrepare2() 2 Forstatements with input, or output, variables, each placeholder in the statement must be bound to an address in the client application using OCIBindByPos2(), OCIBindByName2(), OCIBindObject (), OCIBindDynamic() or OCTBindArrayofStruct() 3. Execute the statement by calling OCIStmtExecute() The remaining steps are only required for SQL queries: 4, Describe the select-lst items using OCIParamGet () and OCIAttrGet (). Ths step isnot required ifthe elements of the select lst are known at compile-time. 5. Define output variables using OCIDefineByPos2( ) or OCIDefineByPos(), OCIDefineObject() OCIDeFineDynamic(), or OCIDefineArrayOFStruct() for each item in the Select list. 6 Fetch the results of the query using OCIStmtFetch2() ‘The code below shows the easiest way to execute an SQL query with bound input variables: std string sql = LYSELECT * FROM ALL_USERS WHERE USERNAME<>: std::wstring myName( L"ESPEN" ); auto statement = servicecontext.CreateStatement( sql, myNane ); auto reader = statement.executeReader( )} ibile ( reader->Read( ) ) { auto userName = reader->As( 2 )5 auto userId = reader->AscInt64>( 1); auto created = reader->As( 2 ); } ‘The CreateStatement(...) function binds all but the first argument automatically, and is able to perform this for the following C+ types: + bool and std: : optional + SByte (signed char) and std: :optional + Byte (unsigned char) and std: :optional + Int16 (short) and std: : optional + UInt16 (unsigned short) and std: :optional + Int32 (int) and std: :optional + UInt32 (unsigned int) and std: : optional + Int64 (long long) and std: :optional + UInt64 (unsigned long long) and std: :optional + Single (float) and std: :optional + Double (double) and std: :optional + DateTime and std: :optional + Guid and std: : optional + std: :wstring and std: Ifthe argument is passed as one of the supported std: : optional<> types, then std: :optional<>: :has_value() is used to control the NULL indicator for the bind. This way of binding variables is intended for input variables only. The CreateStatement function is implemented as a variadic ‘template function, which is why itis able to bind the arguments based on their type: ‘templatectypenane ...Bindabletypes> inline OCI: :Statement ServiceContext: :CreateStatement( const std::wstring& sql, BindableTypes&& ...bindableargs ) const { auto result = CreateStatenent( sql )3 Internal: :Bindargs( result, 1, std: :forward( bindableArgs ). return result; hitps:slwwu.codeproject.com/Antcles/5286225/The-Oracle-CalInterface-OCl-and-ODP-Net-Par-1¢leplay=Print 15108 saioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject CreateStatement( sql_) just calls OCIStmtPrepare2 and checks for errors: Oct: :Statenent: Servicecontext: :CreateStatenent( const std::wstring& sql ) const i auto& environment = Environment( ); if (-environnent.Tsvalid( ) ) t octstmt* ociStatenent = nullptrs auto& error = Error( ); auto errorHandle = (OCIError*)error.Handle( ); auto ne = OCIStmtPrepare?( (OCTSvcCtx*)Handle( ), BociStatenent, errorHandle, (Oratext*)sal.c_str( ), static_cast('sql.length( ) * sizeof( wchar_t ) ), nullper, @, OCIINTV_SYNTAX, OCT_DEFAULT )3 error.checkResult( Fe )} return Statement( *this, ociStatenent, true ); ) aise € ThrowInvalidenvironment( ); > > While the internal implementation of BindArgs processes each ofthe variadic template arguments: ‘templatectypenane Arg, typename ...0therArgsTypes> void BindArgs( OCT::Statement& statement, UInt32 position, const Arg& arg, OtherArgsTypes&& ...otherArgsTypes ) © se constenpr ( tstnyofearg, std::string» ) © juto newind = statenent.BindcAre>( position, arg.tengtn( ) )s newBind->Assign( arg )3 Lise if constexpr ( TsSpectalizationofcarg, sté:toptional> ) {sing pintT = typenane Argssvatue_types 3 Carg.nas.value() ) Lf constexpr ( TsAnyOF« BintT, std::wstring> ) © to newbind = statenent.Bind( position, arg.valve( )-Length( ) )s newBind->Assign( arg.value() )} ? alse { auto newBind = statement .Bind( position ); newBind->Assign( arg.value( ) 5 ? y else ‘ Af constexpr ( IsAnyOf ) © oto nenBind = statenent.sindeBintT>( position, static castesize (0) )s newBind->SetDBNull( ); ? alse { auto newBind = statement .Bind( position ); newBind->SetoBNul1( ); ? y > else { auto newBind = statement.BindcArg>( position ); 4 nmbindmoassiant are); hitps:slwwu.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCland-ODP-Net-Part-1leplay=Print 18134 ‘aioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject Lf constexpr ( sizeof...( otherargsTypes ) > @ ) BindArgs( statement, position + 1, std::forwardcOtherArgsTypes>( otherArgsTypes )... )5 This is an example of how C++ can perform complex compile-time logic while ensuring that the code can still be debugged, where if constexpr’is used to control the code generation. Before C++ 17, debugging code involving compile-time logic could be rather confusing The ExecuteReader function is where al the magic happens, and so far OCIStmtPrepare2 is the only OCI function that has been called auto reader = statement. ExecuteReader( )5 ExecuteReader performs three interesting operations: 1. Creates the DataReader object, or an object of a type derived from DataReader 2.Calls the InitializeDefines() function on the newly created object 3.Executes the SQL statement tenplate requires std::is_base_of_v inline std: :unique_ptr Statement: :ExecuteReader( StatementExecuteNode executeMode ) { auto result = std: :make_uniquecDataReaderType>( *this )3 result->InitializeDefines( ); auto re = Execute( 1, executeMode ); result->Prefetch( re )5 return result; ‘The DataReader provides a default implementation of InitializeDefines() which performs an explicit describe to determine the fields of the Se Lect lst and create appropriate defines using OCIDeFineByPos2 Another alternative isto create a class derived from DataReader: class AllUsersReader : public DataReader { public: using Base = DataReaders constexpr static UInt32 USERNAME = 0; constexpr static UInt32 USER_ID = 1; constexpr static UInt32 CREATED = 2} constexpr static UInt32 COMMON = 3; constexpr static UInt32 ORACLE MAINTAINED = 45 constexpr static UInt32 INHERITED = 5} constexpr static UInt32 DEFAULT_COLLATION = 63 constexpr static UInt32 IMPLICIT = 73 constexpr static UInt32 ALL_SHARD = 8; constexpr static wehar_t SQL] LTUSER_ID, CREATED, COMMON, ORACLE MAINTAINED, LSINHERITED, DEFAULT COLLATION, IMPLICIT, ALL_SHARD L"FROM ALL_USERS" ‘SELECT USERNAME, ” Each field gets its own id, which is the offset in the select-list. Since we know the types for the defines, we can create member variables for each field private: cStringbefine* userName_ = nuliptr; Inte4define* userId_ = hullptr; hitps:slwwu.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCland-ODP-Net-Part-1leplay=Print 17134 ‘aioarz021 DateDefine* created_ = nullptr; CStringDefine* common_ = nullptr; CStringDefine* oracleHaintained_ = nullptr; CStringDefine* inherited_ = nullptr; CStringDefine* defaultCollation_ = nullptrs CStringDefine* implicit_ = nuliptr; CStringDefine* allshard_ = nullptr} public: and then override the InitializeDefines(_) function virtual void InitializeDefines( ) override “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject { UserNane_ = DefinecCStringbefine>( USERNAME + 1, 128 ); userId_ = DefinecInteabefine>( USER_ID + 1); created_ = Define( CREATED + 1 )3 conmon_ = Define( COMMON + 1, 3.)5 oracleMaintained_ = Define( ORACLE MAINTAINED + 1, 1); inherited_ = Define( INHERITED + 1, 3 ); defaultCollation_ = DefineccStringDeFine>( DEFAULT_COLLATION + 1, 100 ); implicit_ = DefineccStringDefine>( IMPLICIT + 1, 3 )3 allshard_ = DefinecCStringDeFine>( ALL_SHARD + 1, 3); } ‘This removes the need to perform any describe on the Select-list and provides direct access to the objects that receives the data fetched from the database through OCI, We can easily implement function to access the data: std::wstring UserName( ) const {ecurn usernane ~>AsString( )s ness Userta( ) const © tur userId_->AsIntea( ); bovetine Created( ) const {turn created >AsbateTime( ); y ‘And now, we can query the ALL_USERS view like this auto statement = servicecontext .createStatement( AllUsersReader auto reader = statement. ExecuteReader( ); while ( reader->Read( ) ) { auto userName = reader->UserNane( )3 auto userId = reader->UserId( ); auto created = reader->Created( ); y SQL )5 While quite a bit more work, this executes more efficiently - and perhaps even more importantly It isolates the internal implementation details of the query from the rest of the code. ‘There are also many situations where you know that it is more efficent to use a 64-bit integer than an Oracle Number, or that OCI: :Date is more appropriate than a Timestamp, ora long var binary (LvB) in place of a BLOB. There are many real- ‘world cases were the abilty to control how data is exchanged with Oracle is crucial to the performance of the solution Improving Performance ‘The article started out with a promise of high performance, and performance is a relative thing, so a base case is needed. Here are the tables that will be used for the test cases: hitps:slwwn.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print 18134 saioarz021 “The Oracle Cal Interface (OCI) and ODPNNet- Parl 1 - CodeProject CREATE TABLE TimeseriesValuet ( Id NUMBER(19) NOT NULL, Ts TIMESTAMP(9) NOT NULL, Flags NUMBER(19) NOT NULL, Val BINARY DOUBLE NOT NULL, CONSTRAINT PK_TSV1. PRIMARY KEV(Id,Ts) ) ORGANIZATION INDEX; CREATE TABLE TimeseriesValue2 ( Td NUMBER(19) NOT NULL, Ts NUMBER(19) NOT NULL, Flags NUMBER(19) NOT NULL, Val BINARY_DOUBLE NOT NULL, CONSTRAINT PK_TSV2 PRIMARY KEY(Id, Ts) ) ORGANIZATION INDEX; ‘They are nearly identical, except thatthe type of the TS column is a TIMESTAMP (9) for TimeseriesValuel anda NUMBER (19) for TimeseriesValue2, NUMBER(19) is large enough to hold any value that can be held by a 64-bit integer. Insert using ODP.Net ‘The base case uses .NET 5.0 and Oracle ODP.Net Core version 2.19100, and it inserts 1 000.000 rows in a loop: public void BasicInsert() { int count = 1000000; var lastTimestamp = new DateTime(2620, 1, 1); var firstTimestanp = lastTimestanp - TimeSpan.FronSeconds(count) ; var oneSecond = TimeSpan.FromSeconds (1); var stopwatch = new Stopwatch(); stopnatch.Start(); for (int 4 i < count; ++i) { var transaction = _connection.BeginTransaction(); using (transaction) { using (var command = _connection.CreateConmand()) { command.ConmandText = "INSERT INTO TimeseriesValuel(Id,Ts,Flags,Val) " + "VALUES ( 3,74)" OracleParaneter id = new OracleParaneter(); id.OraclepbType = OracleDbType. Inte4; id.Value = i + 15 OracleParaneter timestamp = new OracleParameter(); timestamp.OracleDbType = OracleDbType.Timestanp; timestanp.Value = firstTimestamp + (oneSecond * (i + 1))5 OracleParaneter flag = new OracleParameter(); flag.OracleDbType = OracleDbType. Int64; flag.Value = i + 1; OracleParaneter value = new OracleParameter(); value.Oracledblype = OracleDbType.BinaryDouble; value.Value = (double)i + 1.0; command. Parameters Add(id) ; command. Paraneters .Add(timestanp) ; command. Parameters. Add(#lag) ; command. Parameters .Add( value) ; hitps:slwwu.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCland-ODP-Net-Part-1leplay=Print 19184 ‘aioarz021 } “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject command. ExecuteNonQuery() ; ? ‘transaction.Conmit(); + ‘stopnatch.Stop(); var System.Console.Out.WriteLine("Inserted {0} rows in {1} seconds = rows per second: Output: duration = stopwatch. Elapsed. TotalSeconds; rowsPerSecond = count / duration; count, duration, rowsPerSecond) ; Inserted 100880 rows in 285.0611257 seconds - rows per second: 3508.019543332632 Performing the same operation on the TimeseriesValue2 table improves the performance by about 20%: Inserted 100880 rows in 237.306739 seconds - rows per second: 4213.955339886071 Inserting rows one by one is how most client applications insert data into a database. ODPNet has a nice feature that enables us to pass all the data using a single call to ExecuteNonQuery() public void Insert() « int count = 1000000; longl] ids = new longfcount); DateTime[] timestamps = new DateTime[ count]; Longl] flags = new long{count]; double[] values = new double[ count]; var for { > lastTimestamp = new DateTime(202@, 1, 1); firstTimestanp = lastTimestamp - TimeSpan.FromSeconds (count) ; foneSecond = TimeSpan.FromSeconds(1); (int 4 = 0} i < counts ++i) ids[i] = i + 15 timestamps[i] = firstTimestanp + (oneSecond * (i + 1))3 flags[i] = i + 15 values[i] = i + 15 using (var command = _connection.CreateConmand()) { conmand.ConmandText = "INSERT INTO TimeseriesValuel(Id,Ts,Flags,Val) " #"VALUES(:1, 12,23) 24) "5 oracleParaneter id = new OracleParaneter(); id.OracleDbType = OraclebbType. Inte4; id.value = ids; OracleParameter timestamp = new OracleParaneter(); ‘timestamp.OracleDbType = OracleDbType. Timestamp; tinestamp.Value = timestamps; oracleParaneter flag = new OracleParaneter(); flag.OraclepbType = OraclepbType. Int6e4; flag.Value = flags; oracleParameter value = new OracleParameter(); value.OraclepbType = OraclebbType. BinaryDouble; value.Value = values; conmand.ArrayBindCount = ids. Length; hitps:slwwu.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCland-ODP-Net-Part-1leplay=Print (2) 20134 ‘aioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject conmand. Parameters. Add(id) ; conmand.Paraneters.Add(timestamp); conmand. Parameters .Add( flag); conmand.Parameters.Add(value) ; var stopwatch = new Stopwatch(); stopwatch. Start(); var transaction = _connection.BeginTransaction(); using (transaction) { command. ExecuteNonQuery ()5 transaction.Conmit(); y stopwatch. Stop(); var duration = stopwatch.Elapsed. TotalSeconds; var rowsPerSecond = count / duration; System.Console.Out.WriteLine("Inserted {0} rows in {1} seconds - rows per secon 2} count, duration, rowsPerSecond) ; Output: Inserted 1000000 rows in 5.137679 seconds - rows per second: 195550.5254745723 Performing the same operation on the TimeseriesValUe2 table improves the performance by about 9%: Inserted 1800000 rows in 4.6812986 seconds - rows per second: 213615.9398163578 ‘This code binds the input variables to four arrays, each holding 1 000 000 values, and inserts 1 000 000 rows in 4.68 seconds. The program inserted more than 195 000 rows per second, improving the performance by a factor of more than $5, Note: With OD?.Net, lam able to insert 1 000 000 rows using a single call, but attempting to insert 1 050 000 causes ExecuteNonQuery() to throw an exception Changing the type for the TS column to NUMBER (19) also improves the performance, and most modern programming environments use a 64-bit integer representation for time ~ so this is certainly something to consider when performance is a priority Insert using Harlinn.OCl Let us do the same thing using Har'Linn OCI std::wstring sql2( L"INSERT INTO TimeseriesValuel(Id,Ts,Flags,Vel) * L°VALUES( 1, :2, 23, 24)" Jp DateTime lastTinestamp( 2020, 1, 1); auto finstTimestanp = lastTimestamp - TimeSpan: :FromSeconds( count ); auto oneSecond = TimeSpan::FromSeconds( 1); Stopwatch stopwatch; stopwatch.Start( ); for ( size_t i = @; i < count; ++i ) « auto id = i +45 auto timestamp = firstTimestanp + ( oneSecond = (i +1) )5 auto flags = i + 1; auto value = stati¢_castcdouble>( i +1); // Create the statement auto insertStatenent = serviceContext.Createstatement( sql2, id, timestamp, flags, value ); // execute the insert insertStatenent.Execute( ); hitps:slww.codeproject.com/Artcles/5286225/The-Oracle-Cal ntrface-OCl-and-ODP-Net-Part-1leplay=Print nis ‘aioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject y // commit the changes serviceContext.TransactionConmit( )3 stopwatch.stop( ); auto duration = stopwatch. Totalseconds( ); auto rowsPerSecond = count / duration; print#( "Inserted Xzu rows in Xf seconds - Xf rows per seconds\n count, duration, rowsPerSecond ); Output: Inserted 1000000 rows in 113.662342 seconds - 8797.988711 rows per seconds ‘The code performs pretty much the same job as the ODP.Net base case above, inserting rows one-by-one in a loop, allocating an OCI: :Statement and binding the input variables for each iteration. It outperforms the ODP.Net version by a factor of more than 25, ‘The improvement achieved by switching to TimeseriesValue2 is so marginal that itis hard to argue that it proves anything fone way or the other: Inserted 1000800 rows in 110.978168 seconds - 9010.781308 rows per seconds Using ODP.Net demonstrated that we can significantly improve the performance by binding to arrays and perform all the inserts through a single call to ExecuteNonQuery ( ) 50 itis certainly interesting to see how much this technique will improve the OCI based solution: // Number of rows to insert constexpr size_t count = 1'000°@00; wstring sql2( L"INSERT INTO TimeseriesValuel(Id,Ts,Flags,Val) " L°VALUES( 1, :2, 23, 24)" D5 // Create the statement auto insertStatement = serviceContext.createStatement( sql2 ); // Create the bind objects auto id = insertStatement .BindcInt64ArrayBind>( 1 ); auto timestamp = insertStatement .Bind( 2 ); auto flag = insertStatenent .Bind( 3 )5 auto value = insertStatenent.Bind( 4}; DateTime lastTinestamp( 2028, 1, 1); auto firstTimestamp = lastTimestamp - TimeSpan::FromSeconds( count ); auto oneSecond = TimeSpan::FromSeconds( 1 ); // vectors that will be bound by the bind objects std::vectorcUInt64> ids( count ); vector timestamps( count ); vector flags( count ); rtvectorcdouble> values( count); // Initialize the vectors with dummy data for ( size_t i= @; i < count; ++i ) « ids[i] = i +15 timestamps[i] = firstTimestamp + ( oneSecond * (i +1) )j flags[i] = i + 15 values[i] = static_cast( i + 1 ); } // Assign the vectors to the bind objects id->Assign( std::move( ids ) ); ‘timestanp->Assign( tinestanps ); Flag->Assign( std? :nove( flags ) )s value->Assign( std::move( values ) ); // Execute the inserts and conmit the changes Stopwatch stopwatch; hitps:slww.codeproject.com/Artcles/5286225/The-Oracle-Cal ntrface-OCl-and-ODP-Net-Part-1leplay=Print 22134 ‘aioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject stopwatch. Start( ); insertStatement.Execute( count ); serviceContext.TransactionCommit( ); stopwatch. Stop( ); auto duration = stopwatch.TotalSeconds( ); auto rowsPerSecond = count / duration; print#( "Inserted %zu rows in Xf seconds - Xf rows per seconds\n", count, duration, rowsPerSecond ); Output: Inserted 1000000 rows in 0.814117 seconds - 1228324.072179 rows per seconds ‘This version further improved the performance by a factor of 5.75 compared to the fastest NET version - demonstrating that the Oracle Call Interface can be much more efficient than ODP.Net. Compared to the basic ODP.Net version, we have improved the performance by a factor of 350, so this is certainly worth the extra effort which, to be honest isn't that big, Switching to TimeseriesValue2, the code now performs slightly worse than for TimeseriesValuel, but again the difference is so marginal that it cannot be argued that it proves anything. Inserted 1000000 rows in 0.818555 seconds - 1221665.624225 rows per seconds ‘The capacity for handling large volumes of data is also much greater for OCI, and this version can easily insert 100 000 000 rows Using a single cal, but with reduced performance: Inserted 102880000 rows in 106.102264 seconds - 942486.955@12 rows per seconds Select using ODP.Net ‘Again NET 5.0 and ODP.Netis used to establish a base case: public void Select() « using (var command = _connection.CreateConmand()) { int count = 0; var stopwatch = new Stopwatch(); conmand.ConmandText = "SELECT Id,Ts,Flags,Val FROM TimeseriesValue2 ORDER BY Id,Ts"; stopwatch. start(); var reader = conmand.ExecuteReader(); vinile (reader.Read()) « var id = reader.GetInt64(9); var ts = reader.Getint6a(1); var flags = reader.GetInt64(2); var value = reader.GetDouble(3); count++; + Stopwatch.stop(); var duration = stopwatch. lapsed. TotalSeconds; var rowsPerSecond = count / duration; System.Console.Out .WriteLine( "Retrieved {8} rows in {1} seconds - ‘ows per second: {2} ", count, duration, rowsPerSecond); Output: Retrieved 108820 rows in @.5656143 seconds - rows per second: 1767989.246382278 This is surprisingly good. 5) hitps:slwwn.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print 23134 saioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject Select using Harlinn.OCl ‘The intial C++ implementation performs only marginally better: auto statement = serviceContext.Createstatement( L L ELECT Id,Ts,Flags,Val ” ROM TimeseriesValue2" ); statenent.SetPrefetchRows( 30°22 ); Stopwatch stopwatch; stopwatch.start( ); auto reader = statement .ExecuteReader( 120'000 ); size_t count = 0; while ( reader->Read( ) ) i auto id = reader->Geturntea( @ ); auto ts = reader->GetUint6a( 1); auto flags = reader->GetUIntea( 2 ); auto value = reader->GetDouble( 3 ); count+5 y Stopwatch.stop( ); auto duration = stopwatch. Totalseconds( ); auto rowsPerSecond = count / duration print#( "Retrieved %zu rows in %f seconds - %F rows per seconds\n", count, duration, rowsPerSecond ); Output: Retrieved 100000 rows in @.413025 seconds - 2421160.946674 rows per seconds Here, we got about 36% better throughput using C++ and OCI, but we should be able to do better than that, Itis time for a custom data reader: class TimeseriesValues2Reader : public ArrayDataReader { public: using Base = ArrayDataReader; constexpr static UInt32 ID_ID = @; constexpr static UInt32 TS_ID = 13 constexpr static UInt32 FLAGS_ID constexpr static UInt32 VAL_ID = 23 constexpr static wehar_t SQL[] public: TimeseriesValues2Reader( const OCI::Statement& statement, size_t size ) : Base( statement, size ) ‘SELECT Id,Ts,Flags,Val FROM TimeseriesValue: { } Virtual void Initializebefines( ) override { DefinecuInt64>( ID_ID + 1 ); DefinecUInte4>( TS_1D + 1); DefinecUInts4>( FLAGS_ID +1 )5 Definecbouble>( VAL_ID + 1); > Unntes 14( ) const { return GetUInte4( 1D_ID ); } Uinte4 Timestamp( ) const { return GetuIntsa( TS_ID ); } UInts4 Flag() const { return GetUInt64( FLAGS_ID ); } Double Value( ) const { return GetDouble( VAL_ID ); } h ‘The implementation of InitializeDefines( ) allows us to take control over the native data format for each of the elements in the S@Lect-lis. This makes a huge difference: hitps:slwwn.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print parse saioarz021 “The Oracle Cal Interface (OCI) and ODPNNet- Parl 1 - CodeProject constexpr UInt32 PrefetchRows = 32'000; auto statement = serviceContext .createStatement( TimeseriesValues?Reader: :SQL ); statenent.SetPrefetchRows( PrefetchRows ); Stopwatch stopwatch; stopwatch.Start( ); auto reader = statement. ExecuteReader( PrefetchRows*5 ); size_t count = @; while ( reader->Read( ) ) { auto id = reader->14( ); auto ts = reader->Timestamp( ); auto flags = reader->Flag( )s auto value = reader->Value( ); counts; 3 stopwatch.Stop( )s auto duration = stopwatch. Totalseconds( ); auto rowsPerSecond = count / duration; print#( "Retrieved %zu rows in %F seconds - Xf rows per seconds\n", count, duration, rowsPerSecond ); Ourput Retrieved 100000 rows in @.294642 seconds - 3393944.659696 rows per seconds ‘The C++ implementation is 91% faster than ODP.Net and Net 5.0, and ths inclides the time that was spent on parsing, executing, and fetching the data from the cursor on the server ~in this case about 100 ms according to the ELAPSED_TIME column of the \V$SQLAREA view: and the time spent passing the data through the TCP/IP stack. Can we do better than this? HarLinn . OCI does net yet support binding to structures, but this is an option that needs to be explored. First, we need a structure that matches the Select-list for the data: struct Timeseriesvalue { Inte4 14; Int64 Timestamp; Inte4 Flags; Double Value; We still use Har-Linn .OCI to create the statement, and configure the prefetch rows: constexpr UInt32 PrefetchRows = 32'000; auto statement = servicecontext.CreateStatement( L"SELECT Id,Ts,Flags, Val L"FROM TimeseriesValue2" statement. SetPrefetchRows( PrefetchRows ); With that out of the way, we need to allocate the memory that will receive the data from OCI: std: :vectorcTimeseriesValue> values( PrefetchRows*4 ); Timeseriesvalue* data = values.data( ); octstmt* statenentHandle = (OcIStut*)statement.Handle()5 auto& error = statement.Error( ); OctError* errorHandle = (OCIError*)error.Handle( ); ‘When we want OCI to place data directly into a structure, we call OCIDefineByPos2 as usual, passing the address, size and ‘ype ofthe variable fr the fist element ofthe vector hitps:slwwu.codeproject.com/Antcles/5286225/The-Oracle-CalInterface-OCl-and-ODP-Net-Par-1¢leplay=Print 25104 saioarz021 “The Oracle Cal Interface (OCI) and ODPNNet- Parl 1 - CodeProject octDefine* idbefinelandle = nuliptr auto rc = OCIDefineByPos2( statenentHandle, &idDefineHandle, errorHandle, 1, &data->Td, sizeof( data->Id ), SQLT_INT, nullptr, nuliptr, nullptr, OCf_DEFAULT ); if (re < OCT_SUCCESS ) { + error.checkResult( re); Then we need to let OCI know about the distance to the same variable for the next element of the vector: rc = OCIDefineArrayofstruct( idDefineHandle, errorHandle, sizeof( TimeseriesValue ), 0, @, ® ); if (re < OCT_SUCCESS ) { , error.checkResult( re )5 ‘This is then repeated for the remaining members of the structure: OcIbefine* timestanpDeFineHandle = nullptr; rc = OCIDefineByPos2( statementHandle, ×tampDefineHandle, errorHandle, 2, Adata-sTimestamp, sizcof( data->Timestamp ), SQLT_INT, nullpte, nullpte, nullptr, OCX_DEFAULT ); if (re < OCT_SUCCESS ) ‘ + Pe = OCIDefineArrayofStruct( timestampDefineHandle, errorHandle, sizeof( TimeseriesValue ), ®, @, @ ); if (re ¢ OCI_SUCCESS ) { y OcrDefine™ flagsDefineHandle = nullptr; rc = OCIDefineByPos2( statementHandle, &flagsDefineHandle, errorHandle, 3, Adata->Flags, sizeof( data->Timestamp ), SQLT_INT, nullptr, nulipte, nullptr, OCI_DEFAULT )3 if (re < OCI_SUCCESS ) { } Pe = OCIDeFineArrayofstruct( flagsbefineliandle, errorHandle, sizeof( TimeseriesValue ), 0, ®, ); if (re < OCT_SUCCESS ) { + ocrDefine* valueDefineHandle = nuliptr; rc = OCIDefineByPos2( statementHandle, @valueDefineHandle, errorHandle, 4, &data->value, sizeof( data->Value ), SQLT_SDOUBLE, nullptr, nulipte, nullptr, OCI_DEFAULT )3 if (re < OCI_SUCCESS ) { } Pe = OCIDeFineArrayOFstruct( valuebefineliandle, errorHandle, sizeof( TimeseriesValue ), 0, 0, @ ); if (re < OCT_SUCCESS ) { error.checkResult( re )5 error.checkResult( re )5 error.checkResult( rc ); error.checkResult( re )5 error.checkResult( re ); error.checkResult( re )5 Now that we have created defines for each column of the query, itis time to execute the query on the Oracle database: hitps:slwwn.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print 26134 saioarz021 “The Oracle Cal Interface (OCI) and ODPNNet- Parl 1 - CodeProject OcISvcCtx* servicecontextHandle = (OCISvcCtx*)servicecontext.Handle( ); Stopwatch stopwatch; stopwatch. Start( ); size_t count = 0; re = OCIStmtExecute( serviceContextHandle, statementHandle, errorHandle, @, @, NULL, NULL, OCI_DEFAULT ); if (re < OCT_SUCCESS ) { } error.checkResult( re ); Itis only when we receive the data from OCI that we need to specify the size of the vector: re = OCIStntFetch2( statenentiandle, errorHandle, static_casteUInt32>( values.size( ) ), OcT_FETCH NEXT, @, OCT_DEFAULT ); {this point, the data has been placed into the vector, and information about exactly how many rows that was retrieved by the fetch can be determined by calling RowsFetched( ) if (re >= OCI_SUCCESS ) { UInt32 rowsFetched = statement.RowsFetched( ); while ( rowsFetched && rc >= OCI_SUCCESS ) { for ( UInt32 i = @; i < rowsFetched; ++i ) { auto id = data[i].1¢; auto ts = data[i]. Timestamp; auto flags = data[i].Flags; auto value = data[i].Value; count++; + if (re t= OCT_NO_DATA ) { rc = OcIStmtFetcha( statementHandle, errorHandle, static_cast( values.size( ) }, OCT_FETCH NEXT, @, OCT_DEFAULT ); rowsFetched = statement.RowsFetched( ); t else { rowsFetched = 0; y > ? if (re < oct_success { error.checkResult( rc ); } stopwatch.Stop( ); auto duration = stopwatch.Totalseconds( ); auto rowsPerSecond = count / duration; print#( "Retrieved %zu rows in Xf seconds - Xf rows per seconds\n", count, duration, rowsPersecond ); Output: Retrieved 1000000 rows in 0.276765 seconds - 3613176.242065 rows per seconds Ths improved the performance by about 6% compared tothe custom datareader. Update using ODP.Net hitps:slwwn.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print 27134 saioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject he most common way to update tables in a database is to execute an Update statement for each row, lke this: public void BasicUpdate() { var rows = GetAll(); var stopwatch = new Stopwatch(); stopwatch. Start(); foreach (var row in rows) t using (var command = _connection.CreateCommand()) { conmand.CommandText = "UPDATE TimeseriesValuel SET Flags=:1, Val=:2 MERE Td=:3 AND Ts=:4"; OracleParameter flag = new OracleParaneter(); Flag.OracleDeType = OraclebbType. Inté4; flag.Value = row.Flags * 2; OracleParameter value = new OracleParaneter(); value.OracleDbType = OracleDbType.BinaryDouble; value.Value = row.Value * 2; OracleParameter id = new OracleParaneter(); id.OraclepbType = OraclebbType. Int64; id.value = row.Id; OracleParameter timestamp = new OracleParameter(); timestanp.OracleDbType = OracleDbType. Timestamp; timestamp.Value = row. Timestamp; conmand. Parameters .Add(flag) ; conmand. Parameters Add( value) ; conmand. Parameters .Add(id) ; conmand. Parameters .Add(timestamp) ; conmand. ExecuteNonQuery(); y ? stopwatch.Stop() ; var duration = stopwatch. Elapsed. Totalseconds; var rowsPerSecond = rows.count / duration; System.Console.Out.WriteLine("Updated {8} rows in {1} seconds - rows per seconi rows.Count, duration, rowsPerSecond); Output: Updated 1000000 rows in 199,6225356 seconds - rows per second: 5009.454453598274 And as we saw for the inserts, this can be drastically improved by binding to arrays and executing all the updates through 2 single cal to ExecuteNonQuery (): public void Update() { var rows = Getall(); ongt] ids = new longfrows count}; DateTime[] timestamps = new DateTime[rows.Count]; longt] flags = new longtrows.count]; double] values = new double{rows.count]; for (int i = @; i < rows.Count; +44) { var row = rows[]; ids[i] = row.Td; timestanps[i] = row.Timestamp; flags[i] = row.Flags * 2; values[i] = row.Value* 2; hitps:slww.codeproject.com/Artcles/5286225/The-Oracle-Cal ntrface-OCl-and-ODP-Net-Part-1leplay=Print 20134 ‘aioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject } using (var command = _connection.CreateConmand()) { conmand.ConmandText = "UPDATE TimeseriesValuei SET Flag: + WHERE Id=:3 AND Ts=:4"; OracleParameter flag = new OracleParaneter(); flag.OracleDbType = OracleDbType. Int4; flag.Value = flags; oracleParameter value = new OracleParameter(); value.OracleDbType = OracleDbType. BinaryDouble; value.Value = values; OracleParaneter id = new OracleParameter(); id.OracleDbType = OracleDbType. Int643 id.Value = ids; oracleParaneter timestamp = new OracleParaneter(); ‘tinestanp.OracleDbType = OracleDbType. Timestamp; timestanp.Value = timestamps; conmand.ArrayBindCount = rows.Count; conmand.Paraneters..Add( flag); conmand. Parameters. Add( value) ; conmand.Paraeters.Add(id) conmand.Paraneters.Add(timestamp) ; int count = 93 var stopwatch = new Stopwatch(); stopwatch. Start(); var transaction = connection. BeginTransaction(); using (transaction) { count = command. ExecuteNonguery(); transaction.Conmit(); y stopwatch. Stop(); var duration = stopwatch.Elapsed. TotalSeconds; var rowsPerSecond = count / duration; System.Console.Out.WriteLine("“Usdated {0} rows in {1} seconds - rows per second: count, duration, rowsPerSecond); Output: Updated 100000 rows in 11,9057144 seconds - rows per second: 83993,279731286 ‘This improved the performance by a factor of 16 — which is certainly a worthwhile improvement. Update using Harlinn.OCl Using OC! to implement the basic update loop: Stopwatch stopwatch; stopwatch.start( ); constexpr wehar_t sql[] = L'UPOATE TimeseriesValue1 SET Flags=:1, Val=:2 WHERE Td=:3 AND Ts: for ( auto& row : rows ) « auto updateStatenent = serviceContext.createStatement( sql, row.Flags*2, row.Value*2, row.Id, row.Timestanp ); updateStatenent.ExecuteNonQuery( )} hitps:slww.codeproject.com/Artcles/5286225/The-Oracle-Cal ntrface-OCl-and-ODP-Net-Part-1leplay=Print p23 "y rors ‘aioarz021 y “The Oracle Cal Interface (OCI) and ODPINet- Part 1 stopwatch.Stop( ); auto duration = stopwatch.Totalseconds( ); auto rowsPerSecond = count / duration; print#( "Updated zu rows in %f seconds - %f rows per seconds\n", count, duration, rowsPerSecond ); Output: Updated 100000 rows in 123.436877 seconds - 8101.306717 rows per seconds CodeProject Improves the performance by 61% over the basic update loop implemented using ODP.Net, and when we perform array binding using Oct constexpr wehar_t sql[] = L"UPDATE TimeserdesValuel SET Flags=:1, Val=:2 WHERE Id=:3 AND T: updateStatenent = serviceContext .CreateStatement( sql ); auto auto auto auto auto Flag = updateStatement .Bind( 1); value = updateStatement.Bind( 2 }5 id = updateStatement.Bind( 3 ); ‘timestamp = updateStatement..Bind( 4); std: std: vector ids( count ); vector timestanps( count ); vector flags( count ); vector values( count); for ( sizet i = @; i < count; ++i ) « auto& row = rows[i]s ids[i] = row.1d; timestamps[i] = row. Timestamp; flags[i] = row.Flags*2; values[i] = row.Value*2; + // Assign the vectors to the bind objects id-pAssign( std::move( ids ) )3 tinestanp->Assign( timestamps ); flag->Assign( std: :move( flags ) ); value->Assign( std::move( values ) ); Stopwatch stopwatch; stopwatch. Start( ); updatestatenent.. FxecuteNonQuery( static_cast( count ) ); serviceContext.TransactionConmit( )3 stopwatch.Stop( ); auto duration = stopwatch. Totalseconds( ); auto rowsPerSecond = count / duration print#( "Updated zu rows in %f seconds - Xf rows per seconds\n", count, duration, rowsPerSecond ); Output: Updated 1000000 rows in 6.182285 seconds - 161752.5@1656 rows per seconds Improves the performance by a factor of 32 over the basic ODP.Net update loop, and i's twice as fast as the array binding for ODP.Net Updating 100 000 000 rows using a single call to ExecuteNonQuery is possible, but again this hurts performance significantly hitps:slwwn.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print 30134 saioarz021 “The Oracle Cal Interface (OCI) and ODPNNet- Parl 1 - CodeProject Updated 10990000 rows in 1131.646176 seconds - 88366.842991 rows per seconds The End (for now ...) Itis interesting to not how fast Oracle can insert data compared to updating the rows. Most transactional database management systems leverage a technique called multi-version concurrency control As a result, Updates never update the existing record, but create a copy that will only be visible to other sessions after the update is committed to the database. Some systems offer multiple transaction isolation levels, sometimes allowing parallel sessions to read data that has not yet been committed by the session performing the update or insert. These systems will still make a copy of the updated row, otherwise they would be unable to perform a rollback of the current transaction. Managing multiple versions of data in a consistent manner is complicated, so it should not be surprising that this has 2 significant impact on the overall performance of the Since inserts generally outperform updates; particulary for tables with few, fixed size, columns; this is something that can be taken into consideration when designing the database, In the long run, this will probably make the database more valuable, since it vill now be possible to analyze the impact of the changed data, perhaps using machine learning or more traditional statistical methods History + 314 December, 2020 - Initial post +18! December, 2020: © Bug fixes for lO:FileStream © Initial http server development support = Synchronous server: (SolutionDir)Examples\Core\HTTP\Server\HttpServerExO1 1 Asyncronous server: §(SolutionDirExamples\Core\HTTP\Server\HttpServerEx02 © Simplified asynchronous 1/0, Timers, Work and events for Windows waitable kernel objects using Windows thread pool API: §(SolutionDir}Examples\Core\ThreadPools\HExTpEx01 +11" of February, 2021 © Bug fixes © Initial C++ OD8C support + 25th of February, 2021 Updated LMDB. Updated xxHash ‘Added the intial implementation of very fast hash based indexes for large complex keys using LMDB Fast asychronous logging - nearly done :-) + 34 of March, 2021 © New authorization related classes 1 Securityld: Wrapper for SID and related operations = ExplicivAccess: Wrapper for EXCPLICIT_ACCESS, ‘= Trustee: Wrapper for TRUSTEE = SecurityldAndDomain: Holds the result from LookupAccountName = LocalUniquele: Wrapper for LUID 1 AccessMask: Makes it easy to inspect the rights assigned to an ACCESS MASK 1 AccessMaskT<> 1 EventWaitHandleAccessMask Inspect and manipulate the rights of an EventWaitHandle. 1 MutexAccessMask: Inspect and manipulate the rights of a Mutex, 1» SemaphoreAccessMask: Inspect and manipulate the rights of a Semaphore. ‘© WaitableTimerAccessMask: Inspect and manipulate the rights of a WaitableTimer. = FileaccessMask: Inspect and manipulate file related rights, * DirectoryAccessMask: Inspect and manipulate directory related rights. hitps:slwwn.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print 31134 saioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject = PipeAccessMask: Inspect and manipulate pipe related rights. 1 ThreadAccessMask Inspect and manipulate thread related rights = ProcessAccessMask: Inspect and manipulate process related rights = GenerieMapping: Wrapper for GENERIC_MAPPING 1 AccessControlEntry: This is 2 set of tiny classes that wraps the ACE sti = AccessContre tryBase<,> = AccessAllowedAccessControlEntry 1 AccessDeniedAccessControlEntry 1 systemAuditAccessControlEntry 1 SystemAlarmAccessControlEntry 1 SystemResourceAttributeAccessControlEntry = SystemScopedPolicyldAccessControlEntry = SystemMandatoryLabelAccessControlentry 1 SystemProcessTrustLabelAccessControlEntry = SystemAccessfilterAccessControln 1 AccessDeniedCallbackAccessControlEntry 1 SystemAuditCallbackAccessControlEntry 1 SystemAlarmCallbackAccessControléntry = ObjectAccessControléntryBase<,> = AccessAllowedObjectAccessControlEntry 1 AccessDeniedObjectAccessControlEntry 1 SystemAuditObjectAccessControlEntry 1 SystemAlarmObjectAccessControlEntry = AccessAllowedCallbackObjectAccessControlEntry 1 AccessDeniedCallbackObjectAccessControlEntry 1 SystemAuditCallbackObjectAccessControléntry 1 SystemAlarmCallbackObjectAccessControlEntry 1 AccessControlList: Wrapper for ACL * PrivilegeSet: Wrapper for PRIVILEGE_SET ' SecurityDescriptor: Early stage implementation of wrapper for SECURITY_DESCRIPTOR = SecurityAttsibutes: Very early stage implementation of wrapper for SECURITY_ATTRIBUTES ' Token: Early stage implementation of wrapper for an access token = Domaindbject 1 User: Information about a local, workgroup or domain user = Computer: Information about a local, workgroup or domain computer 1 Group: local, workgroup or domain group Users: vector of User objects Groups: vector of Group objects + 14" of March, 2021 - mote work on security related stuff: © Token: A wrapper for a Windows access token with a number of supporting classes like: = TokenAccessMask: An access mask implmentation for the access rights of a Windows access token. ‘= TokenGroups: A wrapper/binary compatible replacement for the Windows TOKEN_GROUPS type with a C++ container style interface, = TokenPrivileges: A wrapper/binary compatible replacement for the TOKEN_PRIVILEGES type with a C++ container style interface, 1 TokensStatistcs: A binary compatible replacement for the Windovis TOKEN. STATISTICS type using types implemented by the library such as LocalUniqueld, TokenType and ImpersonationLevel. © TokenGroupsAndPrivileges: A Wrapper/binary compatible replacement for the Windows TOKEN_GROUPS_AND_PRIVILEGES type, = TokenAccessinformation: A wrapper/binary compatible replacement for the Windows TOKEN_ACCESS INFORMATION type. = TokenMandatoryLabel: A wrapper for the Windows TOKEN_MANDATORY_LABEL type. SecurityPackage: Provides access to information about a Windows security package. SecurityPackages: An std:unordered_map of information about the security packages installed on the system, CredentialsHandle: A wrapper for the Windows CredHandle type, SecurityContext: A wrapper for the Windows ChxtHandle type CryptosBlob and Crypto:BlobT: C++ style_CRYPTOAPI_BLOB replacement hitps:slwwn.codeproject.com/Artcles/5286225/The-Oracle-Cal nterface-OCl-and-ODP-Net-Part-1eleplay=Print 32134 saioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject © CertificateContext: A wrapper for the Windows PCCERT_CONTEXT type, provides access to a X,509 certificate © CertificateChain: A wrapper for the Windows PCCERT_CHAIN_CONTEXT type which contains an array of simple certificate chains and a trust status structure that indicates summary validity data on all of the connected simple chains ServerOcspResponseContext: Contains an encoded OCSP response. ServerOcspResponse: Represents a handle to an OCSP response associated with a server certificate chain CertificateChainngine: Represents a chain engine for an application CertificateTrustList: A wrapper for the Windows PCCTL_CONTEXT type which contains bath the encoded and decoded representations of a CTL. It also contains an opened HCRYPTMSG handle to the decoded, cryptographically signed message containing the CTL_INFO as its inner content. © CertificateRevocationList: Contains both the encoded and decoded representations of a certificate revocation lst (Ry, © CertificateStore: A storage for certificates, certificate revocation lists (CRLs), and certificate trust lsts (CTLs. License This atl, along with any associated source code and fl, i licensed under The Code Project Open License (CPOL) About the Author Espen Harlinn Architect Ulriken Consulting AS Norway ti Senior Architect - Uliken Consulting AS. Specializing in integrated operations and high performance computing solutions, I've been fooling around with computers since the early eighties, ve even done work on CP/M and MP/M, ‘Wrote my first “real” program on a BBC micro model 8 based on a series in a magazine at that time. It was fun and | got hooked fn this thing called programming A few Highlights: + High performance application server development ‘+ Model Driven Architecture and Code generators ‘© Real-Time Distributed Solutions + C.C++, Cr, Java, TSQL, PL/SQL, Delphi, ActionScript, Per, Rexx ‘+ Microsoft SQL Server, Oracle RDBMS, IBM DB2, PostGreSQl + AMQP, Apache qpid, RabbitMQ, Microsoft Message Queuing, IBM WebSphereMQ, Oracle TuxidoM + Oracle WebLogic, IBM WebSphere + Corba, COM, DCE, WCF + AspenTech InfoPlus.21(1P21), OsiSoft PI More information about what | do for a living can be found at: harlinn.com or Linkedin You can contact me at espen@harlinn.no hitps:slwwu.codeproject.com/Antcles/5286225/The-Oracle-CalInterface-OCl-and-ODP-Net-Par-1¢leplay=Print 33004 saioarz021 “The Oracle Cal Interface (OCI) and ODPNNel- Parl 1 - CodeProject Comments and Discussions (54 messages have been posted for this article Visit https://www.codeproject.com/Articles/5286225/The-Oracle-Call- Interface-OCl-and-ODP-Net-Part-t to post and view comments on ths article, or click here to get a print view with messages. Permalink Auticle Copyright 2020 by Espen Harlinn Advertise Everything else Copyright © CodeProject, 1999- Privacy 2021 Cookies Terms of Use \web03 2820210309.1 hitpsslwwu.codeproject.com/Artcles/5286225/The-Oracle-Cal Interface-OCl-and-ODP-Net-Par-1?¢leplay=Print 34034

You might also like