Code highlighting

Showing posts with label Bug. Show all posts
Showing posts with label Bug. Show all posts

Thursday, February 23, 2012

Tip: Illegal 'closing bracket' character when defining a macro

Just a very quick tip today, related to macros:


As you all know, there are multiple ways to define and use macros in X++.
For those that need a refresher, please look up the corresponding section on MSDN
(Direct link: http://msdn.microsoft.com/en-us/library/cc197107.aspx)

Below is a simple X++ job, that demonstrates an existing shortcoming in the #define command, and a possible workaround for this problem.

Nothing complicated, basically, just use #localmacro, if you can't compile your code.

static void ClosingBracketInMacroDefinition(Args _args)
{
    //#define.Question("Why are brackets ')' not working ?")
    //#define.Question(@"Why are brackets ')' not working ?")
    //#define.Question("Why are brackets '\)' not working ?")
    #define.LegalCharacters(' !"#$%&\'(*+,-./:;<=>?@[\\]^_`{|}~\n\r\t')
    #localmacro.Question
        "Why are brackets ')' not working ?"
    #endmacro

    Box::info(#Question);
    Box::info(#LegalCharacters);
}

Thanks for finding the issue to Bogdana, one of our new developers.

Thursday, April 23, 2009

Be careful with join clauses when writing complex queries

Join clause evaluation is dependent on its position in the sql statement. In SQL Management Studio, if you try to specify the conditions incorrectly, you will receive the following error message:

SQLstatement

In X++, you will not receive any compilation errors, nor any runtime errors. In complex scenarios with a lot of queries, this might go unnoticed, and will be extremely hard to weed out at a later stage, when data inconsistencies crawl in.

Take, as an example, the following job. At first glance, the 2 methods look exactly the same, and it seems as if they should work just fine. But in reality, the second method will return incorrect results, because inventSum.InventDimId will be treated as a constant (empty string as the default value) and not as a table field used in the same select statement.

QueryBuild classes have a major advantage in this situation, as you cannot easily add join clauses (links) unless adding to the child (joined) queryBuildDataSource.

static void JoinClauseWarningJob(Args _args)
{
#define.ItemId("ESB-005")

void testCorrectJoin()
{
InventTable inventTable;
InventSum inventSum;
InventDim inventDim;

select inventTable
where inventTable.ItemId == #ItemId
join inventSum
where inventSum.ItemId == inventTable.ItemId
join inventDim
where inventDim.inventDimId == inventSum.InventDimId;

info(strfmt("Correct join where clause: %1", inventSum.AvailPhysical));
}

void testIncorrectJoin()
{
InventTable inventTable;
InventSum inventSum;
InventDim inventDim;

select inventTable
where inventTable.ItemId == #ItemId
join inventDim
where inventDim.inventDimId == inventSum.InventDimId
join inventSum
where inventSum.ItemId == inventTable.ItemId;

info(strfmt("Incorrect join where clause: %1", inventSum.AvailPhysical));
}
// Actually execute the code
testCorrectJoin();
testIncorrectJoin();
}

So, the suggestion is simple: Use QueryBuild classes (or AOT queries) whenever possible, and pay attention to the order of tables and join clauses in the select statements that you write.

Wednesday, June 27, 2007

A bug in validation of methods' access modifiers and class abstract modifier

While testing the new feature of Tabax plugins today, discovered a bug in access modifiers validation.

It is performed only at compile time.
Which means, that if the compiler doesn't know what object the method belogns to, than the validation is skipped.
And you can run protected and private methods from whereever your code is executed.

Here is a simple example: (press cancel in the dialogs, otherwise you will get a RunTime error, because the method pack is not implemented in RunBase)

static void tutorial_AccessModifiersError(Args _args)
{
    Object runBaseObj;
    SysDictClass dictClass;
    ;
    runBaseObj = RunBase::makeObject(classNum(RunBase));
    runBaseObj.setInPrompt(true);
    runBaseObj.promptPrim();
    runBaseObj.setInPrompt(false);

    dictClass = new SysDictClass(classNum(RunBase));
    dictClass.callObject(methodStr(RunBase, promptPrim), dictClass.makeObject());
}

setInPrompt and promptPrim are both private methods. so they should not be allowed to be called this way.

The same thing can be achived by using DictClass.

The funny thing is:
while writing a test example for this post, I stumbled upon another bug - now it's about the abstract modifier of a class.

You all are probably aware that RunBase is an abstract class and cannot be initialized.
Well, in the example I showed this is done using 2 methods again :)

I guess someone should register these with Microsoft.