Can I control how Oracle maps the integer types in ADO.NET? - oracle

I've got a legacy database that was created with the database type INTEGER for many (1.000+) Oracle columns. A database with the same structure exists for MS SQL. As I was told, the original definition was created using a tool that generated the scripts from a logical model to the specific one for MS SQL and Oracle.
Using C++ and MFC the columns were mapped nicely to the integer type for both DBMs.
I am porting this application to .NET and C#. The same C# codebase is used to access both MS SQL and Oracle. We use the same DataSets and logic and we need the same types (int32 for both).
The ODP.NET driver from Oracle maps them to Decimal. This is logical as Oracle created the integer columns as NUMBER(37) automatically. The columns in MS SQL map to int32.
Can I somehow control how to map the types in the ODP.NET driver? I would like to say something like "map NUMBER(37) to int32". The columns will never hold values bigger than the limits of an int32. We know this because it is being used in the MS SQL version.
Alternatively, can I modify all columns from NUMBER(37) to NUMBER(8) or SIMPLE_INTEGER so that they map to the right type for us? Many of these columns are used as primary keys (think autoincrement).

Regarding type mapping, hope this is what you need
http://docs.oracle.com/cd/E51173_01/win.122/e17732/entityDataTypeMapping.htm#ODPNT8300
Regarding type change, if table is empty, you may use following script (just replace [YOUR_TABLE_NAME] with table name in upper case):
DECLARE
v_table_name CONSTANT VARCHAR2(30) := '[YOUR_TABLE_NAME]';
BEGIN
FOR col IN (SELECT * FROM user_tab_columns WHERE table_name = v_table_name AND data_type = 'NUMBER' AND data_length = 37)
LOOP
EXECUTE IMMEDIATE 'ALTER TABLE '||v_table_name||' MODIFY '||col.column_name||' NUMBER(8)';
END LOOP;
END;
If some of these columns are not empty, then you can't decrease precision for them
If you have not too much data, you may move it to temp table
create table temp_table as select * from [YOUR_TABLE_NAME]
then truncate original table
truncate [YOUR_TABLE_NAME]
then run script above
then move data back
insert /*+ append */ into [YOUR_TABLE_NAME] select * from temp_table
commit
If data amount is substantial it is better to move it once. In such case it is faster to create new table with correct datatypes and all indexes, constraints and so on, then move data, then rename both tables to make new table have proper name.

