Jump to content

Lars Fosdal

Administrators
  • Content Count

    3323
  • Joined

  • Last visited

  • Days Won

    110

Posts posted by Lars Fosdal


  1. A rough draft from the top of my head.

    I've made the two query types look a little different from what they probably actually are to point out how the encapsulation hides the differences.
    Your actual wrapper will be different, and the number of methods that you need to wrap depends on the various TDataSet descendants and how you use them now.

    The wrapper exposes the properties and methods I need.
    
    type
      TxQuery<T: TDataSet> = class abstract
       private
         FiQuery: T;
       protected
         procedure SetSQL(aValue: string); ; virtual; abstract;
         function GetSQL: string; virtual; abstract;
         function GetDataSet: TDataSet; virtual; abstract;
         property iQuery: T read FiQuery write FiQuery;
       public
         procedure Execute; virtual; abstract;
         property SQL: string read GetSQL write SetSQL;
         property DataSet: TDataSet read GetDataSet;
      end
      TxQueryClass = class of TxQuery;
    
      TxOraQuery = class TxQuery<TOracleDataset>
      protected
        procedure SetSQL(aValue: string); override;
        function GetSQL: string; override;
        function GetDataSet: TDataSet; virtual; abstract;
      end;
    
      TxPgQuery = class TxQuery<TPgQuery>
      protected
        procedure SetSQL(aValue: string); override;
        function GetSQL: string; override;
         function GetDataSet: TDataSet; virtual; abstract;
      end;
      
    
    procedure TxOraQuery.SetSQL(aValue: string); 
    begin
      iQuery.SQL.Text := aValue;
    end;
    
    function TxOraQuery.GetSQL: string; 
    begin
      Result := iQuery.SQL.Text;
    end;
    
    function TxOraQuery.GetDataSet: TDataSet;
    begin
      Result := iQuery;
    end;
    
    
    procedure TxPgQuery.SetSQL(aValue: string); 
    begin
      iQuery.SQL := aValue;
    end;
    
    function TxPgQuery.GetSQL: string; 
    begin
      Result := iQuery.SQL;
    end;
    
    function TxPgQuery.GetDataSet: TDataSet;
    begin
      Result := iQuery.DataSet;
    end;
    
    type
      TForm1 = class(TForm)
      var
        QryClass: TxQueryClass;
        procedure FormCreate;
        procedure TestQuery(aQuery: TxQuery; aSQL: string);
      end;
    
    procedure TForm1.FormCreate;
    begin
      if Config = Pg
      then QryClass := TxPgQuery
      else QryClass := TxOraQuery;
    end;
    
    procedure TForm1.TestQuery;
    var
      Qry: TxQuery;
      SQL: string;
    begin
      Qry := QryClass.Create;
      Proc1(Qry, aSQL);
    end;
    
    procedure TForm1.Proc1(aQuery: TxQuery; aSQL: string);
    begin
      aQuery.DataSet.Close;
      aQuery.SQL := aSQL;
      aQuery.DataSet.Open;
      ...
    end;

     


  2. Thanks guys, Good observations.

    The dates in question are set to whatever FireDAC returns from a SQL Server datetime field, which typically appears to be 0 for NULL.

    The set date range is always in the future, relatively speaking. If the date is less than today, it is an expired dairy product by definition. We don't sell those 😉 

     

    I'll change the requirement for blank dates to be <= 1.0 to be on the safe side.


  3. At least that confirms that I am not blind. EMBT has fumbled on the doc here.


    Too bad as I have I wanted to use a custom converter to take a Json stream that have a mixed type list and convert it to/from a single object in Delphi, but I didn't have the patience and aptitude to dig that deep. 

     {

      "list": [

        5,

        "thing",

        {"object":"type"}

      ]

    }

     

    It is the Message structure from the Google+Exporter Json
    https://docs.google.com/document/d/1gOYJe61sI1GbO9qpFZJtwY3vdsZalsxtPgUnB2cvzAw/edit


  4. Consider this pseudo code

    uses
      Rest.Json;
    
    TDateClass = class
    private
      FHasDate: TDateTime;
      FNoDate: TDateTIme;
    public
      constructor Create;
      property HasDate: TDateTime read FHasDate write FHasDate;
      property NoDate: TDateTime read FNoDate write FNoDate;
    end;
    
    constructor TDateClass.Create;
    begin
      HasDate := Now;
      NoDate := 0;
    end;
    
    var
      Json: String;
      DateClass: TDateClass;
    begin
      DateClass := TDateClass.Create;
      Json := TJson.ObjectToJsonString(Self, [joIgnoreEmptyStrings, joIgnoreEmptyArrays, joDateIsUTC, joDateFormatISO8601]);
    

    which results in the Json string looking like

    {
                  "HasDate":"2019-02-14T06:09:00.000Z",
                  "NoDate":"1899-12-30T00:00:00.000Z", 
    }

    while the ideal result would be

    {
                  "HasDate":"2019-02-14T06:09:00.000Z",
    }

    Q:Is there a way to make TDateTime properties with a zero value be output as an empty string - and hence be stripped?


  5. We do all data change via SPs of which some do not do explicit transaction handling.

    The current SP call handling will detect if the call was a deadlock victim and rerun it if necessary - but that basically escalates the risk of yet another deadlock.

     

    The bigger sites have nearly a hundred pickers - so the odds for concurrent access = inevitable.
    We need to identify where we do not need exclusive access and explicitly use NoLock where appropriate.
    We need to identify where transactions will be feasible / required.

    We need to identify a best practice for resource allocation. 

     

    The challenge is that the rules are complex and the dataset is dynamic (pallets are emptied and replaced many, many times during a day).

    f.x. get the best pick position for n items of article x with date requirement y which has the best fit for our pick route and least incoming traffic.
    To further complicate it, different clients have different rules for date requirements due to longer transports or specific rules per article for shorter requirements due to high turnover.  The list of these varying parameters go on and on.

    If two clients make the same request at the same time - one has to be the winner of what may be a scarce resource, while the other needs a viable plan b.
    The pick request may consume the remaining items on one pallet, and then grab the rest from the next pallet - if there is one.

    To further complicate it, the allocation may need to be split onto multiple deliveries to multiple clients within the pick order.

     

    What is blatantly clear is that we currently are not doing it the optimal way.

     


  6. 17 hours ago, Remy Lebeau said:

    In some piece of code somewhere, a pointer was set to $DEADBEEF, most likely after the object it was pointing to had been freed, and then that same pointer was used afterwards by code running at address $0040CEEC trying to access a data member at offset -8 ($DEADBEE7) via that pointer.  Magic addresses like $DEADBEEF are commonly used for debugging such mistakes.

    [deletia]

    1

    Excellent analysis, Remy. 

    As I often do, I did not ask the right question - which should have been: What sets dead memory references to $DEADxxxx?  RTL? EurekaLog? OS?


  7. Does anyone recognize that seemingly hardcoded address?

    The call stack indicates that it happens in Rio 10.3.1 source - but searching the source does not yield anything.
    Can it be Eurekalog that tries to tell me something about referencing a deleted or inaccessible piece of memory?

     

    #BAD: EAccessViolation: Access violation at address 0040CEEC in module 'MyApp.exe'. Read of address DEADBEE7.
    DBServer\INDUSTRIELL\DBName # 3017235 20.03.2019 13:23:06 (Brukerinterface, CRITICAL)
    MyApp_ClientId @ [0040CEEC] System._UStrAsg
    __________ CallStack
    [0040CEEC] System._UStrAsg
    [01561AF4] System.Generics.Collections.pas.{GridSet}TGridSet.TFieldValue<System.string>.SetValue (Line 4745, "System.Generics.Collections.pas")
    [0156241F] System.Generics.Collections.pas.{GridSet}TGridSet.TFieldValue<System.string>.CopyRow (Line 4745, "System.Generics.Collections.pas")
    [01558452] GridSet.TGridSet.CopyRow (Line 1002, "GridSet.pas")
    [01B92885] frmPSDExpeditionDeliveries2.TDeliverySet.UpdateFromDeliverySet (Line 4108, "frmPSDExpeditionDeliveries2.pas")
    [01B8F919] frmPSDExpeditionDeliveries2.TDeliveryGrid.RefreshByRouteList (Line 3671, "frmPSDExpeditionDeliveries2.pas")

     


  8. On 3/16/2019 at 2:40 PM, jobo said:

    - handling the «grabbing» might be easier, quicker, more robust, if handled by a single, atomic Grab Stored Procedure (considering the denormalisation)»


    That's why I'd like to find an SQL group to get some hints to best practices

     

    - which version is in use?

    2008r2, 2012, 2016
     

    - could you elaborate «..the actual grabbing is totally void of transactions..» please?

    Some of the current code does not do explicit transaction handling at all.
     

     

    - or describe the workflow a little?


    That would become very detailed, very quickly

     

    2

     


  9. Ref. paths and VCS systems...


    We use standard paths 

      C:\SomeFolder\Projects

       Common

          ThisCollection

          ThatCollection
       External

          Lib1

          Lib2
       Proj1
       Proj2

     

    We also use a $BIN environment var to direct the project outputs.  Allows the team members great freedom for where to put their source, and where to direct the output.

     

    and try to ensure that all project search or include paths are on this form ..\Common\ThisCollection
    Those are stored in the .dproj which can be committed to the VCS.

     

    That also allows me to check out various versions

       Projects        <- my typical working branch switched between trunk/pilot/live

       Projects.Rio  <- while still on Tokyo, but testing Rio

       Projects.Trunk 

       Projects.Pilot

       Projects.Live

      

     

    • Like 1

  10. Somewhat off topic, but related.
    Ctr+-F12 gives you a list of units to open, and I assumed it was from the search paths and project paths, 

    Turns out that it also will look at the .dpr uses statement.

     

    Ideally, the paths should be relative...

    uses

      SomeUnit in '..\RelativeFolder\SomeUnit.pas'

     

    But, I had a stale test project in my project group where the uses path used an explicit path

    uses

      SomeUnit in 'c:\Obsolete.Path\to\RelativeFolder\SomeUnit.pas'

     

    So, pressing Ctrl+F12 gave me both of these - even if Obsolete.Path didn't exist.

    • Like 1

  11. On 3/12/2019 at 12:44 PM, Hans J. Ellingsgaard said:

    There is at NOLOCK isolationlevel, that will minimize risk of locking, but it will not be suitable, if there is a risk of data being changed during the execution of the query. Another thing to do is to make sure that all the fields in the where clause and the joins are indexed to avoid tablescans.

    4

    I usually limit NOLOCK to queries for
    - data that is used as viewing info (i.e. not acted upon)
    - data that are currently in the custody of the client (i.e. not likely to be changed by other processes)


  12. 21 hours ago, azrael_11 said:

    I plan in the future to be multi platform so what is the best way  to do that?

     

    Thank you.

    For Windows, the norm would be to install the font. AddFontResource could be used, but as you demonstrated, it doesn't seem to work as intended?
    For MacOS, I would also assume that installing the font is recommended.
    I've not used custom fonts with FMX on Android or iOS, so I can't answer that.

    • Like 1

  13. 21 hours ago, Rudy Velthuis said:

    That is something that needs to be addressed too, certainly, but it would not be on the top of my list.

    I literally do that failing Generics Ctrl-Click several times a day.  I guess I am a slow learner 😛

    I also wish that the Insight mechanism would deduce the most likely class types in scope and let me select one as jump target, and not ALWAYS send me to the virtual/abstract declarations of the base class. 

×