I've a problem calling a MobileFirst SQL adapter to retrieve result from an Oracle Select-where-IN statement.
My environment is MobileFirst v7.0
The adapter definition is:
sqlGetResultsStatement = WL.Server.createSQLStatement ("select * from table where field IN (?)");
function getResults(param) {
return WL.Server.invokeSQLStatement({
preparedStatement : sqlGetResultsStatement,
parameters : [param]
});
}
If I call adapter with a single value in parameter (e.g. '0001') all works fine, and I obtain results. But if I call adapter with this type of parameter: "'0001','0002','0003'" I obtain an empty-resultset (w/o errors) response like this
{
"isSuccessful": true,
"resultSet": [
]
}
Is there something in the call that is wrong?
You cannot pass in a set of values to a single prepared statement parameter. One of the key features of prepared statements is that it helps prevent SQL injection attacks and thus it makes sense that when there is one parameter inside of the statement it is considered as a single value, that's why it encloses the value you passed in with quotes "'0001','0002','0003'". Also, MobileFirst doesn't allow you to create prepared statements inside if functions in the JS adapters and therefore you cannot modify the number of parameters when the procedure is invoked. With that being said there are two approaches you can take to accomplish this.
Javascript Adapter
Determine the maximum number of parameters that you will ever pass to this procedure and add the parameters to the prepared statement beforehand. Let's say you are never going to pass more than 10 parameters, then I would use something like:
var MAX_PARAMS = 10;
sqlGetResultsStatement = WL.Server.createSQLStatement ("select * from table where field IN (?,?,?,?,?,?,?,?,?,?)");
function getResults(param) {
/*
* (arguments) is an array of parameters passed to the procedure
*/
return WL.Server.invokeSQLStatement({
preparedStatement : sqlGetResultsStatement,
parameters : fillVars(arguments)
});
}
// helper function to fill all the values for the SQL Statement Parameters
function fillVars(vars) {
var list = [];
for(var i = 0; i < MAX_PARAMS; i++) {
if(vars.length >= i + 1) {
list.push(vars[i]);
} else {
// some value that will not be in your db
list.push(null);
}
}
return list;
}
Java Adapter The other option would be to use a Java Adapter and connect to your DB directly and write your own queries/prepared statements. FYI: this option will give you more flexibility but you will have to include DB driver jar files and write all the DB connection/querying logic, etc.
Related
My channel is receiving HL7 messages and I have 2 transformers in my channel. I am capturing all the data from the HL7 message in one transformer like:
- var vACCNo= msg['PID']['PID.17']['PID.17.1'].toString();
- var vSTATE=msg['PID']['PID.11']['PID.11.4'].toString();
....
In the second transformer I am pushing all this data into a external DB as insert statement like insert into table x values (vACCNo, vSTATE....).
In the above design without doing anything data captured in first transformer is available in second and it works.
Now I am planning to get rid of these 2 transformers and move these into code templates, where I'm planning to create a separate function for each of these transformer.
But how I can pass variables captured in first function to second one?
Thanks
When you say 2 transformers, I assume you mean two steps in the same transformer? Different transformer steps compile into the same javascript function, so they share the same variable context/scope. To actually pass values to a different transformer (like from your source transformer to a destination transformer) normally you would use the channelMap for this.
In your (presumed) situation, you can add all of your variables to an object that you return from the first function. Pass the object to the second function.
Code Templates
function getValues(msg) {
var fieldWithComplicatedAssignment = '';
var result = {
vACCNo: msg['PID']['PID.17']['PID.17.1'].toString(),
vSTATE: msg['PID']['PID.11']['PID.11.4'].toString(),
fieldWithComplicatedAssignment: fieldWithComplicatedAssignment
};
if (optionalCondition) {
result.optionalField = '';
}
return result;
}
function insertIntoDB(obj) {
// insert into table x values (obj.vACCNo, obj.vSTATE....)
// return a result status indicating succeeded or failure (or
// just throw an error from this function)
}
Transformer steps
var obj = getValues(msg);
var result = insertIntoDb(obj);
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();
}
}
I'm working on a LINQ provider that uses the IQ Toolkit to tranlate LINQ queries to SQL queries. Are the classes provided by the IQ Toolkit safe from SQL injection attacks? If not, what I have to do to protect against SQL injection attacks, supposing that I'm using the IQ Toolkit and implementing my own LINQ provider. I read the LINQ to SQL uses SqlParameter,
but it's still not clear to me what needs to be done with SqlParameter to protect against SQL injection.
From the blog post it looks like IQ toolkit (or the initial version of the toolkit) is not safe from SQL injection attacks. But you can verify it by yourself - execute a query, capture the generated SQL and see if there are parameters used.
If you want to build your own provider, you must know that it is not that easy. Consider things like nested select, nested where, etc. There are great blog posts on this topic.
But you are interested in protecting your database against SQL injection. So if you look at the sample code on this page and the VisitConstant method, that's the place where you run into constants of value type (string, int, etc.) or IQueryable.
Protection against SQL injections is not complicated, you just create new SQLParameter or you call method DbProviderFactory.CreateParameter described here. You will need some collection to store your parameters while you are traversing the expression tree. So the modified code will look like this:
protected override Expression VisitConstant(ConstantExpression c) {
IQueryable q = c.Value as IQueryable;
if (q != null) {
// assume constant nodes w/ IQueryables are table references
sb.Append("SELECT * FROM ");
sb.Append(q.ElementType.Name);
}
else if (c.Value == null) {
sb.Append("NULL");
}
else {
switch (Type.GetTypeCode(c.Value.GetType())) {
case TypeCode.Boolean:
param = dbProvider.CreateParameter();
param.Name = "#param" + paramsList.Count;
param.Value = (((bool)c.Value) ? 1 : 0;
paramsList.Add(param);
sb.Append(param.Name);
break;
case TypeCode.String:
param = dbProvider.CreateParameter();
param.Name = "#param" + paramsList.Count;
param.Value = c.Value; // you don't have to care about escaping or formatting
paramsList.Add(param);
sb.Append(param.Name);
break;
...
case TypeCode.Object:
throw new NotSupportedException(string.Format("The constant for '{0}' is not supported", c.Value));
default:
sb.Append(c.Value);
break;
}
}
return c;
}
So while you are travesing the expression tree, you are building the SQL string and collecting the SQL parameters.
I want to do something like
from table1
where col5="abcd"
select col1
I did like
query_ = From g In DomainService.GetGEsQuery Select New GE With {.Desc = g.codDesc}
"This cause a runtime error, i tried various combinations but failed"
please help.
I'm assuming your trying to do this on the client side. If so you could do something like this
DomainService.Load(DomainService.GetGEsQuery().Where(g => g.codDesc == "something"), lo =>
{
if (lo.HasError == false)
{
List<string> temp = lo.Entities.Select(a => a.Name).ToList();
}
}, null);
you could also do this in the server side (which i would personally prefer) like this
public IQueryable<string> GetGEStringList(string something)
{
return this.ObjectContext.GE.Where(g => g.codDesc == something).Select(a => a.Name);
}
Hope this helps
DomainService.GetGEsQuery() returns an IQueryable, that is only useful in a subsequent asynchronous load. Your are missing the () on the method call, but that is only the first problem.
You can apply filter operations to the query returned using Where etc, but it still needs to be passed to the Load method of your domain context (called DomainService in your example).
The example Jack7 has posted shows an anonymous callback from the load method which then accesses the results inside the load object lo and extracts just the required field with another query. Note that you can filter the query in RIA services, but not change the basic return type (i.e. you cannot filter out unwanted columns on the client-side).
Jack7's second suggestion to implement a specific method server-side, returning just the data you want, is your best option.
How to use variable mapping while using Oracle OLE DB provider? I have done the following:
Execute SQL Task: Full result set to hold results of the query.
Foreach ADO Enumerator: ADO object source above variable (Object data type).
Variable Mapping: 1 field.
The variable is setup as Evaluate as an Express (True)
Data Flow: SQL Command from variable, as SELECT columnName FROM table where columnName = ?
Basically what I am trying to do is use the results of a query from a SQL Server table, (ie ..account numbers) and pull records from Oracle reference the results from the SQL query
It feels like you're mixing items. The Parameterization ? is a placeholder for a variable which, in an OLE DB Source component, you'd click on the Parameters button and map.
However, since you're using the SQL Command from a Variables, that doesn't allow you to use the Parameterization option, probably because the risk of a user changing the shape of the result set, via Expressions, is too high.
So, pick one - either "SQL Command" with proper parametetization or "SQL Command from Variable" where you add in your parameters in terrible string building fashion like Dynamically assign value to variable in SSIS SQL Server 2005/2008/2008R2 people, be aware that you are limited to 4k characters in a string variable that uses Expressions.
Based on the comment of "Basically what I am trying to do is use the results of a query from a SQL Server table, (ie ..account numbers) and pull records from Oracle reference the results from the SQL query"
There's two ways of going about this. With what you've currently developed, my above answer still stands. You are shredding the account numbers and using those as the filter in your query to Oracle. This will issue a query to Oracle for each account number you have. That may or may not be desirable.
The upside to this approach is that it will allow you to retrieve multiple rows. Assuming you are pulling Sales Order type of information, one account number likely has many sales order rows.
However, if you are working with something that has a zero to one mapping with the account numbers, like account level data, then you can simplify the approach you are taking. Move your SQL Server query to an OLE DB Source component within your data flow.
Then, what you are looking for is the Lookup Component. That allows you to enrich an existing row of data with additional data. Here you will specify a query like "SELECT AllTheColumnsICareAbout, AccountNumber FROM schema.Table ". Then you will map the AccountNumber from the OLE DB Source to the one in the Lookup Component and the click the checkmark next to all the columns you want to augment the existing row with.
I believe what you are asking is how to use SSIS to push data to Oracle OleDb provider.
I will assume that Oracle is the destination. The idea of using data destinations with variable columns is not supported out of the box. You should be able to use the SSIS API or other means, I take a simpler approach.
I recently set up a package to get all tables from a database and create dynamic CSV output. One file for each table. You could do something similar.
Switch out the streamwriter part with a section to 1. Create the table in destination. 2. Insert records into Oracle. I am not sure if you will need to do single inserts to Oracle. In another project that works in reverse, dynamic csv into SQL. SInce I work with SQL server, I load a datatable and use SQLBulkCopy class to use bulk loading which provides excellent performance.
public void Main()
{
string datetime = DateTime.Now.ToString("yyyyMMddHHmmss");
try
{
string TableName = Dts.Variables["User::CurrentTable"].Value.ToString();
string FileDelimiter = ",";
string TextQualifier = "\"";
string FileExtension = ".csv";
//USE ADO.NET Connection from SSIS Package to get data from table
SqlConnection myADONETConnection = new SqlConnection();
myADONETConnection = (SqlConnection)(Dts.Connections["connection manager name"].AcquireConnection(Dts.Transaction) as SqlConnection);
//Read data from table or view to data table
string query = "Select * From [" + TableName + "]";
SqlCommand cmd = new SqlCommand(query, myADONETConnection);
//myADONETConnection.Open();
DataTable d_table = new DataTable();
d_table.Load(cmd.ExecuteReader());
//myADONETConnection.Close();
string FileFullPath = Dts.Variables["$Project::ExcelToCsvFolder"].Value.ToString() + "\\Output\\" + TableName + FileExtension;
StreamWriter sw = null;
sw = new StreamWriter(FileFullPath, false);
// Write the Header Row to File
int ColumnCount = d_table.Columns.Count;
for (int ic = 0; ic < ColumnCount; ic++)
{
sw.Write(TextQualifier + d_table.Columns[ic] + TextQualifier);
if (ic < ColumnCount - 1)
{
sw.Write(FileDelimiter);
}
}
sw.Write(sw.NewLine);
// Write All Rows to the File
foreach (DataRow dr in d_table.Rows)
{
for (int ir = 0; ir < ColumnCount; ir++)
{
if (!Convert.IsDBNull(dr[ir]))
{
sw.Write(TextQualifier + dr[ir].ToString() + TextQualifier);
}
if (ir < ColumnCount - 1)
{
sw.Write(FileDelimiter);
}
}
sw.Write(sw.NewLine);
}
sw.Close();
Dts.TaskResult = (int)ScriptResults.Success;
}
catch (Exception exception)
{
// Create Log File for Errors
//using (StreamWriter sw = File.CreateText(Dts.Variables["User::LogFolder"].Value.ToString() + "\\" +
// "ErrorLog_" + datetime + ".log"))
//{
// sw.WriteLine(exception.ToString());
//}
Dts.TaskResult = (int)ScriptResults.Failure;
throw;
}
Dts.TaskResult = (int)ScriptResults.Success;