Unfortunately the mapping of numeric types between .NET and Oracle is hardcoded in OracleDataReader class.
In general I usually prefer to setup appropriate data types in the database, so if possible I would change the column datatypes because they better represent the actual values and their constraints.
Another option is to wrap the tables using views casting to NUMBER(8) but will negatively impact execution plans because it prohibits index lookups.
Then you have also some application implementation options:
Implement your own data reader or subset of ADO.NET classes (inheriting from DbProviderFactory, DbConnection, DbCommmand, DbDataReader, etc. and wrapping Oracle classes), depending on how complex is your implementation. Oracle.DataAccess, Devart and all providers do exactly the same because it gives total control over everything including any magic with the data types. If the datatype conversion is the only thing you want to achieve, most of the implementation would be just calling wrapped class methods/properties.
If you have access to OracleDataReader after command is executed and before you start to read it you can do a simple hack and set the resulting numeric type using reflection (following implementation is just simplified demonstration).
However this will not work with ExecuteScalar as this method never exposes the underlying data reader.
var connection = new OracleConnection("DATA SOURCE=HQ_PDB_TCP;PASSWORD=oracle;USER ID=HUSQVIK");
connection.Open();
var command = connection.CreateCommand();
command.CommandText = "SELECT 1 FROM DUAL";
var reader = command.ExecuteDatabaseReader();
reader.Read();
Console.WriteLine(reader[0].GetType().FullName);
Console.WriteLine(reader.GetFieldType(0).FullName);
public static class DataReaderExtensions
{
private static readonly FieldInfo NumericAccessorField = typeof(OracleDataReader).GetField("m_dotNetNumericAccessor", BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly object Int32DotNetNumericAccessor = Enum.Parse(typeof(OracleDataReader).Assembly.GetType("Oracle.DataAccess.Client.DotNetNumericAccessor"), "GetInt32");
private static readonly FieldInfo MetadataField = typeof(OracleDataReader).GetField("m_metaData", BindingFlags.NonPublic | BindingFlags.Instance);
private static readonly FieldInfo FieldTypesField = typeof(OracleDataReader).Assembly.GetType("Oracle.DataAccess.Client.MetaData").GetField("m_fieldTypes", BindingFlags.NonPublic | BindingFlags.Instance);
public static OracleDataReader ExecuteDatabaseReader(this OracleCommand command)
{
var reader = command.ExecuteReader();
var columnNumericAccessors = (IList)NumericAccessorField.GetValue(reader);
columnNumericAccessors[0] = Int32DotNetNumericAccessor;
var metadata = MetadataField.GetValue(reader);
var fieldTypes = (Type[])FieldTypesField.GetValue(metadata);
fieldTypes[0] = typeof(Int32);
return reader;
}
}
I implemented extension method for command execution returning the reader where I can set up the desired column numeric types. Without setting the numeric accessor (it's just internal enum Oracle.DataAccess.Client.DotNetNumericAccessor) you will get System.Decimal, with accessor set you get Int32. Using this you can get all Int16, Int32, Int64, Float or Double.
columnNumericAccessors index is a column index and it will applied only to numeric types, if column is DATE or VARCHAR the numeric accessor is just ignored. If your implementation doesn't expose the provider specific type, make the extension method on IDbCommand or DbCommand and then safe cast the DbDataReader to OracleDataReader.
EDIT: Added the hack for GetFieldType method. But it might happen that the static mapping hashtable might be updated so this could have unwanted effects. You need to test it properly. The fieldTypes array holds the types returned for all columns of the data reader.

Related

Attempting to prevent SQL injection when referencing an Oracle Package dynamically with JPA

I've gone down a bit of a path and hit a wall with how this could be possibly achieved.
Basically, a query is constructed using JPA and passed to an Oracle DB. On the DB there is a Package, used to generate a reference, and this is dynamically named, based on the environment. This value is user-editable, and stored as a DB property within the application. I don't have any control over the architecture of this.
At a pre-JPA stage, a Query String is generated using the reference value for the Package, which is set as a property (again, I can't change the way this has been designed). I set this up using the Query method setParameter(), like so:
(pseudocode replacing the irrelevant parts for focused context)
String referenceRef = [ reference is fetched from DB properties ];
String queryString = "SELECT ?1 FROM sys.dual";
final Query myQuery = getEntityManager().createNativeQuery( queryString );
myQuery.setParameter( 1, referenceRef );
return myQuery.getSingleResult();
I pretty much did this as a reflex, only to realise (in retrospec, quite obviously) that this won't actually work, as it is escaping the element that should not be escaped...
So, where the referenceRef = "DynamicallyNamedPackage.DoThisDynamicallyNamedThing", the above code will just return "DynamicallyNamedPackage.DoThisDynamicallyNamedThing", as it is obviously making it safe, and the point of doing so is, to a certain extent, the antethesis of what I'm trying to do.
Is it possible to achieve this without creating a whole chunk of additional code? All I can currently think of, as an alternative, is to query dba_procedures for all package objects that match, and using the result of that query to construct the queryString (hence circumnavigating using any user-editable values), but it feels like it's going to be convoluted. This is the alternative, which I am using in lieu of an improvement:
final String verifyReference = "SELECT object_name FROM "
+ "dba_procedures WHERE object_type = 'PACKAGE' AND object_name =?1";
final Query refQuery = getEntityManager().createNativeQuery( verifyReference );
refQuery.setParameter( 1, referenceRef );
final String result = refQuery.getSingleResult();
final String queryString = "SELECT " + result + " FROM sys.dual";
final Query myQuery = getEntityManager().createNativeQuery( queryString );
return myQuery.getSingleResult();
It will essentially look up the user-editable property reference against a list of existing packages, then use the result of that query for building the original reference. It has more null checking and so on involved, and does remove the vulnerability, but feels a bit 'unpolished'.
(As has already been mentioned in the comments, this sort of is designed to need a SQL injection, but needs to prevent "SQL Injection" as a definition of not allowing the DB to be manipulated outside of the design by using an unintended value.)
The Oracle dictionary view all_procedures contains a list of all procedures accessible to the current user.
Specifically in the view there are columns OWNER, OBJECT_NAME (=package name), PROCEDURE_NAME.
You may use this view to sanitize the configured input by simple adding an EXISTS subquery such as:
select
?
from dual where exists (
select null from all_procedures
where
OWNER||'.'||OBJECT_NAME||'.'||PROCEDURE_NAME = upper(?) and
object_type = 'PACKAGE');
You will have to bind twice the same input parameter.
The query returns no data if there is not procedure with the given name, so you may raise an exception.
The query above expects a full qualified stored procedure name, i.e. owner.package.procedure, you'll have to adapt it slightly if you allow unqualified names (without the owner).

Determine whether a SAS dataset is a table or view

I'm trying to determine, given a SAS dataset's name, whether it is a table or view.
The context is that I have a data step where I iterate over a list of dataset names, and if the dataset is a table (and not a view) I'd like to perform a call execute to a sql procedure which drops the table whose name is specified. As it stands now, the code works as intended but throws several warnings of the form
WARNING: File WORK.datasetname.DATA does not exist.
Here is the code I'm using:
data _null_;
set work.ds_list;
tbl_loc = scan(tbl_name,1,'.');
if(tbl_loc = 'WORK') then do;
drop_string = catx(' ',
'proc sql; drop table',
tbl_name,
'; quit;');
call execute (drop_string);
put ' ** Queueing call to drop table ' tbl_name;
end;
run;
So how do I determine from the dataset's name whether it is a view or table?
Thanks!
The function EXIST function will help you here.
if exist(tbl_name,'DATA') then memtype = 'TABLE'; else
if exist(tbl_name,'VIEW') then memtype = 'VIEW';
drop_statements = catx
( ' ',
'proc sql; drop', memtype, tbl_name, '; quit;'
);
From Docs
Syntax
EXIST(member-name <, member-type <, generation>>)
Required Argument
member-name
is a character constant, variable, or expression that specifies the
SAS library member. If member-name is blank or a null string, then
EXIST uses the value of the LAST system variable as the member name.
Optional Arguments
member-type
is a character constant, variable, or expression that specifies the
type of SAS library member. A few common member types include ACCESS,
CATALOG, DATA, and VIEW. If you do not specify a member-type, then the
member type DATA is assumed.
Rather than 'create it' how about using SASHELP.VTABLE to determine if it's a VIEW or DATA.
data temp /view=temp;
set sashelp.class;
run;
data check;
set sashelp.vtable;
where libname='WORK';
run;
Note that the memtype in this case is VIEW. You could probably join your data set to the table as well or do some form of lookup, but a join would be pretty straightforward.
Then once you have the data sets, you can use a PROC DATASETS to drop them all at once rather than one at a time. You don't indicate what initially created this list, but how that list is created is important and could possibly simplify this a lot.
proc datasets lib=work;
delete temp / memtype=view;
run;quit;
so - you'd like to delete all datasets, but not views, from a library?
Simply use the (documented) delete procedure:
proc delete lib=work data=_all_ (memtype=data) ;
run;

mirth connect use of executeUpdateAndGetGeneratedKeys with Oracle

I am using Mirth Connect 3.5.0.8232. I have created a persisted connection to an Oracle database and using it throughout my source and destination connectors. One of the methods Mirth provides for talking with the database is executeUpdateAndGetGeneratedKeys. It would be quite useful for insert statements that would return the primary keys for the inserted rows.
My question is - how do you specify WHICH columns to return? Running the provided function works, but returns ROWID in the CachedRowSet, which is not what I want.
As far as I understood, which columns to return depends on the type of the database, and every database behaves differently. I am interested in Oracle specifically.
Thank you.
The executeUpdateAndGetGeneratedKeys method uses the Statement.RETURN_GENERATED_KEYS flag to signal to the driver that auto-generated keys should be returned. However, from the Oracle docs:
If key columns are not explicitly indicated, then Oracle JDBC drivers cannot identify which columns need to be retrieved. When a column name or column index array is used, Oracle JDBC drivers can identify which columns contain auto-generated keys that you want to retrieve. However, when the Statement.RETURN_GENERATED_KEYS integer flag is used, Oracle JDBC drivers cannot identify these columns. When the integer flag is used to indicate that auto-generated keys are to be returned, the ROWID pseudo column is returned as key. The ROWID can be then fetched from the ResultSet object and can be used to retrieved other columns.
So instead, try using their suggestion of passing in a column name array to prepareStatement:
var dbConn;
try {
dbConn = DatabaseConnectionFactory.createDatabaseConnection('oracle.jdbc.driver.OracleDriver','jdbc:oracle:thin:#localhost:1521:DBNAME','user','pass');
// Create a Java String array directly
var keyColumns = java.lang.reflect.Array.newInstance(java.lang.String, 1);
keyColumns[0] = 'id';
var ps = dbConn.getConnection().prepareStatement('INSERT INTO tablename (columnname) VALUES (?)', keyColumns);
try {
// Set variables here
ps.setObject(1, 'test');
ps.executeUpdate();
var result = ps.getGeneratedKeys();
result.next();
var generatedKey = result.getObject(1);
logger.info(generatedKey);
} finally {
ps.close();
}
} finally {
if (dbConn) {
dbConn.close();
}
}

How can I cast a Integer to a String in EJBQL

I got a Entity with a Integer
#Entity(name=customer)
public Customer {
...
#Column
private int number;
...
//getter,setter
}
Now I want to cast this Integer in a query to compare it with an other value.
I tried this Query:
"SELECT c FROM customer c WHERE CAST(c.number AS TEXT) LIKE '1%'"
But it doesn't work.
This works in some of my code using Hibernate:
SELECT c FROM customer c WHERE STR(c.number) LIKE '1%'
In general, this is what the Hibernate docs (14.10 Expressions) say on casting:
str() for converting numeric or temporal values to a readable string
cast(... as ...), where the second argument is the name of a Hibernate
type, and extract(... from ...) if ANSI cast() and extract() is
supported by the underlying database
Since EJB3 EJBQL has been (almost) replaced by JPQL. In EJBQL and according to http://docs.oracle.com/cd/E11035_01/kodo41/full/html/ejb3_langref.html in JPQL as well there is no functionality to CAST a property of an entity.
So like I already told there are two options left:
Use a native Query.
Add special cast methods to the entities.
You need to specify the column you're selecting from table alias c, and since EJBQL doesn't support a cast function, pass a string into the query instead of text. (This effectively allows your program to do the cast before it gets to EJBQL.)
The example below is in SQL Server, but should give you the idea:
declare #numberText as varchar(50)
set #numberText = '1'
SELECT c.CustomerNumber FROM customer c
WHERE c.CustomerNumber LIKE #numbertext + '%'
So instead of private int number use private string numberText.
NOTE: I edited this answer after OP confirmed EJBQL does not support a CAST function.

Calling a function with user defined type parameters (Oracle ODP.NET)

I'm using a function :
fu_has_permissions(udt_person('johny','superman'),'fly_away')
udt_person is a user defined type :
create or replace TYPE udt_person AS OBJECT
(name VARCHAR2(3),
id VARCHAR2(18));
I want to use bind variables whan calling this function, but i'm not really sure what am i doing wrong ... Here's the code :
......
OracleParameter udtPersParam = new OracleParameter();
udtPersParam.ParameterName = ":pUdtPers";
udtPersParam.UdtTypeName = "UDT_PERS";
string[] paramValues = { name, id };
udtPersParam.Value = paramValues;
OracleParameter pAction = new OracleParameter(":pAction", OracleDbType.Varchar2, 255);
pAction.Value = action;
parameters.Add(udtPartParam);
parameters.Add(pAction);
try
{
_loginOdr = DBFacade.ExecuteSelectQuery("select fu_has_permissions(:pUdtPart, :pAction) from dual", parameters);
}
Thanks!
the udt type must be a class that implements
IOracleCustomType
and/or IOracleCustomTypeFactory, IOracleArrayTypeFactory
unfortuntately you cannot just create a string array and pass it in
look in the odp.net samples that come with the odp installation
%ora_home%\client_1\odp.net\samples\4\UDT
also check out these links for samples and walkthroughs
http://developergeeks.com/article/35/working-with-user-defined-type-oracle-udt-and-custom-type-using-odp-net-11g
http://www.codeproject.com/KB/database/ORACLE_UDT.aspx
and
http://st-curriculum.oracle.com/obe/db/hol08/dotnet/udt/udt_otn.htm
Don't know anything about ODP.Net really, but the error suggests that it doesn't like you trying to use a string array as the value for an Oracle parameter. Which doesn't sound unreasonable.
A quick google of 'odp.net object varchar2' gave this OTN forum post as the first result; it includes an example of using an object about half-way down, including converting to and from Oracle object types.
if I were you I'd have a look at the ODP add-in to Visual Studio. With this you can connect to your database, select a UDT on the database, and "Generate Custom Class" to get a .net class you can use.
Look inside the class and you'll see what Harrison means. Pay particular attention to the OracleObjectMappingAttributes on top of the properties, and the overrides of To/FromCustomObject.
When you construct your OracleCommand, the OracleParameter.Value needs to be a class of this type.
That should get you started at a high level. A word of warning, though. The code generated by ODP is ugly to say the least - we're on the point of ditching all ODP-generated classes in our own scenario. But you'll need to understand what things like IOracleCustomType, IOracleCustomTypeFactory, IOracleArrayTypeFactory, INullable are before you'll be in a position to do this.
Incidentally since your specific question surrounds arrays, you might want to look at Oracle NTYPEs here, rather than TYPEs.

Resources