Changeset 13 for trunk


Ignore:
Timestamp:
Mar 17, 2015, 12:09:11 AM (9 years ago)
Author:
chronos
Message:
  • Modified: Now records are loaded from SQL database using SQL query. The query is parsed also by XML database client to load records from XML file.
  • Added: Packages Network and Synapse.
Location:
trunk
Files:
54 added
1 deleted
14 edited

Legend:

Unmodified
Added
Removed
  • trunk/DbEngines/UEngineMySQL.pas

    r12 r13  
    1414  TDatabaseMySQL = class(TDatabaseClient)
    1515  private
     16    procedure LoadFields(Table: TTable);
    1617    procedure LoadTables;
    1718  public
    1819    SqlDatabase: TSqlDatabase;
     20    procedure Query(DbRows: TDbRows; Text: string); override;
    1921    constructor Create; override;
    2022    destructor Destroy; override;
     
    2729{ TDatabaseMySQL }
    2830
     31procedure TDatabaseMySQL.LoadFields(Table: TTable);
     32var
     33  DbRows: TDbRows;
     34  NewField: TField;
     35  I: Integer;
     36  DataType: Integer;
     37begin
     38  DbRows := TDbRows.Create;
     39  try
     40    SqlDatabase.Query(DbRows, 'SELECT * FROM `ModelField` WHERE `Model` = ' + IntToStr(Table.Id) + '');
     41    for I := 0 to DbRows.Count - 1 do begin
     42      NewField := TField.Create;
     43      NewField.Table := Table;
     44      NewField.Name := TDictionaryStringString(DbRows[I]).Values['Name'];
     45      NewField.TextBefore := TDictionaryStringString(DbRows[I]).Values['Title'];
     46      DataType := StrToInt(TDictionaryStringString(DbRows[I]).Values['DataType']);
     47      NewField.DataType := Table.Database.Engine.DataTypes.FindByType(TFieldType(DataType));
     48      if not Assigned(NewField.DataType) then
     49        NewField.DataType := Table.Database.Engine.DataTypes.FindByType(ftString);
     50      Table.Fields.Add(NewField);
     51    end;
     52  finally
     53    DbRows.Free;
     54  end;
     55end;
     56
    2957procedure TDatabaseMySQL.LoadTables;
    3058var
    3159  DbRows: TDbRows;
     60  DbRows2: TDbRows;
    3261  NewTable: TTable;
    3362  I: Integer;
     
    3564  DbRows := TDbRows.Create;
    3665  try
    37     SqlDatabase.Query(DbRows, 'SELECT `TABLE_NAME` FROM `information_schema`.`TABLES` WHERE `TABLE_SCHEMA` = "' + SqlDatabase.Database + '"');
     66    SqlDatabase.Query(DbRows, 'SELECT `Id`,`Name`,`Title` FROM `Model`');
    3867    for I := 0 to DbRows.Count - 1 do begin
    3968      NewTable := TTable.Create;
     69      NewTable.Id := StrToInt(TDictionaryStringString(DbRows[I]).Values['Id']);
    4070      NewTable.Database := Database;
    41       NewTable.Name := (TDictionaryStringString(DbRows[I])[0]).Value;
    42       NewTable.Caption := NewTable.Name;
     71      NewTable.Name := TDictionaryStringString(DbRows[I]).Values['Name'];
     72      NewTable.Caption := TDictionaryStringString(DbRows[I]).Values['Title'];
     73      LoadFields(NewTable);
     74      DbRows2 := TDbRows.Create;
     75      try
     76        SqlDatabase.Query(DbRows2, 'SELECT COUNT(*) FROM `' + NewTable.Name + '`');
     77        if DbRows2.Count = 1 then
     78          NewTable.RecordsCount := StrToInt(DbRows2[0].Values['COUNT(*)']);
     79      finally
     80        DbRows2.Free;
     81      end;
    4382      Database.Tables.Add(NewTable);
    4483    end;
     
    4685    DbRows.Free;
    4786  end;
     87end;
     88
     89procedure TDatabaseMySQL.Query(DbRows: TDbRows; Text: string);
     90begin
     91  SqlDatabase.Query(DbRows, Text);
    4892end;
    4993
  • trunk/DbEngines/UEngineXML.pas

    r12 r13  
    77uses
    88  Classes, SysUtils, DOM, XMLRead, XMLWrite, UDatabase, UXMLUtils, FileUtil,
    9   UHtmlClasses;
     9  UHtmlClasses, USqlDatabase, SpecializedDictionary;
    1010
    1111type
     
    2929    procedure LoadFromFile(FileName: string);
    3030    procedure SaveToFile(FileName: string);
     31    function GetNextPart(var Text: string): string;
    3132  public
     33    procedure Query(DbRows: TDbRows; Text: string); override;
    3234    procedure Load; override;
    3335    procedure Save; override;
     
    280282end;
    281283
     284function TDatabaseXML.GetNextPart(var Text: string): string;
     285begin
     286  if Pos(' ', Text) > 0 then begin
     287    Result := Trim(Copy(Text, 1, Pos(' ', Text) - 1));
     288    Delete(Text, 1, Pos(' ', Text));
     289  end else begin
     290    Result := Text;
     291    Text := '';
     292  end;
     293end;
     294
     295procedure TDatabaseXML.Query(DbRows: TDbRows; Text: string);
     296var
     297  Command: string;
     298  Columns: string;
     299  TableName: string;
     300  Table: TTable;
     301  NewRecord: TDictionaryStringString;
     302  I: Integer;
     303  F: Integer;
     304begin
     305  Command := GetNextPart(Text);
     306  if Command = 'SELECT' then begin
     307    Columns := GetNextPart(Text);
     308    Command := GetNextPart(Text);
     309    if Command = 'FROM' then begin
     310      TableName := GetNextPart(Text);
     311    end;
     312    Table := Database.Tables.SearchByName(TableName);
     313    if Assigned(Table) then begin
     314      DbRows.Count := 0;
     315      if Columns = '*' then begin
     316        for I := 0 to Table.Records.Count - 1 do begin
     317          NewRecord := TDictionaryStringString.Create;
     318          for F := 0 to Table.Fields.Count - 1 do
     319            NewRecord.Add(TField(Table.Fields[F]).Name, TValue(TRecord(Table.Records[I]).Values[I]).GetString);
     320          DbRows.Add(NewRecord);
     321        end;
     322      end else
     323      if Columns = 'COUNT(*)' then begin
     324        NewRecord := TDictionaryStringString.Create;
     325        NewRecord.Add('COUNT(*)', IntToStr(Table.Records.Count));
     326        DbRows.Add(NewRecord);
     327      end else raise Exception.Create('Unsupported columns ' + Columns + ' specification');
     328    end else raise Exception.Create('Table ' + TableName + ' not found.');
     329  end else raise Exception.Create('Unsupported SQL command ' + Command);
     330end;
     331
    282332procedure TDatabaseXML.Load;
    283333begin
  • trunk/Forms/UFormRecord.lfm

    r8 r13  
    11object FormRecord: TFormRecord
    22  Left = 639
    3   Height = 694
    4   Top = 174
    5   Width = 859
     3  Height = 649
     4  Top = 223
     5  Width = 858
    66  Caption = 'FormRecord'
    7   ClientHeight = 694
    8   ClientWidth = 859
     7  ClientHeight = 649
     8  ClientWidth = 858
    99  OnCreate = FormCreate
    1010  OnDestroy = FormDestroy
     
    1414  object Panel1: TPanel
    1515    Left = 4
    16     Height = 632
     16    Height = 583
    1717    Top = 4
    18     Width = 851
     18    Width = 850
    1919    Align = alTop
     20    Anchors = [akTop, akLeft, akRight, akBottom]
    2021    BorderSpacing.Around = 4
    2122    BevelOuter = bvNone
     
    2324  end
    2425  object ButtonOk: TButton
    25     Left = 760
     26    Left = 759
    2627    Height = 25
    27     Top = 648
     28    Top = 603
    2829    Width = 75
    2930    Anchors = [akRight, akBottom]
     
    3334  end
    3435  object ButtonCancel: TButton
    35     Left = 649
     36    Left = 648
    3637    Height = 27
    37     Top = 646
     38    Top = 601
    3839    Width = 72
    3940    Anchors = [akRight, akBottom]
  • trunk/Forms/UFormRecords.lfm

    r11 r13  
    11object FormRecords: TFormRecords
    22  Left = 621
    3   Height = 433
    4   Top = 424
    5   Width = 859
     3  Height = 549
     4  Top = 308
     5  Width = 897
    66  Caption = 'Records'
    7   ClientHeight = 433
    8   ClientWidth = 859
     7  ClientHeight = 549
     8  ClientWidth = 897
    99  OnShow = FormShow
    1010  Position = poMainFormCenter
     
    1212  object ListView1: TListView
    1313    Left = 4
    14     Height = 389
     14    Height = 505
    1515    Top = 4
    16     Width = 851
     16    Width = 889
    1717    Align = alClient
    1818    BorderSpacing.Around = 4
     
    3939    Left = 0
    4040    Height = 36
    41     Top = 397
    42     Width = 859
     41    Top = 513
     42    Width = 897
    4343    Align = alBottom
    4444    ButtonHeight = 32
  • trunk/Forms/UFormRecords.pas

    r11 r13  
    147147  NewColumn: TListColumn;
    148148begin
     149  Table.LoadRecords;
    149150  ListView1.Columns.Clear;
    150151  for I := 0 to Table.Fields.Count - 1 do begin
  • trunk/Forms/UFormTables.pas

    r11 r13  
    7777  with TTable(Database.Tables[Item.Index]) do begin
    7878    Item.Caption := Caption;
    79     Item.SubItems.Add(IntToStr(Records.Count));
     79    Item.SubItems.Add(IntToStr(RecordsCount));
    8080    Item.Data := Database.Tables[Item.Index];
    8181  end
     
    215215
    216216procedure TFormTables.ReloadList;
    217 begin
     217var
     218  I: Integer;
     219begin
     220  for I := 0 to Database.Tables.Count - 1 do
     221    TTable(Database.Tables[I]).LoadRecordsCount;
    218222  if Assigned(Database) then begin
    219223    ListView1.Items.Count := Database.Tables.Count;
  • trunk/Languages/MyData.cs.po

    r12 r13  
    241241
    242242#: tformpreferences.buttonsave.caption
     243msgctxt "tformpreferences.buttonsave.caption"
    243244msgid "Save"
    244245msgstr "Uložit"
     
    257258msgstr "Jazyk:"
    258259
     260#: tformrecord.acancel.caption
     261msgctxt "tformrecord.acancel.caption"
     262msgid "Cancel"
     263msgstr "Zrušit"
     264
     265#: tformrecord.asave.caption
     266msgctxt "tformrecord.asave.caption"
     267msgid "Save"
     268msgstr "Uložit"
     269
     270#: tformrecord.buttoncancel.caption
     271msgctxt "tformrecord.buttoncancel.caption"
     272msgid "Cancel"
     273msgstr "Zrušit"
     274
     275#: tformrecord.buttonok.caption
     276msgctxt "tformrecord.buttonok.caption"
     277msgid "Ok"
     278msgstr "Ok"
     279
     280#: tformrecord.caption
     281msgid "FormRecord"
     282msgstr ""
     283
    259284#: tformrecords.aadd.caption
    260285msgctxt "tformrecords.aadd.caption"
     
    403428#: usqldatabase.sdatabasequeryerror
    404429msgid "Database query error: \"%s\""
    405 msgstr ""
    406 
     430msgstr "Chyba požadavku databáze: \"%s\""
  • trunk/Languages/MyData.po

    r12 r13  
    232232
    233233#: tformpreferences.buttonsave.caption
     234msgctxt "tformpreferences.buttonsave.caption"
    234235msgid "Save"
    235236msgstr ""
     
    248249msgstr ""
    249250
     251#: tformrecord.acancel.caption
     252msgctxt "TFORMRECORD.ACANCEL.CAPTION"
     253msgid "Cancel"
     254msgstr ""
     255
     256#: tformrecord.asave.caption
     257msgctxt "TFORMRECORD.ASAVE.CAPTION"
     258msgid "Save"
     259msgstr ""
     260
     261#: tformrecord.buttoncancel.caption
     262msgctxt "TFORMRECORD.BUTTONCANCEL.CAPTION"
     263msgid "Cancel"
     264msgstr ""
     265
     266#: tformrecord.buttonok.caption
     267msgctxt "TFORMRECORD.BUTTONOK.CAPTION"
     268msgid "Ok"
     269msgstr ""
     270
     271#: tformrecord.caption
     272msgid "FormRecord"
     273msgstr ""
     274
    250275#: tformrecords.aadd.caption
    251276msgctxt "tformrecords.aadd.caption"
  • trunk/MyData.lpi

    r12 r13  
    6868      </local>
    6969    </RunParams>
    70     <RequiredPackages Count="6">
     70    <RequiredPackages Count="8">
    7171      <Item1>
     72        <PackageName Value="synapse"/>
     73        <DefaultFilename Value="Packages/synapse/synapse.lpk" Prefer="True"/>
     74      </Item1>
     75      <Item2>
     76        <PackageName Value="Network"/>
     77        <DefaultFilename Value="Packages/Network/Network.lpk" Prefer="True"/>
     78      </Item2>
     79      <Item3>
    7280        <PackageName Value="CoolWeb"/>
    7381        <DefaultFilename Value="Packages/CoolWeb/CoolWeb.lpk" Prefer="True"/>
    74       </Item1>
    75       <Item2>
     82      </Item3>
     83      <Item4>
    7684        <PackageName Value="TemplateGenerics"/>
    7785        <DefaultFilename Value="Packages/TemplateGenerics/TemplateGenerics.lpk" Prefer="True"/>
    78       </Item2>
    79       <Item3>
     86      </Item4>
     87      <Item5>
    8088        <PackageName Value="CoolTranslator"/>
    8189        <DefaultFilename Value="Packages/CoolTranslator/CoolTranslator.lpk" Prefer="True"/>
    82       </Item3>
    83       <Item4>
     90      </Item5>
     91      <Item6>
    8492        <PackageName Value="FCL"/>
    85       </Item4>
    86       <Item5>
     93      </Item6>
     94      <Item7>
    8795        <PackageName Value="Common"/>
    8896        <DefaultFilename Value="Packages/Common/Common.lpk" Prefer="True"/>
    89       </Item5>
    90       <Item6>
     97      </Item7>
     98      <Item8>
    9199        <PackageName Value="LCL"/>
    92       </Item6>
     100      </Item8>
    93101    </RequiredPackages>
    94102    <Units Count="17">
     
    129137        <HasResources Value="True"/>
    130138        <ResourceBaseClass Value="Form"/>
    131         <UnitName Value="UFormTable"/>
    132139      </Unit5>
    133140      <Unit6>
     
    153160        <HasResources Value="True"/>
    154161        <ResourceBaseClass Value="Form"/>
    155         <UnitName Value="UFormFields"/>
    156162      </Unit8>
    157163      <Unit9>
     
    161167        <HasResources Value="True"/>
    162168        <ResourceBaseClass Value="Form"/>
    163         <UnitName Value="UFormField"/>
    164169      </Unit9>
    165170      <Unit10>
     
    184189        <HasResources Value="True"/>
    185190        <ResourceBaseClass Value="Form"/>
    186         <UnitName Value="UFormMain"/>
    187191      </Unit13>
    188192      <Unit14>
     
    192196        <HasResources Value="True"/>
    193197        <ResourceBaseClass Value="Form"/>
    194         <UnitName Value="UFormConnect"/>
    195198      </Unit14>
    196199      <Unit15>
     
    208211        <HasResources Value="True"/>
    209212        <ResourceBaseClass Value="Form"/>
    210         <UnitName Value="UFormPreferences"/>
    211213      </Unit16>
    212214    </Units>
  • trunk/MyData.lpr

    r12 r13  
    1010  Forms, UFormTables, UDatabase, UCore, Common, CoolTranslator, UEngineXML,
    1111  UFormTable, UFormRecords, UFormRecord, UFormFields, UFormField, UDataTypes,
    12   TemplateGenerics, CoolWeb, UEngineMySQL, UEngineSQLite, UFormMain,
     12  TemplateGenerics, CoolWeb, synapse, UEngineMySQL, UEngineSQLite, UFormMain,
    1313  UFormConnect, UFormDatabases, UFormPreferences;
    1414
  • trunk/Packages/CoolWeb/WebServer/UHTTPServerTCP.pas

    r12 r13  
    144144  inherited;
    145145  MaxConnection := 10000;
    146   Socket := TTCPServer.Create;
     146  Socket := TTCPServer.Create(nil);
    147147  Socket.OnClientConnect := HandleClient;
    148148  RequestHandlerList := TRequestHandlerList.Create;
  • trunk/UCore.pas

    r11 r13  
    140140  DataTypes.Clear;
    141141  with DataTypes do begin
    142     RegisterType(STypeString, ftString, TFieldString);
    143     RegisterType(STypeInteger, ftInteger, TFieldInteger);
    144     RegisterType(STypeDateTime, ftDateTime, TFieldDateTime);
    145     RegisterType(STypeBoolean, ftBoolean, TFieldBoolean);
    146     RegisterType(STypeFloat, ftFloat, TFieldFloat);
    147     RegisterType(STypeMapPosition, ftMapPosition, TFieldMapPosition);
    148     RegisterType(STypeDate, ftDate, TFieldDate);
    149     RegisterType(STypeTime, ftTime, TFieldTime);
    150     RegisterType(STypeImage, ftImage, TFieldImage);
     142    RegisterType(1, STypeString, ftString, TFieldString);
     143    RegisterType(2, STypeInteger, ftInteger, TFieldInteger);
     144    RegisterType(3, STypeDateTime, ftDateTime, TFieldDateTime);
     145    RegisterType(4, STypeBoolean, ftBoolean, TFieldBoolean);
     146    RegisterType(5, STypeFloat, ftFloat, TFieldFloat);
     147    RegisterType(6, STypeMapPosition, ftMapPosition, TFieldMapPosition);
     148    RegisterType(7, STypeDate, ftDate, TFieldDate);
     149    RegisterType(8, STypeTime, ftTime, TFieldTime);
     150    RegisterType(9, STypeImage, ftImage, TFieldImage);
    151151  end;
    152152end;
  • trunk/UDataTypes.pas

    r8 r13  
    1515    procedure Assign(Source: TValue); override;
    1616    function GetString: string; override;
     17    function SetString(Value: string): string; override;
    1718  end;
    1819
     
    2324    procedure Assign(Source: TValue); override;
    2425    function GetString: string; override;
     26    function SetString(Value: string): string; override;
    2527  end;
    2628
     
    3133    procedure Assign(Source: TValue); override;
    3234    function GetString: string; override;
     35    function SetString(Value: string): string; override;
    3336  end;
    3437
     
    3942    procedure Assign(Source: TValue); override;
    4043    function GetString: string; override;
     44    function SetString(Value: string): string; override;
    4145  end;
    4246
     
    4751    procedure Assign(Source: TValue); override;
    4852    function GetString: string; override;
     53    function SetString(Value: string): string; override;
    4954  end;
    5055
     
    129134end;
    130135
     136function TValueFloat.SetString(Value: string): string;
     137begin
     138  Self.Value := StrToFloat(Value);
     139end;
     140
    131141{ TFieldBoolean }
    132142
     
    188198end;
    189199
     200function TValueBoolean.SetString(Value: string): string;
     201begin
     202  Self.Value := StrToBool(Value);
     203end;
     204
    190205{ TValueInteger }
    191206
     
    199214begin
    200215  Result := IntToStr(Value);
     216end;
     217
     218function TValueInteger.SetString(Value: string): string;
     219begin
     220  Self.Value := StrToInt(Value);
    201221end;
    202222
     
    229249end;
    230250
     251function TValueDateTime.SetString(Value: string): string;
     252begin
     253  Self.Value := StrToDateTime(Value);
     254end;
     255
    231256{ TValueString }
    232257
     
    242267end;
    243268
     269function TValueString.SetString(Value: string): string;
     270begin
     271  Self.Value := Value;
     272end;
     273
    244274{ TFieldString }
    245275
  • trunk/UDatabase.pas

    r12 r13  
    66
    77uses
    8   Classes, SysUtils, Contnrs, ExtCtrls, StdCtrls, EditBtn, dialogs;
     8  Classes, SysUtils, Contnrs, ExtCtrls, StdCtrls, EditBtn, dialogs, USqlDatabase,
     9  SpecializedDictionary;
    910
    1011type
     
    2324    procedure Assign(Source: TValue); virtual;
    2425    function GetString: string; virtual;
     26    function SetString(Value: string): string; virtual;
    2527  end;
    2628
     
    9496
    9597  TTable = class
     98    Id: Integer;
    9699    Name: string;
    97100    Caption: string;
     
    99102    Fields: TFields;
    100103    Database: TDatabase;
     104    RecordsCount: Integer;
     105    procedure LoadRecords;
     106    procedure LoadRecordsCount;
    101107    procedure Assign(Source: TTable);
    102108    constructor Create;
     
    104110  end;
    105111
     112  { TTables }
     113
    106114  TTables = class(TObjectList)
    107115    Database: TDatabase;
     116    function SearchByName(Name: string): TTable;
    108117  end;
    109118
     
    140149
    141150  TDataType = class
     151    Id: Integer;
    142152    Name: string;
    143153    FieldType: TFieldType;
     
    148158
    149159  TDataTypes = class(TObjectList)
    150     function RegisterType(Name: string; FieldType: TFieldType; FieldTypeClass: TFieldTypeSpecificClass): TDataType;
     160    function RegisterType(Id: Integer; Name: string; FieldType: TFieldType; FieldTypeClass: TFieldTypeSpecificClass): TDataType;
    151161    function FindByType(FieldType: TFieldType): TDataType;
    152162  end;
     
    156166  TDatabaseClient = class
    157167    Database: TDatabase;
     168    procedure Query(DbRows: TDbRows; Text: string); virtual;
    158169    constructor Create; virtual;
    159170    procedure Load; virtual;
     
    194205  UDataTypes;
    195206
     207{ TTables }
     208
     209function TTables.SearchByName(Name: string): TTable;
     210var
     211  I: Integer;
     212begin
     213  I := 0;
     214  while (I < Count) and (TTable(Items[I]).Name <> Name) do Inc(I);
     215  if I < Count then Result := TTable(Items[I])
     216    else Result := nil;
     217end;
     218
    196219{ TDatabases }
    197220
     
    208231{ TDatabaseClient }
    209232
     233procedure TDatabaseClient.Query(DbRows: TDbRows; Text: string);
     234begin
     235end;
     236
    210237constructor TDatabaseClient.Create;
    211238begin
     
    245272{ TDataTypes }
    246273
    247 function TDataTypes.RegisterType(Name: string; FieldType: TFieldType;
     274function TDataTypes.RegisterType(Id: Integer; Name: string; FieldType: TFieldType;
    248275  FieldTypeClass: TFieldTypeSpecificClass): TDataType;
    249276begin
    250277  Result := TDataType.Create;
     278  Result.Id := Id;
    251279  Result.Name := Name;
    252280  Result.FieldType := FieldType;
     
    380408end;
    381409
     410function TValue.SetString(Value: string): string;
     411begin
     412
     413end;
     414
    382415{ TFieldTypeSpecific }
    383416
     
    430463{ TTable }
    431464
     465procedure TTable.LoadRecords;
     466var
     467  DbRows: TDbRows;
     468  I: Integer;
     469  F: Integer;
     470  NewRecord: TRecord;
     471  NewValue: TValue;
     472begin
     473  Records.Clear;
     474  DbRows := TDbRows.Create;
     475  Database.Client.Query(DbRows, 'SELECT * FROM ' + Name);
     476  for I := 0 to DbRows.Count - 1 do begin
     477    NewRecord := TRecord.Create;
     478    for F := 0 to Fields.Count - 1 do begin
     479      NewValue := TField(Fields[F]).GetValueClass.Create;
     480      NewValue.SetString(TDictionaryStringString(DbRows[I]).Values[TField(Fields[F]).Name]);
     481      NewRecord.Values.Add(NewValue);
     482    end;
     483    Records.Add(NewRecord);
     484  end;
     485  DbRows.Free;
     486end;
     487
     488procedure TTable.LoadRecordsCount;
     489var
     490  DbRows: TDbRows;
     491  I: Integer;
     492  F: Integer;
     493  NewRecord: TRecord;
     494  NewValue: TValue;
     495begin
     496  Records.Clear;
     497  DbRows := TDbRows.Create;
     498  Database.Client.Query(DbRows, 'SELECT COUNT(*) FROM ' + Name);
     499  if DbRows.Count = 1 then begin
     500    RecordsCount := StrToInt(TDictionaryStringString(DbRows[0]).Items[0].Value);
     501  end else RecordsCount := 0;
     502  DbRows.Free;
     503end;
     504
    432505procedure TTable.Assign(Source: TTable);
    433506begin
Note: See TracChangeset for help on using the changeset viewer.