ViewVC Help
View File | Revision Log | Show Annotations | Download File | View Changeset | Root Listing
root/public/ibx/trunk/runtime/IBDataOutput.pas
Revision: 47
Committed: Mon Jan 9 15:31:51 2017 UTC (7 years, 10 months ago) by tony
Content type: text/x-pascal
File size: 16392 byte(s)
Log Message:
Committing updates for Release R2-0-1

File Contents

# User Rev Content
1 tony 47 (*
2     * IBX For Lazarus (Firebird Express)
3     *
4     * The contents of this file are subject to the Initial Developer's
5     * Public License Version 1.0 (the "License"); you may not use this
6     * file except in compliance with the License. You may obtain a copy
7     * of the License here:
8     *
9     * http://www.firebirdsql.org/index.php?op=doc&id=idpl
10     *
11     * Software distributed under the License is distributed on an "AS
12     * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
13     * implied. See the License for the specific language governing rights
14     * and limitations under the License.
15     *
16     * The Initial Developer of the Original Code is Tony Whyman.
17     *
18     * The Original Code is (C) 2017 Tony Whyman, MWA Software
19     * (http://www.mwasoftware.co.uk).
20     *
21     * All Rights Reserved.
22     *
23     * Contributor(s): ______________________________________.
24     *
25     *)
26     unit IBDataOutput;
27    
28     {$mode objfpc}{$H+}
29     {$codepage UTF8}
30    
31     (*
32     These are helper components, primarily for use with TIBXScript, but which are
33     also used by TIBExtact (for formatting data as SQL Insert statements). Their
34     purpose is to execute SQL SELECT statements and to format the results of the
35     query. Data Output Formatters are currently available for:
36    
37     Block Format Output (TIBBlockFormatOut)
38     CSV Format (TIBCSVDataOut)
39     SQL Insert Statements (TIBInsertStmtsOut).
40     *)
41     interface
42    
43     uses
44     Classes, SysUtils, IBSQL, IBDatabase, IB;
45    
46     const
47     MaxBlobText = 80;
48    
49     sTimeStampFormat = 'yyyy.mm.dd hh:nn:ss.zzz';
50     sDateFormat = 'yyyy.mm.dd';
51     sTimeFormat = 'hh:nn:ss.zzz';
52    
53     type
54     TPlanOptions = (poNoPlan,poIncludePlan, poPlanOnly);
55    
56     TAdd2Log = procedure(const Msg: string; IsError: boolean=true) of object;
57    
58     { TIBCustomDataOutput }
59    
60     TIBCustomDataOutput = class(TComponent)
61     private
62     FIBSQL: TIBSQL;
63     FIncludeHeader: Boolean;
64     FPlanOptions: TPlanOptions;
65     FRowCount: integer;
66     FShowPerformanceStats: boolean;
67     function GetDatabase: TIBDatabase;
68     function GetTransaction: TIBTransaction;
69     procedure SetDatabase(AValue: TIBDatabase);
70     procedure SetTransaction(AValue: TIBTransaction);
71     protected
72     procedure HeaderOut(Add2Log: TAdd2Log); virtual;
73     procedure FormattedDataOut(Add2Log: TAdd2Log); virtual; abstract;
74     procedure TrailerOut(Add2Log: TAdd2Log); virtual;
75     property IncludeHeader: Boolean read FIncludeHeader write FIncludeHeader default true;
76     public
77     constructor Create(aOwner: TComponent); override;
78     procedure Assign(Source: TPersistent); override;
79     procedure DataOut(SelectQuery: string; Add2Log: TAdd2Log);
80     procedure SetCommand(command, aValue, stmt: string; var Done: boolean); virtual;
81     class procedure ShowPerfStats(Statement: IStatement; Add2Log: TAdd2Log);
82     published
83     property Database: TIBDatabase read GetDatabase write SetDatabase;
84     property Transaction: TIBTransaction read GetTransaction write SetTransaction;
85     property PlanOptions: TPlanOptions read FPlanOptions write FPlanOptions;
86     property RowCount: integer read FRowCount write FRowCount;
87     property ShowPerformanceStats: boolean read FShowPerformanceStats write FShowPerformanceStats;
88     end;
89    
90     TDataOutputFormatter = class of TIBCustomDataOutput;
91    
92     { TIBCSVDataOut }
93    
94     TIBCSVDataOut = class(TIBCustomDataOutput)
95     private
96     FQuoteChar: char;
97     protected
98     procedure HeaderOut(Add2Log: TAdd2Log); override;
99     procedure FormattedDataOut(Add2Log: TAdd2Log); override;
100     public
101     constructor Create(aOwner: TComponent); override;
102     published
103     property IncludeHeader;
104     property QuoteChar: char read FQuoteChar write FQuoteChar default '''';
105     end;
106    
107     { TIBInsertStmtsOut }
108    
109     TIBInsertStmtsOut = class(TIBCustomDataOutput)
110     private
111     FIncludeBlobsAndArrays: boolean;
112     FInsertHeader: string;
113     protected
114     procedure HeaderOut(Add2Log: TAdd2Log); override;
115     procedure FormattedDataOut(Add2Log: TAdd2Log); override;
116     public
117     constructor Create(aOwner: TComponent); override;
118     published
119     property IncludeBlobsAndArrays: boolean read FIncludeBlobsAndArrays
120     write FIncludeBlobsAndArrays default true;
121     end;
122    
123     TAlignments = (taLeft, taCentre, taRight);
124    
125     { TIBBlockFormatOut }
126    
127     TIBBlockFormatOut = class(TIBCustomDataOutput)
128     private
129     FColWidths: array of integer;
130     FRowWidth: integer;
131     function DashedLine: string;
132     function TextAlign(s: string; ColWidth: integer; alignment: TAlignments
133     ): string;
134     protected
135     procedure HeaderOut(Add2Log: TAdd2Log); override;
136     procedure FormattedDataOut(Add2Log: TAdd2Log); override;
137     procedure TrailerOut(Add2Log: TAdd2Log); override;
138     published
139     property IncludeHeader;
140     end;
141    
142     implementation
143    
144     uses IBUtils, FBMessages, Math, IBXScript;
145    
146     { TIBBlockFormatOut }
147    
148     function TIBBlockFormatOut.DashedLine: string;
149     var i: integer;
150     begin
151     Setlength(Result,FRowWidth);
152     for i := 1 to FRowWidth do
153     Result[i] := '-';
154     end;
155    
156     function TIBBlockFormatOut.TextAlign(s: string; ColWidth: integer;
157     alignment: TAlignments): string;
158     begin
159     SetLength(Result,ColWidth);
160     FillChar(Result[1],ColWidth,' ');
161     if Length(s) > ColWidth then
162     s := LeftStr(s,ColWidth);
163     case alignment of
164     taLeft:
165     Move(s[1],Result[1],Length(s));
166     taCentre:
167     Move(s[1],Result[(ColWidth - Length(s)) div 2 + 1],Length(s));
168     taRight:
169     Move(s[1],Result[ColWidth - Length(s) + 1],Length(s));
170     end;
171     end;
172    
173     procedure TIBBlockFormatOut.HeaderOut(Add2Log: TAdd2Log);
174     var i: integer;
175     s: string;
176     begin
177     with FIBSQL do
178     begin
179     {Calculate column Widths}
180     SetLength(FColWidths,MetaData.Count);
181     FRowWidth := 1; {assume leading '|'}
182     for i := 0 to MetaData.Count - 1 do
183     with MetaData[i] do
184     begin
185     case SQLType of
186     SQL_VARYING, SQL_TEXT:
187     FColWidths[i] := GetSize;
188    
189     SQL_DOUBLE, SQL_FLOAT, SQL_D_FLOAT:
190     FColWidths[i] := 18; {see http://www.freepascal.org/docs-html/rtl/sysutils/formatfloat.html}
191    
192     SQL_LONG:
193     if Scale = 0 then
194     FColWidths[i] := 12 {allow for minus sign}
195     else
196     FColWidths[i] := 13; {leave room for the decimal point}
197    
198     SQL_SHORT:
199     if Scale = 0 then
200     FColWidths[i] := 6 {allow for minus sign}
201     else
202     FColWidths[i] := 7; {leave room for the decimal point}
203    
204     SQL_INT64:
205     if Scale = 0 then
206     FColWidths[i] := 20 {allow for minus sign}
207     else
208     FColWidths[i] := 21; {leave room for the decimal point}
209    
210     SQL_TIMESTAMP:
211     FColWidths[i] := 23;
212    
213     SQL_TYPE_DATE:
214     FColWidths[i] := 10;
215    
216     SQL_TYPE_TIME:
217     FColWidths[i] := 12;
218    
219     SQL_BLOB:
220     if SQLSubType = 1 then
221     FColWidths[i] := MaxBlobText
222     else
223     FColWidths[i] := length(SBlob);
224    
225     SQL_ARRAY:
226     FColWidths[i] := length(SArray);
227    
228     SQL_BOOLEAN:
229     FColWidths[i] := Max(Length(STrue),Length(SFalse));
230     end;
231     if FColWidths[i] < Length(Name) then
232     FColWidths[i] := Length(Name);
233     FRowWidth += FColWidths[i] + 1;
234     end;
235    
236     {Now output the header}
237    
238     Add2Log(DashedLine);
239     s := '|';
240     for i := 0 to MetaData.Count - 1 do
241     s += TextAlign(MetaData[i].Name,FColWidths[i],taCentre) + '|';
242     Add2Log(s);
243     Add2Log(DashedLine);
244     end;
245     end;
246    
247     procedure TIBBlockFormatOut.FormattedDataOut(Add2Log: TAdd2Log);
248    
249     function TruncateTextBlob(textStr: string): string;
250     begin
251     if Length(textStr) > MaxBlobText then
252     Result := LeftStr(textStr,MaxBlobText-3) + '...'
253     else
254     Result := textStr;
255     end;
256    
257     var i: integer;
258     s: string;
259     begin
260     s := '|';
261     for i := 0 to FIBSQL.Current.Count - 1 do
262     with FIBSQL.Current[i] do
263     begin
264     if IsNull then
265     s += TextAlign('NULL',FColWidths[i],taCentre)
266     else
267     case SQLType of
268     SQL_VARYING, SQL_TEXT:
269     s += TextAlign(AsString,FColWidths[i],taLeft);
270    
271     SQL_TIMESTAMP:
272     s += TextAlign(FormatDateTime(sTimeStampFormat,AsDateTime),FColWidths[i],taLeft);
273    
274     SQL_TYPE_DATE:
275     s += TextAlign(FormatDateTime(sDateFormat,AsDateTime),FColWidths[i],taLeft);
276    
277     SQL_TYPE_TIME:
278     s += TextAlign(FormatDateTime(sTimeFormat,AsDateTime),FColWidths[i],taLeft);
279    
280     SQL_DOUBLE, SQL_FLOAT, SQL_D_FLOAT,
281     SQL_LONG, SQL_SHORT, SQL_INT64:
282     s += TextAlign(AsString,FColWidths[i],taRight);
283    
284     SQL_BOOLEAN, SQL_ARRAY:
285     s += TextAlign(AsString,FColWidths[i],taCentre);
286    
287     SQL_BLOB:
288     if SQLSubType = 1 then
289     s += TextAlign(TruncateTextBlob(AsString),FColWidths[i],taLeft)
290     else
291     s += TextAlign(sBlob,FColWidths[i],taCentre);
292     end;
293     s += '|';
294     end;
295     Add2Log(s);
296     Add2Log(DashedLine);
297     end;
298    
299     procedure TIBBlockFormatOut.TrailerOut(Add2Log: TAdd2Log);
300     begin
301     Add2Log(DashedLine);
302     end;
303    
304     { TIBInsertStmtsOut }
305    
306     procedure TIBInsertStmtsOut.HeaderOut(Add2Log: TAdd2Log);
307     var TableName: string;
308     i,j: integer;
309     begin
310     TableName := trim(FIBSQL.GetUniqueRelationName);
311     if TableName = '' then
312     IBError(ibxeUniqueRelationReqd,[nil]);
313    
314     Add2Log('');
315     Add2Log('/* Inserting data into Table: ' + TableName + ' */');
316     Add2Log('');
317    
318     FInsertHeader := 'INSERT INTO ' + QuoteIdentifier(Database.SQLDialect, TableName) + ' (';
319     with FIBSQL do
320     begin
321     j := 0;
322     for i := 0 to MetaData.Count - 1 do
323     if IncludeBlobsAndArrays or
324     ((MetaData[i].SQLTYPE <> SQL_BLOB) and (MetaData[i].SQLType <> SQL_ARRAY)) then
325     begin
326     if j <> 0 then FInsertHeader += ',';
327     FInsertHeader += QuoteIdentifierIfNeeded(Database.SQLDialect,Trim(MetaData[i].getAliasName));
328     Inc(j);
329     end;
330     end;
331     FInsertHeader += ') VALUES(';
332     end;
333    
334     procedure TIBInsertStmtsOut.FormattedDataOut(Add2Log: TAdd2Log);
335     const
336     QuoteChar = '''';
337    
338     var s: string;
339     i, j: integer;
340     ar: IArray;
341     begin
342     s := FInsertHeader;
343     with FIBSQL do
344     begin
345     j := 0;
346     for i := 0 to Current.Count - 1 do
347     if IncludeBlobsAndArrays or
348     ((Current[i].SQLTYPE <> SQL_BLOB) and (Current[i].SQLType <> SQL_ARRAY)) then
349     begin
350     if j <> 0 then s += ',';
351     if Current[i].IsNull then
352     s += 'NULL'
353     else
354     case Current[i].SQLType of
355     SQL_BLOB:
356     if Current[i].SQLSubType = 1 then
357     begin
358     if Current[i].getCharSetID = 1 {octets} then
359     s += Format('x''%s''',[StringToHex(Current[i].AsString)])
360     else
361     s += QuoteChar + SQLSafeString(Current[i].AsString) + QuoteChar
362     end
363     else
364     s += TIBXMLProcessor.FormatBlob(Current[i]);
365    
366     SQL_ARRAY:
367     begin
368     ar := Current[i].AsArray;
369     if ar = nil then
370     s += 'NULL'
371     else
372     s += TIBXMLProcessor.FormatArray(ar);
373     end;
374    
375     SQL_TEXT, SQL_VARYING:
376     if Current[i].getCharSetID = 1 {octets} then
377     s += Format('x''%s''',[StringToHex(Current[i].AsString)])
378     else
379     s += QuoteChar + SQLSafeString(Current[i].AsString) + QuoteChar;
380    
381     SQL_TIMESTAMP:
382     s += QuoteChar + FormatDateTime(sTimeStampFormat,Current[i].AsDateTime) + QuoteChar;
383    
384     SQL_TYPE_DATE:
385     s += QuoteChar + FormatDateTime(sDateFormat,Current[i].AsDateTime) + QuoteChar;
386    
387     SQL_TYPE_TIME:
388     s += QuoteChar + FormatDateTime(sTimeFormat,Current[i].AsDateTime) + QuoteChar;
389    
390     else
391     s += Current[i].AsString;
392     end;
393     Inc(j);
394     end;
395     end;
396     s += ');';
397     Add2Log(s);
398     end;
399    
400     constructor TIBInsertStmtsOut.Create(aOwner: TComponent);
401     begin
402     inherited Create(aOwner);
403     FIncludeBlobsAndArrays := true;
404     end;
405    
406     { TIBCSVDataOut }
407    
408     procedure TIBCSVDataOut.HeaderOut(Add2Log: TAdd2Log);
409     var i: integer;
410     s: string;
411     begin
412     s := '';
413     for i := 0 to FIBSQL.MetaData.Count - 1 do
414     begin
415     if i <> 0 then s += ',';
416     s += FIBSQL.MetaData[i].getAliasName;
417     end;
418     Add2Log(s);
419     end;
420    
421     procedure TIBCSVDataOut.FormattedDataOut(Add2Log: TAdd2Log);
422     var i: integer;
423     s: string;
424     begin
425     s := '';
426     with FIBSQL do
427     begin
428     for i := 0 to Current.Count - 1 do
429     with Current[i] do
430     begin
431     if i <> 0 then s += ',';
432     case SQLType of
433     SQL_BLOB:
434     if SQLSubType <> 1 then
435     s += sBlob
436     else
437     s += QuoteChar + Current[i].AsString + QuoteChar;
438    
439     SQL_VARYING,SQL_TEXT,
440     SQL_TIMESTAMP,SQL_TYPE_DATE,SQL_TYPE_TIME:
441     s += QuoteChar + Current[i].AsString + QuoteChar;
442    
443     else
444     s += Current[i].AsString;
445     end;
446     end;
447     end;
448     Add2Log(s);
449     end;
450    
451     constructor TIBCSVDataOut.Create(aOwner: TComponent);
452     begin
453     inherited Create(aOwner);
454     FQuoteChar := '''';
455     end;
456    
457     { TIBCustomDataOutput }
458    
459     function TIBCustomDataOutput.GetDatabase: TIBDatabase;
460     begin
461     Result := FIBSQL.Database;
462     end;
463    
464     function TIBCustomDataOutput.GetTransaction: TIBTransaction;
465     begin
466     Result := FIBSQL.Transaction;
467     end;
468    
469     procedure TIBCustomDataOutput.SetDatabase(AValue: TIBDatabase);
470     begin
471     FIBSQL.Database := AValue;
472     end;
473    
474     procedure TIBCustomDataOutput.SetTransaction(AValue: TIBTransaction);
475     begin
476     FIBSQL.Transaction := AValue;
477     end;
478    
479     procedure TIBCustomDataOutput.HeaderOut(Add2Log: TAdd2Log);
480     begin
481     //stub
482     end;
483    
484     procedure TIBCustomDataOutput.TrailerOut(Add2Log: TAdd2Log);
485     begin
486     //stub
487     end;
488    
489     constructor TIBCustomDataOutput.Create(aOwner: TComponent);
490     begin
491     inherited Create(aOwner);
492     FIBSQL := TIBSQL.Create(self);
493     FIncludeHeader := true;
494     end;
495    
496     procedure TIBCustomDataOutput.Assign(Source: TPersistent);
497     begin
498     if Source is TIBCustomDataOutput then
499     begin
500     IncludeHeader := TIBCustomDataOutput(Source).IncludeHeader;
501     RowCount := TIBCustomDataOutput(Source).RowCount;
502     ShowPerformanceStats := TIBCustomDataOutput(Source).ShowPerformanceStats;
503     PlanOptions := TIBCustomDataOutput(Source).PlanOptions;
504     end;
505     end;
506    
507     procedure TIBCustomDataOutput.DataOut(SelectQuery: string; Add2Log: TAdd2Log);
508     var Count: integer;
509     begin
510     FIBSQL.SQL.Text := SelectQuery;
511     FIBSQL.Prepare;
512     FIBSQL.Statement.EnableStatistics(ShowPerformanceStats);
513     if PlanOptions <> poNoPlan then
514     Add2Log(FIBSQL.Plan);
515     if PlanOptions = poPlanOnly then
516     Exit;
517    
518     if IncludeHeader then
519     HeaderOut(Add2Log);
520     Count := 0;
521     FIBSQL.ExecQuery;
522     try
523     while (not FIBSQL.EOF) and ((FRowCount = 0) or (Count < FRowCount)) do
524     begin
525     FormattedDataOut(Add2Log);
526     FIBSQL.Next;
527     Inc(Count);
528     end;
529     ShowPerfStats(FIBSQL.Statement,Add2Log);
530     finally
531     FIBSQL.Close;
532     end;
533     end;
534    
535     procedure TIBCustomDataOutput.SetCommand(command, aValue, stmt: string;
536     var Done: boolean);
537    
538     function Toggle(aValue: string): boolean;
539     begin
540     aValue := AnsiUpperCase(aValue);
541     if aValue = 'ON' then
542     Result := true
543     else
544     if aValue = 'OFF' then
545     Result := false
546     else
547     raise Exception.CreateFmt(sInvalidSetStatement, [command,stmt]);
548     end;
549    
550     begin
551     done := true;
552     if command = 'HEADING' then
553     FIncludeHeader := ((aValue = '') and not FIncludeHeader) or
554     ((aValue <> '') and Toggle(aValue))
555     else
556     if command = 'ROWCOUNT' then
557     FRowCount := StrToInt(aValue)
558     else
559     if command = 'PLAN' then
560     begin
561     if aValue = '' then
562     begin
563     if FPlanOptions <> poIncludePlan then
564     FPlanOptions := poIncludePlan
565     else
566     FPlanOptions := poNoPlan;
567     end
568     else
569     if Toggle(aValue) then
570     FPlanOptions := poIncludePlan
571     else
572     FPlanOptions := poNoPlan;
573     end
574     else
575     if command = 'PLANONLY' then
576     begin
577     if aValue = '' then
578     begin
579     if FPlanOptions <> poPlanOnly then
580     FPlanOptions := poPlanOnly
581     else
582     FPlanOptions := poNoPlan;
583     end
584     else
585     if Toggle(aValue) then
586     FPlanOptions := poPlanOnly
587     else
588     if FPlanOptions <> poIncludePlan then
589     FPlanOptions := poNoPlan;
590     end
591     else
592     done := false;
593     end;
594    
595     class procedure TIBCustomDataOutput.ShowPerfStats(Statement: IStatement;
596     Add2Log: TAdd2Log);
597     var stats: TPerfCounters;
598     begin
599     if Statement.GetPerfStatistics(stats) then
600     begin
601     Add2Log(Format('Current memory = %d',[stats[psCurrentMemory]]));
602     Add2Log(Format('Delta memory = %d',[stats[psDeltaMemory]]));
603     Add2Log(Format('Max memory = %d',[stats[psMaxMemory]]));
604     Add2Log('Elapsed time= ' + FormatFloat('#0.000',stats[psRealTime]/1000) +' sec');
605     Add2Log('Cpu = ' + FormatFloat('#0.000',stats[psUserTime]/1000) + ' sec');
606     Add2Log(Format('Buffers = %d',[stats[psBuffers]]));
607     Add2Log(Format('Reads = %d',[stats[psReads]]));
608     Add2Log(Format('Writes = %d',[stats[psWrites]]));
609     Add2Log(Format('Fetches = %d',[stats[psFetches]]));
610     end;
611     end;
612    
613     end.
614