How can I get EclipseLink to output valid Informix SQL for an UPDATE WHERE clause? - jpa-2.0

We have a named query like this:
UPDATE Foo f SET f.x = 0 WHERE f.x = :invoiceId
Foo in this case is an entity with a superclass, using the table-per-class inheritance strategy.
The SQL that EclipseLink generates is:
UPDATE foo_subclass SET x = ?
WHERE EXISTS(SELECT t0.id
FROM foo_superclass t0, foo_subclass t1
WHERE ((t1.x = ?) AND ((t1.id = t0.id) AND (t0.DTYPE = ?)))
(The ? slots are correctly filled in.)
On Informix 11.70, we get an error that the subquery cannot access the table being changed.
Here is the documentation that I was able to find on subquery restrictions on Informix: http://publib.boulder.ibm.com/infocenter/idshelp/v115/index.jsp?topic=%2Fcom.ibm.sqls.doc%2Fids_sqs_2005.htm
Other databases also feature restrictions on subqueries like this, so although this is manifesting as an Informix issue, I'm sure that if we ran this against, say, MySQL, we would get a similar error.
How can I get EclipseLink to honor these restrictions? Is there a better query I should be using?

Instead of:
UPDATE foo_subclass SET x = ?
WHERE EXISTS(SELECT t0.id
FROM foo_superclass t0, foo_subclass t1
WHERE ((t1.x = ?) AND ((t1.id = t0.id) AND (t0.DTYPE = ?)))
do this:
UPDATE
foo_subclass SET x = ?
WHERE
foo_subclass.x = ? AND
EXISTS(SELECT t0.id
FROM foo_superclass t0
WHERE ((foo_subclass.id = t0.id) AND (t0.DTYPE = ?))
Note that on 11.50 you cannot use alias for foo_subclass. It's allowed in 11.70.
I'm assuming "id" are primary keys (or at least unique identifiers).

Found the answer. Looks like EclipseLink had to handle this case for MySQL, which also has similar issues.
The answer is that in your InformixPlatform subclass, you need to override the following methods to solve this problem:
supportsLocalTemporaryTables(): this needs to return true
shouldAlwaysUseTempStorageForModifyAll(): this needs to return true
dontBindUpdateAllQueryUsingTempTables needs to return true
getCreateTempTableSqlPrefix(): this needs to return CREATE TEMP TABLE
getCreateTempTableSqlSuffix(): this needs to return WITH NO LOG
isInformixOuterJoin(): needs to return false
getTempTableForTable(DatabaseTable): this needs to do this:
return new DatabaseTable("TL_" + table.getName(), "" /* no table qualifier */, table.shouldUseDelimiters(), this.getStartDelimiter(), this.getEndDelimiter());
In addition, you need to override the following methods as well for proper InformixPlatform behavior:
appendBoolean(Boolean, Writer): the stock Informix platform does not write out boolean literals properly. Yours needs to do this:
if (Boolean.TRUE.equals(booleanValue)) {
writer.write("'t'");
} else {
writer.write("'f'");
}
You need to override writeUpdateOriginalFromTempTableSql so that it contains the same code as the H2Platform's override does:
#Override
public void writeUpdateOriginalFromTempTableSql(final Writer writer, final DatabaseTable table, final Collection pkFields, final Collection assignedFields) throws IOException {
writer.write("UPDATE ");
final String tableName = table.getQualifiedNameDelimited(this);
writer.write(tableName);
writer.write(" SET ");
final int size = assignedFields.size();
if (size > 1) {
writer.write("(");
}
writeFieldsList(writer, assignedFields, this);
if (size > 1) {
writer.write(")");
}
writer.write(" = (SELECT ");
writeFieldsList(writer, assignedFields, this);
writer.write(" FROM ");
final String tempTableName = this.getTempTableForTable(table).getQualifiedNameDelimited(this);
writer.write(tempTableName);
writeAutoJoinWhereClause(writer, null, tableName, pkFields, this);
writer.write(") WHERE EXISTS(SELECT ");
writer.write(((DatabaseField)pkFields.iterator().next()).getNameDelimited(this));
writer.write(" FROM ");
writer.write(tempTableName);
writeAutoJoinWhereClause(writer, null, tableName, pkFields, this);
writer.write(")");
}
Lastly, your constructor needs to call this.setShouldBindLiterals(false).
With these changes, it seems that Informix is happy.

Related

ASP NET MVC Core 2 with entity framework saving primery key Id to column that isnt a relationship column

Please look at this case:
if (product.ProductId == 0)
{
product.CreateDate = DateTime.UtcNow;
product.EditDate = DateTime.UtcNow;
context.Products.Add(product);
if (!string.IsNullOrEmpty(product.ProductValueProduct)) { SaveProductValueProduct(product); }
}
context.SaveChanges();
When I debug code the ProductId is negative, but afer save the ProductId is corect in database (I know this is normal), but when I want to use it here: SaveProductValueProduct(product) after add to context.Products.Add(product); ProductId behaves strangely. (I'm creating List inside SaveProductValueProduct)
List<ProductValueProductHelper> pvpListToSave = new List<ProductValueProductHelper>();
foreach (var item in dProductValueToSave)
{
ProductValueProductHelper pvp = new ProductValueProductHelper();
pvp.ProductId = (item.Value.HasValue ? item.Value : product.ProductId).GetValueOrDefault();
pvp.ValueId = Convert.ToInt32(item.Key.Remove(item.Key.IndexOf(",")));
pvp.ParentProductId = (item.Value.HasValue ? product.ProductId : item.Value);
pvpListToSave.Add(pvp);
}
I want to use ProductId for relationship. Depends on logic I have to save ProductId at ProductId column OR ProductId at ParentProductId column.
The ProductId 45 save corent at ProductId column (3rd row - this is a relationship), BUT not at ParentProductId (2nd row - this is just null able int column for app logic), although while debug I pass the same negative value from product.ProductId there.
QUESTION:
how can I pass correct ProductId to column that it isn't a relationship column
I just use SaveProductValueProduct(product) after SaveChanges (used SaveChanges twice)
if (product.ProductId == 0)
{
product.CreateDate = DateTime.UtcNow;
product.EditDate = DateTime.UtcNow;
context.Products.Add(product);
}
context.SaveChanges();
if (product.ProductId != 0)
{
if (!string.IsNullOrEmpty(product.ProductValueProduct)) { SaveProductValueProduct(product); }
context.SaveChanges();
}
I set this as an answer because it fixes the matter, but I am very curious if there is any other way to solve this problem. I dont like to use context.SaveChanges(); one after another.

HBase Get/Scan in a Scalding job

I'm using Scalding with Spyglass to read from/write to HBase.
I'm doing a left outer join of table1 and table2 and write back to table1 after transforming a column.
Both table1 and table2 are declared as Spyglass HBaseSource.
This works fine. But, i need to access a different row in table1 using rowkey to compute transformed value.
I tried the following for HBase get:
val hTable = new HTable(conf, TABLE_NAME)
val result = hTable.get(new Get(rowKey.getBytes()))
I'm getting access to Configuration in Scalding job as mentioned in this link:
https://github.com/twitter/scalding/wiki/Frequently-asked-questions#how-do-i-access-the-jobconf
This works when i run the scalding job locally.
But, when i run it in cluster, conf is null when this code is executed in Reducer.
Is there a better way to do HBase get/scan in a Scalding/Cascading job for cases like this?
Ways to do this...
1) You can use a managed resource
class SomeJob(args: Args) extends Job(args) {
val someConfig = HBaseConfiguration.create().addResource(new Path(pathtoyourxmlfile))
lazy val hPool = new HTablePool(someConfig, 3)
def getConf = {
implicitly[Mode] match {
case Hdfs(_, conf) => conf
case _ => whateveryou are doing for a local conf...
}
}
... somePipe.someOperation.... {
val gets = key.map { key => new Get(key) }
managed(hPool.getTable("myTableName")) acquireAndGet { table =>
val results = table.get(gets)
...do something with these results
}
}
}
2) You can use some more specific cascading code, where you write a custom scheme and inside that you will override the source method and possibly some others depending on your needs. In there you can access the JobConf like this:
class MyScheme extends Scheme[JobConf, SomeRecordReader, SomeOutputCollector, ..] {
#transient var jobConf: Configuration = super.jobConfiguration
override def source(flowProcess: FlowProcess[JobConf], ...): Boolean = {
jobConf = flowProcess match {
case h: HadoopFlowProcess => h.getJobConf
case _ => jconf
}
... dosomething with the jobConf here
}
}

How do I query multiple IDs via the ContentSearchManager?

When I have an array of Sitecore IDs, for example TargetIDs from a MultilistField, how can I query the ContentSearchManager to return all the SearchResultItem objects?
I have tried the following which gives an "Only constant arguments is supported." error.
using (var s = Sitecore.ContentSearch.ContentSearchManager.GetIndex("sitecore_master_index").CreateSearchContext())
{
rpt.DataSource = s.GetQueryable<SearchResultItem>().Where(x => f.TargetIDs.Contains(x.ItemId));
rpt.DataBind();
}
I suppose I could build up the Linq query manually with multiple OR queries. Is there a way I can use Sitecore.ContentSearch.Utilities.LinqHelper to build the query for me?
Assuming I got this technique to work, is it worth using it for only, say, 10 items? I'm just starting my first Sitecore 7 project and I have it in mind that I want to use the index as much as possible.
Finally, does the Page Editor support editing fields somehow with a SearchResultItem as the source?
Update 1
I wrote this function which utilises the predicate builder as dunston suggests. I don't know yet if this is actually worth using (instead of Items).
public static List<T> GetSearchResultItemsByIDs<T>(ID[] ids, bool mustHaveUrl = true)
where T : Sitecore.ContentSearch.SearchTypes.SearchResultItem, new()
{
Assert.IsNotNull(ids, "ids");
if (!ids.Any())
{
return new List<T>();
}
using (var s = Sitecore.ContentSearch.ContentSearchManager.GetIndex("sitecore_master_index").CreateSearchContext())
{
var predicate = PredicateBuilder.True<T>();
predicate = ids.Aggregate(predicate, (current, id) => current.Or(p => p.ItemId == id));
var results = s.GetQueryable<T>().Where(predicate).ToDictionary(x => x.ItemId);
var query = from id in ids
let item = results.ContainsKey(id) ? results[id] : null
where item != null && (!mustHaveUrl || item.Url != null)
select item;
return query.ToList();
}
}
It forces the results to be in the same order as supplied in the IDs array, which in my case is important. (If anybody knows a better way of doing this, would love to know).
It also, by default, ensures that the Item has a URL.
My main code then becomes:
var f = (Sitecore.Data.Fields.MultilistField) rootItem.Fields["Main navigation links"];
rpt.DataSource = ContentSearchHelper.GetSearchResultItemsByIDs<SearchResultItem>(f.TargetIDs);
rpt.DataBind();
I'm still curious how the Page Editor copes with SearchResultItem or POCOs in general (my second question), am going to continue researching that now.
Thanks for reading,
Steve
You need to use the predicate builder to create multiple OR queries, or AND queries.
The code below should work.
using (var s = Sitecore.ContentSearch.ContentSearchManager.GetIndex("sitecore_master_index").CreateSearchContext())
{
var predicate = PredicateBuilder.True<SearchResultItem>();
foreach (var targetId in f.Targetids)
{
var tempTargetId = targetId;
predicate = predicate.Or(x => x.ItemId == tempTargetId)
}
rpt.DataSource = s.GetQueryable<SearchResultItem>().Where(predicate);
rpt.DataBind();
}

Doctrine 2 query builder vs entity persist performance

Summary: which is quicker: updating / flushing a list of entities, or running a query builder update on each?
We have the following situation in Doctrine ORM (version 2.3).
We have a table that looks like this
cow
wolf
elephant
koala
and we would like to use this table to sort a report of a fictional farm. The problem is that the user wishes to have a customer ordering of the animals (e.g. Koala, Elephant, Wolf, Cow). Now there exist possibilities using CONCAT, or CASE to add a weight to the DQL (example 0002wolf, 0001elephant). In my experience this is either tricky to build and when I got it working the result set was an array and not a collection.
So, to solve this we added a "weight" field to each record and, before running the select, we assign each one with a weight:
$animals = $em->getRepository('AcmeDemoBundle:Animal')->findAll();
foreach ($animals as $animal) {
if ($animal->getName() == 'koala') {
$animal->setWeight(1);
} else if ($animal->getName() == 'elephant') {
$animal->setWeight(2);
}
// etc
$em->persist($animal);
}
$em->flush();
$query = $em->createQuery(
'SELECT c FROM AcmeDemoBundle:Animal c ORDER BY c.weight'
);
This works perfectly. To avoid race conditions we added this inside a transaction block:
$em->getConnection()->beginTransaction();
// code from above
$em->getConnection()->rollback();
This is a lot more robust as it handles multiple users generating the same report. Alternatively the entities can be weighted like this:
$em->getConnection()->beginTransaction();
$qb = $em->createQueryBuilder();
$q = $qb->update('AcmeDemoBundle:Animal', 'c')
->set('c.weight', $qb->expr()->literal(1))
->where('c.name = ?1')
->setParameter(1, 'koala')
->getQuery();
$p = $q->execute();
$qb = $em->createQueryBuilder();
$q = $qb->update('AcmeDemoBundle:Animal', 'c')
->set('c.weight', $qb->expr()->literal(2))
->where('c.name = ?1')
->setParameter(1, 'elephant')
->getQuery();
$p = $q->execute();
// etc
$query = $em->createQuery(
'SELECT c FROM AcmeDemoBundle:Animal c ORDER BY c.weight'
);
$em->getConnection()->rollback();
Questions:
1) which of the two examples would have better performance?
2) Is there a third or better way to do this bearing in mind we need a collection as a result?
Please remember that this is just an example - sorting the result set in memory is not an option, it must be done on the database level - the real statement is a 10 table join with 5 orderbys.
Initially you could make use of a Doctrine implementation named Logging (\Doctrine\DBAL\LoggingProfiler). I know that it is not the better answer, but at least you can implement it in order to get best result for each example that you have.
namespace Doctrine\DBAL\Logging;
class Profiler implements SQLLogger
{
public $start = null;
public function __construct()
{
}
/**
* {#inheritdoc}
*/
public function startQuery($sql, array $params = null, array $types = null)
{
$this->start = microtime(true);
}
/**
* {#inheritdoc}
*/
public function stopQuery()
{
echo "execution time: " . microtime(true) - $this->start;
}
}
In you main Doctrine configuration you can enable as:
$logger = new \Doctrine\DBAL\Logging\Profiler;
$config->setSQLLogger($logger);

