-
Notifications
You must be signed in to change notification settings - Fork 18
Expand file tree
/
Copy pathCcProviders.pas
More file actions
2925 lines (2461 loc) · 84 KB
/
CcProviders.pas
File metadata and controls
2925 lines (2461 loc) · 84 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// CopyCat replication suite<p/>
// Copyright (c) 2015 Microtec Communications<p/>
// For any questions or technical support, contact us at contact@copycat.fr
unit CcProviders;
{$I CC.INC}
interface
uses Classes, Sysutils, DB {$IFDEF CLR}, Borland.Vcl.Variants{$ENDIF};
type
TCcExceptionNotifyEvent = procedure(Sender: TObject; var RaiseException: Boolean) of object;
TCcQuery = class;
TCcConnection = class;
TCcConnectionClass = class of TCcConnection;
TCcCustomKeyRing = class
protected
FConnection: TCcConnection;
public
property Connection: TCcConnection read FConnection;
constructor Create(conn: TCcConnection); virtual;
end;
// Description:
// \Internal query field or parameter object.
TCcField = class
private
FQuery: TCcQuery;
FSize: Integer;
FDataType: TFieldType;
protected
// \ \
FIsParam: Boolean;
FFieldName: string;
FIndex: Integer;
function GetValue: Variant;
procedure SetValue(Val: Variant);
function GetIsNull: Boolean;
function GetAsString: string;
procedure SetAsString(Val: string);
function GetAsInteger: Integer;
procedure SetAsInteger(Val: Integer);
function GetAsFloat: Double;
procedure SetAsFloat(Val: Double);
function GetAsDateTime: TDateTime;
procedure SetAsDateTime(Val: TDateTime);
function GetAsCurrency: Currency;
procedure SetAsCurrency(Val: Currency);
public
// Set this field to null
procedure Clear;
procedure SetValueAsType(Val: Variant; AFieldType: TFieldType);
// IsParam indicates whether this TCcField is a field or a param
property IsParam: Boolean read FIsParam;
property index: Integer read FIndex;
property IsNull: Boolean read GetIsNull;
property Value: Variant read GetValue write SetValue;
property AsString: string read GetAsString write SetAsString;
property AsInteger: Integer read GetAsInteger write SetAsInteger;
property AsFloat: Double read GetAsFloat write SetAsFloat;
property AsDateTime: TDateTime read GetAsDateTime write SetAsDateTime;
property AsCurrency: Currency read GetAsCurrency write SetAsCurrency;
property DataType: TFieldType read FDataType;
property Size: Integer read FSize;
property FieldName: string read FFieldName;
constructor Create(Query: TCcQuery; cFieldName: string; FieldType: TFieldType; nSize: Integer; lParam: Boolean);
end;
// Represents a query macro. This is an internal object, not needed by application
// developers.
TCcMacro = class
private
FValue: string;
FName: string;
FQuery: TCcQuery;
procedure SetValue(const Value: string);
protected
procedure ApplyToSQL(var SQLText: string);
constructor Create(Query: TCcQuery; Name: string);
public
// Name of the macro.
property name: string read FName;
// Current value of the macro. Macros may only be set before the TCcQuery has been
// prepared.
property Value: string read FValue write SetValue;
end;
TCcSqlParser = class
private
FTokenChar: string;
FTokens: TStringList;
function GetOffset(nIndex: Integer): Integer;
function GetToken(index: Integer): string;
function GetTokenCount: Integer;
function GetQuoteCount(nIndex: Integer): Integer;
public
property TokenChar: string read FTokenChar write FTokenChar;
property Token[index: Integer]: string read GetToken;
property TokenCount: Integer read GetTokenCount;
property Offset[nIndex: Integer]: Integer read GetOffset;
property QuoteCount[nIndex: Integer]: Integer read GetQuoteCount;
procedure Parse(SQLText: string);
constructor Create;
destructor Destroy; override;
end;
TCcAbstractQueryObject = class
private
FConnection: TCcConnection;
FID: Integer;
FQuery: TCcQuery;
FSelectStatement: Boolean;
FAfterClose: TDataSetNotifyEvent;
protected
function GetRowsAffected: Integer; virtual; abstract;
function GetEof: Boolean; virtual; abstract;
procedure DoInitParams(ParamList: TStringList); virtual; abstract;
procedure DoInitFields(FieldList: TStringList); virtual; abstract;
procedure DoExec; virtual; abstract;
procedure DoPrepare(SQLText: string); virtual; abstract;
procedure DoUnPrepare; virtual; abstract;
procedure SetParamCheck(lParamCheck: Boolean); virtual; abstract;
procedure DoClose; virtual; abstract;
procedure DoNext; virtual; abstract;
function GetFieldType(FieldName: string; IsParam: Boolean): TFieldType; virtual; abstract;
function GetFieldSize(FieldName: string; IsParam: Boolean): Integer; virtual; abstract;
function GetFieldValue(Field: TCcField): Variant; virtual; abstract;
procedure SetFieldValue(Field: TCcField; Val: Variant); virtual; abstract;
property Connection: TCcConnection read FConnection;
property ID: Integer read FID;
public
property SelectStatement: Boolean read FSelectStatement;
property Query: TCcQuery read FQuery;
constructor Create(Conn: TCcConnection; qry: TCcQuery; nID: Integer; Select: Boolean); virtual;
property AfterClose: TDataSetNotifyEvent read FAfterClose write FAfterClose;
end;
// Description:
// Represents a database query. TCcQuery is used internally in CopyCat, and can also
// be used in your applications as a general purpose query object using a TCcConnection.
// This allows a total abstraction of the underlying data-access components, while
// still providing all the basic functionality needed for executing queries,
// including named parameters, macros, etc.<p/>
TCcQuery = class(TComponent)
private
FOldSQLText: string;
FActive: Boolean;
FRecordCount: Integer;
FParamCheck: Boolean;
FOnExecute: TNotifyEvent;
FMacroParser: TCcSqlParser;
FQueryObject: TCcAbstractQueryObject;
FProperties: TStringList;
FSelectStatement: Boolean;
procedure SQLChanged(Sender: TObject);
procedure SetActive(const Value: Boolean);
procedure SetConnection(const Value: TCcConnection);
function GetParamByIndex(index: Integer): TCcField;
function GetFieldByIndex(index: Integer): TCcField;
function GetMacro(MacroName: string): TCcMacro;
function GetParam(Param: string): TCcField;
function GetField(FieldName: string): TCcField;
function GetMacroCount: Integer;
function GetParamCount: Integer;
function GetFieldCount: Integer;
// This function parses the SQL, sets the value of each macro, and returns the resulting SQL with macros applied
function ApplyMacros(cSQL: string): string;
function DoFindParam(Param: string): TCcField;
protected
FFields: TStringList;
FParams: TStringList;
FMacros: TStringList;
FConnection: TCcConnection;
FQueryID: Integer;
// This variable doesn't necessarily mean that the query has been prepared
// It merely means that a parameter has been accessed, and that the SQL
// therefore cannot be changed any more until the query is closed
FSQLPrepared: Boolean;
FSQLWithMacros: TStrings;
FRealSQL: string;
FQueryName: string;
procedure CheckMacros(SQLText: string);
procedure FreeMacros;
procedure FreeParams;
procedure FreeFields;
procedure CreateField(cFieldName: string; FieldType: TFieldType; nSize: Integer);
procedure CreateParam(cFieldName: string; FieldType: TFieldType; nSize: Integer);
// After accessing the parameters, the SQL can no longer be modified. This is
// a lowest common denominator approach, some DACs support it while others don't
procedure SetSQL(Value: TStrings);
procedure SetParamCheck(Value: Boolean);
function GetRecordCount: Integer;
function GetRowsAffected: Integer;
function GetEof: Boolean;
procedure InitFields;
procedure ClearFields;
procedure InitParams;
procedure ClearParams;
property OnExecute: TNotifyEvent read FOnExecute write FOnExecute;
// Called by TCcConnection in order to signal that the DBType or DBVersion have changed
procedure ClearQueryObject;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
public
function GetQueryObject: TCcAbstractQueryObject;
procedure CheckInactive;
// A unique ID identifying this query among all the queries of this connection
// QueryID is attributed when the Connection is assigned and set to -1 when there is no Connection
property QueryID: Integer read FQueryID;
// The SQL text with macros expanded, as it will be sent to the server
property RealSQL: string read FRealSQL;
procedure Close;
procedure Next;
procedure Prepare;
procedure UnPrepare;
// Execute the query (synchronously). If the database connection is inactive, or if
// it gets cut during execution, the OnConnectionLost event of the database is called
procedure Exec;
// Indicates whether a macro by the specified name exists
function MacroExists(MacroName: string): Boolean;
// Find a macro by its name. Returns nil if the macro is not found.
function FindMacro(MacroName: string): TCcMacro;
// Find a macro by its name. Throws an exception if the macro is not found.
property Macro[ParamName: string]: TCcMacro read GetMacro;
// The number of macros defined in the query
property MacroCount: Integer read GetMacroCount;
// Indicates whether a parameter by the specified name exists
function ParamExists(ParamName: string): Boolean;
// Find a parameter by its name. Returns nil if the parameter is not found.
function FindParam(Param: string): TCcField;
// Find a parameter by its index. Throws an exception if the parameter is not found.
property ParamByIndex[index: Integer]: TCcField read GetParamByIndex;
// Find a parameter by its name. Throws an exception if the parameter is not found.
property Param[ParamName: string]: TCcField read GetParam;
// The number of parameters defined in the query
property ParamCount: Integer read GetParamCount;
// Indicates whether a field by the specified name exists
function FieldExists(FieldName: string): Boolean;
// Find a field by its name. Returns nil if the field is not found.
function FindField(FieldName: string): TCcField;
// Find a field by its index. Throws an exception if the field is not found.
property FieldByIndex[index: Integer]: TCcField read GetFieldByIndex;
// Find a field by its name. Throws an exception if the field is not found.
property Field[FieldName: string]: TCcField read GetField;
// The number of fields returned by the query
property FieldCount: Integer read GetFieldCount;
// Number or rows touched by the last insert, update or delete statement.
// Returns -1 for select statements or for connectors that don't support it.
property RowsAffected: Integer read GetRowsAffected;
// Number of rows returned by the query
property RecordCount: Integer read GetRecordCount;
// Eof is true after Next is called on the last record.
property Eof: Boolean read GetEof;
property Prepared: Boolean read FSQLPrepared;
// Set Active to true in order to execute the query. Same as Exec.
property Active: Boolean read FActive write SetActive;
// Properties is an all-purpose list of name/value pairs, that can be used for storing
// and retrieving user properties associated with the query.
property Properties: TStringList read FProperties;
constructor Create(AOwner: TComponent); overload; override;
constructor Create(Conn: TCcConnection; cName: string; Select: Boolean); overload; virtual;
destructor Destroy; override;
published
// Indicates whether this query is returns a result set or not.
// If your query is a select statement (i.e. returns a result set), you need to set
// SelectStatement to true
property SelectStatement: Boolean read FSelectStatement write FSelectStatement;
// The CopyCat database connection to be used with this query
property Connection: TCcConnection read FConnection write SetConnection;
// Set ParamCheck to false for executing DDL statements, so that CopyCat won't attempt to
// detect parameters in the SQL text
property ParamCheck: Boolean read FParamCheck write SetParamCheck;
// The SQL text of the query. The SQL can contain parameters, defined with a semi-colon (:param_name),
// or macros, defined with a percentage (%macro_name). The difference is that a parameter is sent to the
// server and allows the statement to be prepared once and executed multiple times with different values
// of the parameter. A macro on the other hand is simply a place-holder in the SQL text and is replaced
// by its value on the client side before getting sent to the server.
property SQL: TStrings read FSQLWithMacros write SetSQL;
end;
TCcSysTable = (tabLog, tabConflicts, tabUsers);
TCcDBAdaptor = class;
TCcDBAdaptorClass = class of TCcDBAdaptor;
// Summary:
// Abstract database connection class.
// Description:
// TCcConnection is used by TCcReplicator and TCcConfig in order to acheive database
// access component independence. Descendants of TCcConnection override the
// NewDatabase, NewTransaction and NewQuery methods in order to provide database
// connectivity for CopyCat.<p/>
// <p/>
// Thus, CopyCat can be used interchangeably with any database access components for
// which a TCcConnection descendant has been written. Currently, FIBPlus, IBX, IBO and UIB
// have been implemented. Support for other component sets (such as Zeos and DBX)
// is also possible, and planned for future versions.
TCcConnection = class(TComponent)
private
FDBName: TFileName;
FOnConnectionLost: TCcExceptionNotifyEvent;
FOnDestroyQueries: TNotifyEvent;
FQueries: TStringList;
FDBTypes: TStringList;
FDBType: string;
FDBVersion: string;
FDBAdaptor: TCcDBAdaptor;
FOnQueryExecute: TNotifyEvent;
lStreamedDBType: Boolean;
lStreamedDBVersion: Boolean;
FConnectionParams: TStringList;
FPooledQueries: TStringList;
FQueryIDCounter: Integer;
slReturnList: TStringList;
procedure SetConnected(const Value: Boolean);
procedure SetConnectionParams(const Value: TStringList);
procedure QueryExecute(Sender: TObject);
function GetDDLQuery(qryName: string): TCcQuery;
function GetSelectQuery(qryName: string): TCcQuery;
function GetUpdateQuery(qryName: string): TCcQuery;
function GetDBType(cName: string): TCcDBAdaptorClass;
function GetCanUseRowsAffected: Boolean;
function GetQuery(qryName: string; SelectStatement: Boolean): TCcQuery;
protected
FReplicatingNode: string;
FReplicatingNodePassword: string;
FConnectionLost: Boolean;
procedure Notification(AComponent: TComponent; Operation: TOperation); override;
function GetConnected: Boolean;
function GetConnectorConnected: Boolean; virtual;
function GetInTransaction: Boolean; virtual;
procedure AddQuery(qry: TCcQuery);
procedure RemoveQuery(qry: TCcQuery);
procedure ResetQueryObjects;
procedure DoResetQueryObjects(NewValue: string); virtual;
procedure SetDBType(const Value: string);
procedure SetDBVersion(const Value: string);
// Override to indicate whether of not this connector supports use
// of rowsAffected property. Default value is true. This value may
// be overridden by the adaptor if it does not support RowsAffected
// See also: TCcDBAdaptor.UseRowsAffected, CanUseRowsAffected
function RowsAffectedSupported: Boolean; virtual;
// SignalConnectLost is called internally whenever the database connection is lost,
// in order to reset the state of the connection object.
procedure SignalConnectLost; virtual;
// Descendant classes must call this procedure in order to register which database types they support
procedure AddDBAdaptor(Adaptor: TCcDBAdaptorClass);
procedure AddDBAdaptors(Adaptors: array of TCcDBAdaptorClass);
procedure DoDisconnect; virtual;
procedure DoConnect; virtual;
procedure DoCommit; virtual;
procedure DoCommitRetaining; virtual;
procedure DoRollback; virtual;
procedure DoRollbackRetaining; virtual;
procedure DoStartTransaction; virtual;
// DoCleanup is called just before the connection is destroyed
// It should be over-ridden by descendants instead of the destructor for performing cleanup,
// because the destructor of TCcConnection may perform database operations (if the connection is
// still active), which descendants must still be able to perform.
procedure DoCleanup; virtual;
// Instantiates and returns a concrete TCcAbstractQueryObject descendant.
// Note: This method must be over-ridden by TCcConnection descendants.
function NewQueryObject(qry: TCcQuery; nID: Integer): TCcAbstractQueryObject; virtual;
// Called immediately before opening the connection
// Descendants can over-ride this method to provide special functionality before connecting
procedure DoBeforeConnect; virtual;
public
function GetFieldType(tableName: string; FieldName: string): TFieldType;
// Returns a managed query by name
function FindQuery(cName: string): TCcQuery;
// Name of the database
// The exact format will depend on the underlying database type
property DBName: TFileName read FDBName write FDBName;
// Summary: Call ExecQuery to execute a query directly.
// Description: ExecQuery creates a query with default options, executes the provided SQL,
// and returns the number of rows affected.
function ExecQuery(SQL: string): Integer;
// Call RefreshNodes after inserting new nodes into RPL$USERS, in order to register them.
// If you create nodes using TCcConfig.RegisterNode, this step is unnecessary.
procedure RefreshNodes;
procedure CheckDisconnected;
// Clear the query cache associated with this connection
procedure ClearQueries;
procedure Loaded; override;
procedure Assign(Source: TPersistent); override;
function Gen_Id(GenName: string; Increment: Integer):
{$IFDEF CC_D2K9}
Int64;
{$ELSE}
Integer;
{$ENDIF}
// Maximum length of meta-data names for the selected database type
function MaxDDLNameLength: Integer;
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
// The currently selected database adaptor object
property DBAdaptor: TCcDBAdaptor read FDBAdaptor;
// Returns the list of supported database types.
procedure DatabaseTypes(Databases: TStrings);
// Returns the list of supported versions of the current database type.
procedure DatabaseVersions(Versions: TStrings);
// Get a query object by name
// Description:
// TCcConnection maintains a pool of TCcQuery objects for the current database
// connection. Attempting to access a non-existing query will result in a new
// TCcQuery object being created and returned.
// This property returns select queries (to be used for SQL statement returning a result-set)
// Note: This property is used internally in CopyCat, and shouldn't normally be needed
// by application developpers.
property SelectQuery[qryName: string]: TCcQuery read GetSelectQuery;
// Get a query object by name
// Description:
// TCcConnection maintains a pool of TCcQuery objects for the current database
// connection. Attempting to access a non-existing query will result in a new
// TCcQuery object being created and returned.
// This property returns update queries (to be used for SQL statement returning no result-set)
// Note: This property is used internally in CopyCat, and shouldn't normally be needed
// by application developpers.
property UpdateQuery[qryName: string]: TCcQuery read GetUpdateQuery;
// Gets a DDL managed query object by name, that is, a query with ParamCheck set to false.
// Note: This property is used internally in CopyCat, and shouldn't normally be needed
// by application developpers.
property DDLQuery[qryName: string]: TCcQuery read GetDDLQuery;
// Returns the list of tables in the database, including temporary tables.
// Note: The TStringList returned is managed by the TCcConnection, and shouldn't be freed.
function ListAllTables: TStringList;
// Returns the list of tables in the database.
// Note: The TStringList returned is managed by the TCcConnection, and shouldn't be freed.
function ListTables: TStringList;
// Returns the list of fields in the specified table
// Note: The TStringList returned is managed by the TCcConnection, and shouldn't be freed.
function ListTableFields(cTableName: string): TStringList;
// Returns the list of fields in the specified table, excluding read-only fields
// Note: The TStringList returned is managed by the TCcConnection, and shouldn't be freed.
function ListUpdatableTableFields(cTableName: string): TStringList;
// Returns the list of primary key fields in the specified table
// Note: The TStringList returned is managed by the TCcConnection, and shouldn't be freed.
function ListPrimaryKeys(cTableName: string): TStringList;
// Returns the list of triggers in the database
// Note: The TStringList returned is managed by the TCcConnection, and shouldn't be freed.
function ListTriggers: TStringList;
// Returns the list of fields to use as keys in tables with no primary keys.
// Note: The TStringList returned is managed by the TCcConnection, and shouldn't be freed.
function ListFieldsForNoPK(cTableName: string): TStringList;
// Lists stored procedures that can be replicated, that is, procedures that don't return
// any value.
// Note: The TStringList returned is managed by the TCcConnection, and shouldn't be freed.
function ListProcedures: TStringList;
// Lists all stored procedures in the database.
// Note: The TStringList returned is managed by the TCcConnection, and shouldn't be freed.
function ListAllProcedures: TStringList;
// Lists all the generators (sequences) defined in the database.
// Note: The TStringList returned is managed by the TCcConnection, and shouldn't be freed.
function ListGenerators: TStringList;
// Lists the database-specific keywords that can't be used as field names
// Any existing fields named using these keywords are excluded from replication
function ListKeywordsForbiddenAsFieldNames: TStringList;
// Commit transaction and disconnect from database
procedure Disconnect;
// Connect to database and start transaction
procedure Connect;
// Connect to database and start transaction, specifying the name of the
// currently replicating node.
procedure ConnectAsNode(NodeName: string; NodePassword: string);
procedure Commit;
procedure CommitRetaining;
procedure Rollback;
procedure RollbackRetaining;
procedure StartTransaction;
property InTransaction: Boolean read GetInTransaction;
// Fired when the internal pool of database queries is about to be emptied.
// Any pointers to these queries must be cleared, in order to avoid invalid references.
// Note: TCcConnection handles actually destroying the queries.
property OnDestroyQueries: TNotifyEvent read FOnDestroyQueries write FOnDestroyQueries;
// Summary:
// Node that is currently being replicated
// Description:
// ReplicatingNode hold the name of the node that is being replicated. If no
// replication is active, ReplicatingNode will be empty.
property ReplicatingNode: string read FReplicatingNode;
// Summary:
// Password of the replicating node
// Description:
// The password of this node when connecting to a remote CopyCat server (PHP or
// Java). This is not the database connection password, but the password stored in
// RPL$USERS on the remote server.
property ReplicatingNodePassword: string read FReplicatingNodePassword;
// List of database-specific connection parameters to use when opening a connection.
// Descendants should add values to this list for every additional connection parameter that is needed.
property ConnectionParams: TStringList read FConnectionParams write SetConnectionParams;
// Type of database to connect to.
property DBType: string read FDBType write SetDBType;
// Version of the database.
property DBVersion: string read FDBVersion write SetDBVersion;
// Determines whether the current combination of connector and db adaptor can
// support use of rowsaffected property
property CanUseRowsAffected: Boolean read GetCanUseRowsAffected;
published
// Return the name of the database access component set for which this connection type is designed.
// Note: This method must be over-ridden by TCcConnection descendants.
class function ConnectorName: string; virtual;
property Connected: Boolean read GetConnected write SetConnected stored False;
{ ******************************************************************
* Summary: *
* Fired upon database connection loss. *
* Description: *
* OnConnectionLost gets called when the database connection gets*
* abruptly cut. It does not occur however if the connection is *
* closed explicitly with the Connected property. *
***************************************************************** }
property OnConnectionLost: TCcExceptionNotifyEvent read FOnConnectionLost write FOnConnectionLost;
// This event is fired whenever a query is executed using this connection.
property OnQueryExecute: TNotifyEvent read FOnQueryExecute write FOnQueryExecute;
end;
ECcLostConnection = class(Exception)
private
FConnection: TCcConnection;
public
constructor Create(Conn: TCcConnection);
property Connection: TCcConnection read FConnection;
end;
// TCcDBAdaptor is the abstract ancestor class for the adaptor classes providing database-specific features
// for each of the supported database types. You can access the DBAdaptor of your database connection by using
// the TCcConnection.DBAdaptor property.
TCcDBAdaptor = class
private
FVersion: string;
FSupportedVersions: TStringList;
protected
Query: TStringList;
FConnection: TCcConnection;
procedure ExecConfQuery;
procedure SetVersion(const Value: string); virtual;
procedure RegisterVersions(Versions: array of string);
function MaxDDLNameLength: Integer; virtual;
function ListFieldNames(slFields: TStringList; SourceDBAdaptor: TCcDBAdaptor; prefix: String): String;
function GetFieldTypeSQLText(FieldName, TableName : String): String;virtual;
// Override to detect error messages that should be interpreted as a connection loss
function CheckConnectionLossException(E: Exception): Boolean; virtual;
// Should be implemented by descending classes in order to indicate whether to use RowsAffected during replication or not.
// See also: UseRowsAffected
function GetUseRowsAffected: Boolean; virtual;
// InitConnection is called by the TCcConnection as soon as a connection
// has been established, in order to allow database-specific connection initialization.
procedure InitConnection; virtual;
// InitTransaction is called by the TCcConnection immediately after a new transaction
// has been started, in order to allow database-specific transaction initialization.
procedure InitTransaction; virtual;
// CleanupConnection is called by the TCcConnection before dropping a connection,
// in order to allow database-specific connection cleanup.
procedure CleanupConnection; virtual;
// CleanupTransaction is called by the TCcConnection before closing a transaction,
// in order to allow database-specific transaction cleanup.
procedure CleanupTransaction; virtual;
function GetCurrentTimeStampSQL: string; virtual;
// Descendants can override GetQuoteMetadata to indicate whether this database requires quoted meta-data or not
// By default, if not overridden, we assume that it's unnecessary
function GetQuoteMetadata: Boolean; virtual;
// BeforeConnect is called just before connecting to the database, in order for
// descendants set any necessary to database-specific connection parameters
// See also: TCcConnection.ConnectionParams
procedure BeforeConnect; virtual;
function GetGeneratorValue(GenName: string; Increment: Integer):
{$IFDEF CC_D2K9}
Int64;
{$ELSE}
Integer;
{$ENDIF}virtual;
// Override DoMetaQuote to change the way identifiers are quoted.
// By default, if QuoteMetadata is true, identifiers (table and field names) are surrounded by double quotes,
// unless the identifier is in all-caps.
function DoMetaQuote(Identifier: string): string; virtual;
// DoRegisterNode is called every time a new node is created by calling TCcConfig.RegisterNode,
// or whenever a newly created node has been discovered using TCcConnection.RefreshNodes.
// Descendants should override this method if they need to do special database-specific
// processing to register a new node (other than inserting it into RPL$USERS, which is done by TCcConfig.RegisterNode).
procedure DoRegisterNode(NodeName: string); virtual;
procedure DoListTables(list: TStringList; IncludeTempTables: Boolean); virtual;
procedure DoListTableFields(cTableName: string; list: TStringList); virtual;
procedure DoListUpdatableTableFields(cTableName: string; list: TStringList); virtual;
procedure DoListPrimaryKeys(cTableName: string; list: TStringList); virtual;
procedure DoListTriggers(list: TStringList); virtual;
procedure DoListProcedures(list: TStringList); virtual;
procedure DoListAllProcedures(list: TStringList); virtual;
procedure DoListGenerators(list: TStringList); virtual;
procedure DoListFieldsForNoPK(cTableName: string; list: TStringList); virtual;
procedure DoListKeywordsForbiddenAsFieldNames(list: TStringList); virtual;
public
function SubStringFunction(str: String; start, length: Integer): String; virtual;
function ConcatenationOperator: String; virtual;
procedure ExecutingReplicationQuery(cTableName, queryType: String;
fieldList: TStringList);virtual;
procedure ExecutedReplicationQuery(cTableName, queryType: String;
fieldList: TStringList);virtual;
procedure ExecutingReplicationQuerySQL(cTableName, queryType: String;
query: TCcQuery);virtual;
// Override SupportsInsertOrUpdate if this database type supports the insert INSERT OR UPDATE statement, or some other equivalent
// Override GetInsertOrUpdateSQL as well to provide an implementation
function SupportsInsertOrUpdate: Boolean;virtual;
// Override GetInsertOrUpdateSQL if this database type supports the insert INSERT OR UPDATE statement, or some other equivalent
// Returns a full SQL statement if supported.
function GetInsertOrUpdateSQL(slFields: TStringList; sourceDBAdaptor: TCcDBAdaptor; keys: TCcCustomKeyRing; tableName: String): string;virtual;
// Grants full rights to specified table to public
// Used internally to give rights to RPL$ tables
// Override if necessary to provide database-specific syntax
procedure GrantRightsToTable(tableName: string); virtual;
procedure DropGenerator(cGeneratorName: string); virtual;
// Indicates whether this database type supports the RowsAffected property
// If so, as with Interbase/Firebird, the RowsAffected property will be used during replication
// to determine whether an update or an insert should be performed (i.e. an update will be performed, and
// if RowsAffected = 0, an insert will be performed as well).
// If not, as with MySQL, a query will systematically be executed first in order to determine whether the record exists or not
// in the destination database, thus slightly hurting performance.
property UseRowsAffected: Boolean read GetUseRowsAffected;
// Indicates whether this database type supports SQL generators (sequences)
function SupportsGenerators: Boolean; virtual;
// Used internally for quoting meta-data identifiers.
class function GetAdaptorName: string; virtual;
// MetaQuote surrounds the provided identifier with the quote marks required by this database (double-quote by default),
// if it's supported by this database. If not, the identifier is simply returned.
function MetaQuote(Identifier: string): string; overload;
function MetaQuote(Identifier: string; SourceDBAdaptor: TCcDBAdaptor): string; overload;
// QuoteMetadata indicates whether this database supports quoted metadata.
// Description: If QuoteMetadata is false, all table and field names will be handled case-insensitively.
// If QuoteMetadata is true, the case and special characters of the identifier names are preserved and used quoted in queries.
property QuoteMetadata: Boolean read GetQuoteMetadata;
// Returns the unquoted name of the identifier
// On most databases, that means the uppercase identifier, on others it's lowercase
// This method is used internally for the CopyCat system tables and fields
function UnQuotedIdentifier(Identifier: string): string; virtual;
// This method is called when TCcConfig connects to a database, so as to allow
// the database adaptor to create any necessary database-specific meta-data
// (such as user-defined functions, etc).
procedure CheckCustomMetadata; virtual;
// This method is called after TCcConfig creates meta-data for a database, so as to allow
// the database adaptor to create any necessary database-specific meta-data.
// See also: CheckCustomMetadata
procedure CheckExtraCustomMetadata; virtual;
procedure RemoveCustomMetadata; virtual;
procedure RemoveExtraCustomMetadata; virtual;
function DeclareField(FieldName: string; FieldType: TFieldType;
Length: Integer; NotNull: Boolean; PK: Boolean; AutoInc: Boolean): string; virtual;
function DeclarePK(FieldNames: string): string; virtual;
function FieldExists(cTableName, cFieldName: string): Boolean;
function TableExists(cTableName: string): Boolean;
function ProcedureExists(cProcName: string): Boolean;
function TriggerExists(cTriggerName: string): Boolean;
property CurrentTimeStampSQL: string read GetCurrentTimeStampSQL;
property Version: string read FVersion write SetVersion;
function GenDeclared(GenName: string): Boolean; virtual;
procedure DeclareGenerator(GenName: string); virtual;
// Called after the replication tables have been created, so that stored procedures,
// or other dependant objects, can be declared.
procedure CreateProcedures; virtual;
procedure DropProcedures; virtual;
procedure RemoveTriggers(qTable: TCcQuery); virtual;
function GenerateTriggers(qTable: TCcQuery; qTableConf: TCcQuery; FailIfNoPK: Boolean; TrackFieldChanges: Boolean): Integer; virtual;
function SQLFormatValue(Data: Variant; FieldType: TFieldType): string; virtual;
function GetProcGenerator(ProcName: string; Params: TDataSet;
OutputParam: string; FieldNames: TStringList): string; virtual;
function GetGenerator(GenName: string; Increment: Integer): string; virtual;
procedure GetProcParams(ProcName: string; Params: TDataSet; InputParam: Boolean); virtual;
constructor Create(Conn: TCcConnection); virtual;
destructor Destroy; override;
function ConvertValue(Val: Variant; DataType: TFieldType): Variant; virtual;
property SupportedVersions: TStringList read FSupportedVersions;
end;
TCcClassInfo = class
public
Name: string;
ClassReference: TClass;
end;
procedure RegisterDBConnector(ConnectorClass: TCcConnectionClass; ConnectorName: string);
function GetDBConnectorClass(cConnectorName: string): TCcConnectionClass;
var
CcAvailableDBConnectors: TStringList;
CcAvailableAdaptors: TList;
implementation
uses CCat, CcInterbase, CcMySQL, CcSQLServer, CcSQLite, CcNexusDB {$IFDEF CC_USEVARIANTS}, Variants {$ENDIF}
, CcOracle;
procedure TCcQuery.ClearFields;
var
i: Integer;
begin
with FFields do
begin
for i := Count - 1 downto 0 do
begin
TCcField(Objects[i]).Free;
Delete(i);
end;
end;
end;
procedure TCcQuery.ClearParams;
var
i: Integer;
begin
with FParams do
for i := Count - 1 downto 0 do
begin
TCcField(Objects[i]).Free;
Delete(i);
end;
end;
procedure TCcQuery.Close;
begin
if Assigned(Connection) then
GetQueryObject.DoClose;
FActive := False;
end;
constructor TCcQuery.Create(Conn: TCcConnection; cName: string; Select: Boolean);
begin
Create(Conn);
FQueryName := cName;
SetConnection(Conn);
SelectStatement := Select;
end;
constructor TCcQuery.Create(AOwner: TComponent);
begin
inherited;
FQueryID := -1;
// FMetaSQL := sqlNone;
FSQLPrepared := False;
FParamCheck := True;
FFields := TStringList.Create;
FParams := TStringList.Create;
FMacros := TStringList.Create;
FProperties := TStringList.Create;
FSQLWithMacros := TStringList.Create;
TStringList(FSQLWithMacros).OnChange := SQLChanged;
FMacroParser := TCcSqlParser.Create;
FMacroParser.TokenChar := '%';
SelectStatement := False;
end;
procedure TCcQuery.CreateField(cFieldName: string; FieldType: TFieldType;
nSize: Integer);
var
fField: TCcField;
begin
fField := TCcField.Create(Self, cFieldName, FieldType, nSize, False);
fField.FIndex := FFields.AddObject(cFieldName, fField);
end;
procedure TCcQuery.CreateParam(cFieldName: string; FieldType: TFieldType;
nSize: Integer);
var
fField: TCcField;
begin
fField := TCcField.Create(Self, cFieldName, FieldType, nSize, True);
fField.FIndex := FParams.AddObject(cFieldName, fField);
end;
procedure TCcQuery.ClearQueryObject;
begin
if FActive then
Close;
UnPrepare;
FQueryObject.Free;
FQueryObject := nil;
end;
destructor TCcQuery.Destroy;
begin
if FSQLPrepared and Assigned(Connection) then
if Connection.Connected then
UnPrepare;
if Assigned(Connection) then
Connection.RemoveQuery(Self);
FreeFields;
FreeParams;
FreeMacros;
FProperties.Free;
FFields.Free;
FParams.Free;
FMacros.Free;
if Assigned(FQueryObject) then
FreeAndNil(FQueryObject);
FreeAndNil(FSQLWithMacros);
FreeAndNil(FMacroParser);
inherited;
end;
procedure TCcQuery.Exec;
begin
if Active then
raise Exception.Create('Query already open!');
if not FConnection.Connected then
begin
FConnection.SignalConnectLost;
end;
try
if not FConnection.InTransaction then
FConnection.StartTransaction;
if not FSQLPrepared then
Prepare;
// Execute the query
GetQueryObject.DoExec;
FActive := True;
if Eof then
FRecordCount := 0
else
FRecordCount := 1;
{ //We now create the fields just after preparing }
// Create the field objects
InitFields;
if Assigned(FOnExecute) then
FOnExecute(Self);
except
on E: Exception do begin
if not FConnection.Connected or FConnection.DBAdaptor.CheckConnectionLossException(e) then begin
Close;
FConnection.SignalConnectLost;
end
else
raise;
end;
end;
end;
function TCcQuery.FieldExists(FieldName: string): Boolean;
begin
Result := (FindField(FieldName) <> nil);
end;
function TCcQuery.GetEof: Boolean;
begin
Result := GetQueryObject.GetEof;
end;
function TCcQuery.GetField(FieldName: string): TCcField;
begin
Result := FindField(FieldName);
if Result = nil then
if name <> '' then
raise Exception.Create('Field ' + FieldName + ' not found')
else
raise Exception.Create(Self.Name + ': Field ' + FieldName + ' not found');
end;
function TCcQuery.FindField(FieldName: string): TCcField;
var
i: Integer;
begin
Result := nil;
for i := 0 to FFields.Count - 1 do
if Trim(Uppercase(FFields[i])) = Trim(Uppercase(FieldName)) then
begin
Result := TCcField(FFields.Objects[i]);
Break;
end;
end;
function TCcQuery.GetFieldByIndex(index: Integer): TCcField;
begin
Result := TCcField(FFields.Objects[index]);
end;
function TCcQuery.GetFieldCount: Integer;
begin
Result := FFields.Count;