Using Conversion Studio by To-Increase to import Notes into Microsoft Dynamics AX 2009

Currently, I'm using Conversion Studio to bring in a CSV file and store the contents in an AX table. This part is working. I have a block defined and the fields are correctly mapped.
The CSV file contains several comments columns, such as Comments-1, Comments-2, etc. There are a fixed number of these. The public comments are labeled as Comments-1...5, and the private comments are labeled as Private-Comment-1...5.
The desired result would be to bring the data into the AX table (as is currently working) and either concatenate the comment fields or store them as separate comments into the DocuRef table as internal or external notes.
Would it not require just setting up a new block in the Conversion Studio project that I already have setup? Can you point me to a resource that maybe shows a similar procedure or how to do this?
Thanks in advance!
After chasing the rabbit down the deepest of rabbit holes, I discovered that the easiest way to do this is like so:
Override the onEntityCommit method of your Document Handler (that extends AppDataDocumentHandler), like so:
AppEntityAction onEntityCommit(AppDocumentBlock documentBlock, AppBlock fromBlock, AppEntity toEntity)
{
AppEntityAction ret;
int64 recId; // Should point to the record currently being imported into CMCTRS
;
ret = super(documentBlock, fromBlock, toEntity);
recId = toEntity.getRecord().recId;
// Do whatever you need to do with the recId now
return ret;
}
Here is my method to insert the notes, in case you need that too:
private static boolean insertNote(RefTableId _tableId, int64 _docuRefId, str _note, str _name, boolean _isPublic)
{
DocuRef docuRef;
boolean insertResult = false;
;
if (_docuRefId)
{
try
{
docuRef.clear();
ttsbegin;
docuRef.RefCompanyId = curext();
docuRef.RefTableId = _tableId;
docuRef.RefRecId = _docuRefId;
docuRef.TypeId = 'Note';
docuRef.Name = _name;
docuRef.Notes = _note;
docuRef.Restriction = (_isPublic) ? DocuRestriction::External : DocuRestriction::Internal;
docuRef.insert();
ttscommit;
insertResult = true;
}
catch
{
ttsabort;
error("Could not insert " + ((_isPublic) ? "public" : "private") + " comment:\n\n\t\"" + _note + "\"");
}
}
return insertResult;
}