From b45a0d57c734daccaa5df3a0be6006ffda39d71d Mon Sep 17 00:00:00 2001 From: Ansgar Becker Date: Wed, 8 Jan 2020 15:28:36 +0100 Subject: [PATCH] Issue #12: Implement TDBConnection.GetTableColumns and .GetTableKeys (todo: .GetTableForeignKeys), as a replacement for the error prone ParseTableStructure. SQLite columns and keys should be parsed correctly now, MS SQL and PostgreSQL may now have some glitches to fix. --- source/copytable.pas | 17 +- source/dbconnection.pas | 461 +++++++++++++++++++++++++++++++++------- source/insertfiles.pas | 3 +- source/loaddata.pas | 2 +- source/main.pas | 49 +++-- source/table_editor.pas | 10 +- source/tabletools.pas | 2 +- 7 files changed, 430 insertions(+), 114 deletions(-) diff --git a/source/copytable.pas b/source/copytable.pas index e0df446b..3b2fa59c 100644 --- a/source/copytable.pas +++ b/source/copytable.pas @@ -72,9 +72,6 @@ begin Height := AppSettings.ReadInt(asCopyTableWindowHeight); MainForm.SetupSynEditors; FixVT(TreeElements); - FColumns := TTableColumnList.Create; - FKeys := TTableKeyList.Create; - FForeignKeys := TForeignKeyList.Create; end; @@ -129,12 +126,16 @@ begin comboDatabase.ItemIndex := 0; // Fetch columns and key structures from table or view - FColumns.Clear; - FKeys.Clear; - FForeignKeys.Clear; case FDBObj.NodeType of - lntTable: FConnection.ParseTableStructure(FDBObj.CreateCode, FColumns, FKeys, FForeignKeys); - lntView: FConnection.ParseViewStructure(FDBObj.CreateCode, FDBObj, FColumns, Dummy, Dummy, Dummy, Dummy, Dummy); + lntTable: begin + FColumns := FDBObj.TableColumns; + FKeys := FDBObj.TableKeys; + FForeignKeys := FDBObj.TableForeignKeys; + end; + lntView: begin + FColumns := TTableColumnList.Create; + FConnection.ParseViewStructure(FDBObj.CreateCode, FDBObj, FColumns, Dummy, Dummy, Dummy, Dummy, Dummy); + end else raise Exception.CreateFmt(_('Neither table nor view: %s'), [FDBObj.Name]); end; diff --git a/source/dbconnection.pas b/source/dbconnection.pas index 8dad0afe..bd095691 100644 --- a/source/dbconnection.pas +++ b/source/dbconnection.pas @@ -19,79 +19,12 @@ type TConnectionParameters = class; TDBQuery = class; TDBQueryList = TObjectList; - TDBObject = class(TPersistent) - private - FCreateCode: String; - FCreateCodeFetched: Boolean; - FWasSelected: Boolean; - FConnection: TDBConnection; - function GetObjType: String; - function GetImageIndex: Integer; - function GetOverlayImageIndex: Integer; - function GetPath: String; - procedure SetCreateCode(Value: String); - public - // Table options: - Name, Schema, Database, Column, Engine, Comment, RowFormat, CreateOptions, Collation: String; - Created, Updated, LastChecked: TDateTime; - Rows, Size, Version, AvgRowLen, MaxDataLen, IndexLen, DataLen, DataFree, AutoInc, CheckSum: Int64; - // Routine options: - Body, Definer, Returns, DataAccess, Security, ArgTypes: String; - Deterministic: Boolean; - - NodeType, GroupType: TListNodeType; - constructor Create(OwnerConnection: TDBConnection); - procedure Assign(Source: TPersistent); override; - procedure Drop; - function IsSameAs(CompareTo: TDBObject): Boolean; - function QuotedDatabase(AlwaysQuote: Boolean=True): String; - function QuotedName(AlwaysQuote: Boolean=True; SeparateSegments: Boolean=True): String; - function QuotedDbAndTableName(AlwaysQuote: Boolean=True): String; - function QuotedColumn(AlwaysQuote: Boolean=True): String; - function RowCount: Int64; - function GetCreateCode: String; overload; - function GetCreateCode(RemoveAutoInc, RemoveDefiner: Boolean): String; overload; - property ObjType: String read GetObjType; - property ImageIndex: Integer read GetImageIndex; - property OverlayImageIndex: Integer read GetOverlayImageIndex; - property Path: String read GetPath; - property CreateCode: String read GetCreateCode write SetCreateCode; - property WasSelected: Boolean read FWasSelected write FWasSelected; - property Connection: TDBConnection read FConnection; - end; - PDBObject = ^TDBObject; - TDBObjectList = class(TObjectList) - private - FDatabase: String; - FDataSize: Int64; - FLargestObjectSize: Int64; - FLastUpdate: TDateTime; - FCollation: String; - FOnlyNodeType: TListNodeType; - public - property Database: String read FDatabase; - property DataSize: Int64 read FDataSize; - property LargestObjectSize: Int64 read FLargestObjectSize; - property LastUpdate: TDateTime read FLastUpdate; - property Collation: String read FCollation; - property OnlyNodeType: TListNodeType read FOnlyNodeType; - end; - TDatabaseCache = class(TObjectList); // A list of db object lists, used for caching - TDBObjectComparer = class(TComparer) - function Compare(const Left, Right: TDBObject): Integer; override; - end; - TDBObjectDropComparer = class(TComparer) - function Compare(const Left, Right: TDBObject): Integer; override; - end; - - // General purpose editing status flag - TEditingStatus = (esUntouched, esModified, esDeleted, esAddedUntouched, esAddedModified, esAddedDeleted); - - TOidStringPairs = TDictionary; TColumnPart = (cpAll, cpName, cpType, cpAllowNull, cpDefault, cpVirtuality, cpComment, cpCollation); TColumnParts = Set of TColumnPart; TColumnDefaultType = (cdtNothing, cdtText, cdtNull, cdtAutoInc, cdtExpression); + // General purpose editing status flag + TEditingStatus = (esUntouched, esModified, esDeleted, esAddedUntouched, esAddedModified, esAddedDeleted); // Column object, many of them in a TObjectList TTableColumn = class(TObject) @@ -113,6 +46,7 @@ type destructor Destroy; override; function SQLCode(OverrideCollation: String=''; Parts: TColumnParts=[cpAll]): String; function ValueList: TStringList; + procedure ParseDatatype(Source: String); function CastAsText: String; property Status: TEditingStatus read FStatus write SetStatus; property Connection: TDBConnection read FConnection; @@ -157,6 +91,82 @@ type end; TRoutineParamList = TObjectList; + TDBObject = class(TPersistent) + private + FCreateCode: String; + FCreateCodeFetched: Boolean; + FWasSelected: Boolean; + FConnection: TDBConnection; + FTableColumns: TTableColumnList; + FTableKeys: TTableKeyList; + FTableForeignKeys: TForeignKeyList; + function GetObjType: String; + function GetImageIndex: Integer; + function GetOverlayImageIndex: Integer; + function GetPath: String; + procedure SetCreateCode(Value: String); + function GetTableColumns: TTableColumnList; + function GetTableKeys: TTableKeyList; + function GetTableForeignKeys: TForeignKeyList; + public + // Table options: + Name, Schema, Database, Column, Engine, Comment, RowFormat, CreateOptions, Collation: String; + Created, Updated, LastChecked: TDateTime; + Rows, Size, Version, AvgRowLen, MaxDataLen, IndexLen, DataLen, DataFree, AutoInc, CheckSum: Int64; + // Routine options: + Body, Definer, Returns, DataAccess, Security, ArgTypes: String; + Deterministic: Boolean; + + NodeType, GroupType: TListNodeType; + constructor Create(OwnerConnection: TDBConnection); + procedure Assign(Source: TPersistent); override; + procedure Drop; + function IsSameAs(CompareTo: TDBObject): Boolean; + function QuotedDatabase(AlwaysQuote: Boolean=True): String; + function QuotedName(AlwaysQuote: Boolean=True; SeparateSegments: Boolean=True): String; + function QuotedDbAndTableName(AlwaysQuote: Boolean=True): String; + function QuotedColumn(AlwaysQuote: Boolean=True): String; + function RowCount: Int64; + function GetCreateCode: String; overload; + function GetCreateCode(RemoveAutoInc, RemoveDefiner: Boolean): String; overload; + property ObjType: String read GetObjType; + property ImageIndex: Integer read GetImageIndex; + property OverlayImageIndex: Integer read GetOverlayImageIndex; + property Path: String read GetPath; + property CreateCode: String read GetCreateCode write SetCreateCode; + property WasSelected: Boolean read FWasSelected write FWasSelected; + property Connection: TDBConnection read FConnection; + property TableColumns: TTableColumnList read GetTableColumns; + property TableKeys: TTableKeyList read GetTableKeys; + property TableForeignKeys: TForeignKeyList read GetTableForeignKeys; + end; + PDBObject = ^TDBObject; + TDBObjectList = class(TObjectList) + private + FDatabase: String; + FDataSize: Int64; + FLargestObjectSize: Int64; + FLastUpdate: TDateTime; + FCollation: String; + FOnlyNodeType: TListNodeType; + public + property Database: String read FDatabase; + property DataSize: Int64 read FDataSize; + property LargestObjectSize: Int64 read FLargestObjectSize; + property LastUpdate: TDateTime read FLastUpdate; + property Collation: String read FCollation; + property OnlyNodeType: TListNodeType read FOnlyNodeType; + end; + TDatabaseCache = class(TObjectList); // A list of db object lists, used for caching + TDBObjectComparer = class(TComparer) + function Compare(const Left, Right: TDBObject): Integer; override; + end; + TDBObjectDropComparer = class(TComparer) + function Compare(const Left, Right: TDBObject): Integer; override; + end; + + TOidStringPairs = TDictionary; + // Structures for in-memory changes of a TDBQuery TGridValue = class(TObject) public @@ -464,6 +474,9 @@ type property Favorites: TStringList read FFavorites; function GetLockedTableCount(db: String): Integer; function IdentifierEquals(Ident1, Ident2: String): Boolean; + function GetTableColumns(Table: TDBObject): TTableColumnList; virtual; + function GetTableKeys(Table: TDBObject): TTableKeyList; virtual; + function GetTableForeignKeys(Table: TDBObject): TForeignKeyList; virtual; published property Active: Boolean read FActive write SetActive default False; property Database: String read FDatabase write SetDatabase; @@ -511,6 +524,7 @@ type property LastRawResults: TMySQLRawResults read FLastRawResults; function MaxAllowedPacket: Int64; override; function ExplainAnalyzer(SQL, DatabaseName: String): Boolean; override; + function GetTableKeys(Table: TDBObject): TTableKeyList; override; end; TAdoRawResults = Array of _RecordSet; @@ -602,6 +616,8 @@ type function GetCreateCode(Obj: TDBObject): String; override; function GetRowCount(Obj: TDBObject): Int64; override; property LastRawResults: TSQLiteRawResults read FLastRawResults; + function GetTableColumns(Table: TDBObject): TTableColumnList; override; + function GetTableKeys(Table: TDBObject): TTableKeyList; override; end; @@ -653,6 +669,7 @@ type function DataType(Column: Integer): TDBDataType; function MaxLength(Column: Integer): Int64; function ValueList(Column: Integer): TStringList; + // Todo: overload ColumnExists: function ColExists(Column: String): Boolean; function ColIsPrimaryKeyPart(Column: Integer): Boolean; virtual; abstract; function ColIsUniqueKeyPart(Column: Integer): Boolean; virtual; abstract; @@ -3101,7 +3118,6 @@ var ObjType: String; TmpObj: TDBObject; begin - Column := -1; TmpObj := TDBObject.Create(Self); TmpObj.NodeType := Obj.NodeType; ObjType := TmpObj.ObjType; @@ -4652,6 +4668,220 @@ begin end; +function TDBConnection.GetTableColumns(Table: TDBObject): TTableColumnList; +var + TableIdx: Integer; + ColQuery: TDBQuery; + Col: TTableColumn; + dt, SchemaClause, DefText, ExtraText: String; +begin + // Generic: query table columns from IS.COLUMNS + Result := TTableColumnList.Create(True); + TableIdx := InformationSchemaObjects.IndexOf('columns'); + if Table.Schema <> '' then + SchemaClause := 'TABLE_SCHEMA='+EscapeString(Table.Schema) + else + SchemaClause := GetSQLSpecifity(spISTableSchemaCol)+'='+EscapeString(Table.Database); + ColQuery := GetResults('SELECT * FROM '+QuoteIdent('information_schema')+'.'+QuoteIdent(InformationSchemaObjects[TableIdx])+ + ' WHERE '+SchemaClause+' AND TABLE_NAME='+EscapeString(Table.Name)); + while not ColQuery.Eof do begin + Col := TTableColumn.Create(Self); + Result.Add(Col); + Col.Name := ColQuery.Col('COLUMN_NAME'); + Col.OldName := Col.Name; + // PG/MySQL use different ones: + dt := IfThen(ColQuery.ColExists('COLUMN_TYPE'), 'COLUMN_TYPE', 'DATA_TYPE'); + Col.ParseDatatype(ColQuery.Col(dt)); + Col.Charset := ColQuery.Col('CHARACTER_SET_NAME'); + Col.Collation := ColQuery.Col('COLLATION_NAME'); + // MSSQL has no expression + Col.Expression := ColQuery.Col('GENERATION_EXPRESSION', True); + // PG has no extra: + ExtraText := ColQuery.Col('EXTRA', True); + Col.Virtuality := RegExprGetMatch('^(\w+)\s+generated$', ExtraText.ToLower, 1); + Col.AllowNull := ColQuery.Col('IS_NULLABLE').ToLower = 'yes'; + + DefText := ColQuery.Col('COLUMN_DEFAULT'); + Col.OnUpdateType := cdtNothing; + if DefText.ToLower = 'null' then begin + Col.DefaultType := cdtNull; + end else if ExecRegExpr('^auto_increment$', ExtraText.ToLower) then begin + Col.DefaultType := cdtAutoInc; + Col.DefaultText := 'AUTO_INCREMENT'; + end else if ColQuery.IsNull('COLUMN_DEFAULT') then begin + Col.DefaultType := cdtNothing; + end else if DefText.StartsWith('''') then begin + Col.DefaultType := cdtText; + Col.DefaultText := ExtractLiteral(DefText, ''); + end else begin + Col.DefaultType := cdtExpression; + Col.DefaultText := DefText; + end; + Col.OnUpdateText := RegExprGetMatch('^on update (.*)$', ExtraText, 1); + if not Col.OnUpdateText.IsEmpty then begin + Col.OnUpdateType := cdtExpression; + end; + + // PG has no column_comment: + Col.Comment := ColQuery.Col('COLUMN_COMMENT', True); + ColQuery.Next; + end; + ColQuery.Free; +end; + + +function TSQLiteConnection.GetTableColumns(Table: TDBObject): TTableColumnList; +var + ColQuery: TDBQuery; + Col: TTableColumn; +begin + // SQLite has no IS.COLUMNS + // Todo: include database name + // Todo: default values + Result := TTableColumnList.Create(True); + ColQuery := GetResults('SELECT * FROM pragma_table_info('+EscapeString(Table.Name)+')'); + while not ColQuery.Eof do begin + Col := TTableColumn.Create(Self); + Result.Add(Col); + Col.Name := ColQuery.Col('name'); + Col.OldName := Col.Name; + Col.ParseDatatype(ColQuery.Col('type')); + Col.AllowNull := ColQuery.Col('notnull') <> '1'; + Col.DefaultType := cdtNothing; + Col.DefaultText := ''; + Col.OnUpdateType := cdtNothing; + Col.OnUpdateText := ''; + ColQuery.Next; + end; + ColQuery.Free; +end; + + +function TDBConnection.GetTableKeys(Table: TDBObject): TTableKeyList; +var + ColTableIdx, ConTableIdx: Integer; + KeyQuery: TDBQuery; + NewKey: TTableKey; +begin + // Generic: query table keys from IS.KEY_COLUMN_USAGE + Result := TTableKeyList.Create(True); + ColTableIdx := InformationSchemaObjects.IndexOf('KEY_COLUMN_USAGE'); + ConTableIdx := InformationSchemaObjects.IndexOf('TABLE_CONSTRAINTS'); + KeyQuery := GetResults('SELECT * FROM '+ + QuoteIdent('information_schema')+'.'+QuoteIdent(InformationSchemaObjects[ColTableIdx])+' AS col'+ + ', '+QuoteIdent('information_schema')+'.'+QuoteIdent(InformationSchemaObjects[ConTableIdx])+' AS con'+ + ' WHERE col.TABLE_SCHEMA='+EscapeString(Database)+ + ' AND col.TABLE_NAME='+EscapeString(Table.Name)+ + ' AND col.TABLE_SCHEMA=con.TABLE_SCHEMA'+ + ' AND col.TABLE_NAME=con.TABLE_NAME'+ + ' AND col.CONSTRAINT_NAME=con.CONSTRAINT_NAME' + ); + NewKey := nil; + while not KeyQuery.Eof do begin + if (not KeyQuery.ColExists('REFERENCED_TABLE_NAME')) + or KeyQuery.Col('REFERENCED_TABLE_NAME').IsEmpty then begin + if (not Assigned(NewKey)) or (NewKey.Name <> KeyQuery.Col('CONSTRAINT_NAME')) then begin + NewKey := TTableKey.Create(Self); + Result.Add(NewKey); + NewKey.Name := KeyQuery.Col('CONSTRAINT_NAME'); + NewKey.OldName := NewKey.Name; + if KeyQuery.Col('CONSTRAINT_TYPE').ToLower.StartsWith('primary') then + NewKey.IndexType := 'PRIMARY' + else + NewKey.IndexType := KeyQuery.Col('CONSTRAINT_TYPE'); + NewKey.OldIndexType := NewKey.IndexType; + end; + NewKey.Columns.Add(KeyQuery.Col('COLUMN_NAME')); + NewKey.SubParts.Add(''); + end; + KeyQuery.Next; + end; +end; + + +function TMySQLConnection.GetTableKeys(Table: TDBObject): TTableKeyList; +var + KeyQuery: TDBQuery; + NewKey: TTableKey; +begin + Result := TTableKeyList.Create(True); + KeyQuery := GetResults('SHOW INDEXES FROM '+QuoteIdent(Table.Name)+' FROM '+QuoteIdent(Table.Database)); + NewKey := nil; + while not KeyQuery.Eof do begin + if (not Assigned(NewKey)) or (NewKey.Name <> KeyQuery.Col('Key_name')) then begin + NewKey := TTableKey.Create(Self); + Result.Add(NewKey); + NewKey.Name := KeyQuery.Col('Key_name'); + NewKey.OldName := NewKey.Name; + if NewKey.Name.ToLower = 'primary' then + NewKey.IndexType := 'PRIMARY' + else if KeyQuery.Col('Non_unique') = '0' then + NewKey.IndexType := 'UNIQUE' + else + NewKey.IndexType := 'KEY'; + // Todo: fulltext and spatial keys + NewKey.OldIndexType := NewKey.IndexType; + NewKey.Algorithm := KeyQuery.Col('Index_type'); + NewKey.Comment := KeyQuery.Col('Index_comment'); + end; + NewKey.Columns.Add(KeyQuery.Col('Column_name')); + NewKey.SubParts.Add(KeyQuery.Col('Sub_part')); + KeyQuery.Next; + end; +end; + + +function TSQLiteConnection.GetTableKeys(Table: TDBObject): TTableKeyList; +var + ColQuery, KeyQuery: TDBQuery; + NewKey: TTableKey; +begin + Result := TTableKeyList.Create(True); + ColQuery := GetResults('SELECT * FROM pragma_table_info('+EscapeString(Table.Name)+') WHERE pk!=0 ORDER BY pk'); + NewKey := nil; + while not ColQuery.Eof do begin + if not Assigned(NewKey) then begin + NewKey := TTableKey.Create(Self); + Result.Add(NewKey); + NewKey.Name := 'PRIMARY'; + NewKey.OldName := NewKey.Name; + NewKey.IndexType := 'PRIMARY'; + NewKey.OldIndexType := NewKey.IndexType; + end; + NewKey.Columns.Add(ColQuery.Col('name')); + NewKey.SubParts.Add(''); + ColQuery.Next; + end; + ColQuery.Free; + + KeyQuery := GetResults('SELECT * FROM pragma_index_list('+EscapeString(Table.Name)+') WHERE origin!='+EscapeString('pk')); + while not KeyQuery.Eof do begin + NewKey := TTableKey.Create(Self); + Result.Add(NewKey); + NewKey.Name := KeyQuery.Col('name'); + NewKey.OldName := NewKey.Name; + NewKey.IndexType := IfThen(KeyQuery.Col('unique')='0', 'KEY', 'UNIQUE'); + NewKey.OldIndexType := NewKey.IndexType; + ColQuery := GetResults('SELECT * FROM pragma_index_info('+EscapeString(NewKey.Name)+')'); + while not ColQuery.Eof do begin + NewKey.Columns.Add(ColQuery.Col('name')); + NewKey.SubParts.Add(''); + ColQuery.Next; + end; + ColQuery.Free; + KeyQuery.Next; + end; + KeyQuery.Free; +end; + + +function TDBConnection.GetTableForeignKeys(Table: TDBObject): TForeignKeyList; +begin + // Generic: query table foreign keys from IS.? + Result := TForeignKeyList.Create(True); +end; + + function TMySQLConnection.GetRowCount(Obj: TDBObject): Int64; var Rows: String; @@ -4755,6 +4985,8 @@ begin Ping(True); if not Assigned(FInformationSchemaObjects) then begin FInformationSchemaObjects := TStringList.Create; + // Need to find strings case insensitively: + FInformationSchemaObjects.CaseSensitive := False; // Gracefully return an empty list on old servers if AllDatabases.IndexOf('information_schema') > -1 then begin Objects := GetDBObjects('information_schema'); @@ -7347,15 +7579,17 @@ begin if Obj = nil then raise EDbError.Create(f_('Could not find table or view %s.%s. Please refresh database tree.', [DB, TableName])); end; - CreateCode := Connection.GetCreateCode(Obj); - FColumns := TTableColumnList.Create; - FKeys := TTableKeyList.Create; - FForeignKeys := TForeignKeyList.Create; case Obj.NodeType of - lntTable: - Connection.ParseTableStructure(CreateCode, FColumns, FKeys, FForeignKeys); - lntView: + lntTable: begin + FColumns := Obj.TableColumns; + FKeys := Obj.TableKeys; + FForeignKeys := Obj.TableForeignKeys; + end; + lntView: begin + CreateCode := Connection.GetCreateCode(Obj); + FColumns := TTableColumnList.Create; Connection.ParseViewStructure(CreateCode, Obj, FColumns, Dummy, Dummy, Dummy, Dummy, Dummy); + end; end; end; @@ -8066,6 +8300,9 @@ begin ArgTypes := s.ArgTypes; FCreateCode := s.FCreateCode; FCreateCodeFetched := s.FCreateCodeFetched; + FTableColumns := s.FTableColumns; + FTableKeys := s.FTableKeys; + FTableForeignKeys := s.FTableForeignKeys; end else inherited; end; @@ -8277,12 +8514,55 @@ begin end; +function TDBObject.GetTableColumns: TTableColumnList; +begin + // Return columns from table object + if not Assigned(FTableColumns) then begin + FTableColumns := Connection.GetTableColumns(Self); + end; + Result := FTableColumns; +end; + +function TDBObject.GetTableKeys: TTableKeyList; +begin + // Return keys from table object + if not Assigned(FTableKeys) then begin + FTableKeys := Connection.GetTableKeys(Self); + end; + Result := FTableKeys; +end; + +function TDBObject.GetTableForeignKeys: TForeignKeyList; +begin + // Return foreign keys from table object + if not Assigned(FTableForeignKeys) then begin + FTableForeignKeys := Connection.GetTableForeignKeys(Self); + end; + Result := FTableForeignKeys; +end; + + + { *** TTableColumn } constructor TTableColumn.Create(AOwner: TDBConnection); begin inherited Create; FConnection := AOwner; + Status := esUntouched; + LengthCustomized := False; + Unsigned := False; + ZeroFill := False; + Charset := ''; + Collation := ''; + Expression := ''; + Virtuality := ''; + AllowNull := True; + DefaultType := cdtNothing; + DefaultText := ''; + OnUpdateType := cdtNothing; + OnUpdateText := ''; + Comment := ''; end; destructor TTableColumn.Destroy; @@ -8392,6 +8672,33 @@ begin end; +procedure TTableColumn.ParseDatatype(Source: String); +var + InLiteral: Boolean; + ParenthLeft, i: Integer; +begin + DataType := Connection.GetDatatypeByName(Source, True); + // Length / Set + // Various datatypes, e.g. BLOBs, don't have any length property + InLiteral := False; + ParenthLeft := Pos('(', Source); + if ParenthLeft > 0 then begin + for i:=ParenthLeft+1 to Length(Source) do begin + if (Source[i] = ')') and (not InLiteral) then + break; + if Source[i] = '''' then + InLiteral := not InLiteral; + end; + LengthSet := Copy(Source, ParenthLeft+1, i-2); + Unsigned := ExecRegExpr('\sunsigned[\s\w]*$', Source.ToLower); + ZeroFill := ExecRegExpr('\szerofill[\s\w]*$', Source.ToLower); + end else begin + LengthSet := ''; + Unsigned := False; + end; +end; + + function TTableColumn.CastAsText: String; begin // Cast data types which are incompatible to string functions to text columns diff --git a/source/insertfiles.pas b/source/insertfiles.pas index 065b069a..1680674c 100644 --- a/source/insertfiles.pas +++ b/source/insertfiles.pas @@ -353,9 +353,8 @@ begin // Populate combobox with columns from selected table ListColumns.Clear; if comboTables.ItemIndex > -1 then begin - Columns := TTableColumnList.Create(True); Selected := FConnection.FindObject(comboDBs.Text, comboTables.Text); - FConnection.ParseTableStructure(Selected.CreateCode, Columns, nil, nil); + Columns := Selected.TableColumns; Node := nil; for Col in Columns do begin ColInfo := TColInfo.Create; diff --git a/source/loaddata.pas b/source/loaddata.pas index c8006330..1611b036 100644 --- a/source/loaddata.pas +++ b/source/loaddata.pas @@ -263,7 +263,7 @@ begin for Obj in DBObjects do begin if (Obj.Database=comboDatabase.Text) and (Obj.Name=comboTable.Text) then begin case Obj.NodeType of - lntTable: Obj.Connection.ParseTableStructure(Obj.CreateCode, Columns, nil, nil); + lntTable: Columns := Obj.TableColumns; lntView: Obj.Connection.ParseViewStructure(Obj.CreateCode, Obj, Columns, DummyStr, DummyStr, DummyStr, DummyStr, DummyStr); end; end; diff --git a/source/main.pas b/source/main.pas index b4578f7d..dd103c73 100644 --- a/source/main.pas +++ b/source/main.pas @@ -6003,12 +6003,13 @@ var DBObjects := Conn.GetDBObjects(dbname); for Obj in DBObjects do begin if Obj.Name = tblname then begin - Columns := TTableColumnList.Create(True); case Obj.NodeType of lntTable: - Conn.ParseTableStructure(Obj.CreateCode, Columns, nil, nil); - lntView: + Columns := Obj.TableColumns; + lntView: begin + Columns := TTableColumnList.Create(True); Conn.ParseViewStructure(Obj.CreateCode, Obj, Columns, Dummy, Dummy, Dummy, Dummy, Dummy); + end; end; for Col in Columns do begin Proposal.InsertList.Add(Col.Name); @@ -8470,8 +8471,7 @@ begin end; lntTable: if GetParentFormOrFrame(Sender) is TfrmSelectDBObject then begin - Columns := TTableColumnList.Create(True); - DBObj.Connection.ParseTableStructure(DBObj.CreateCode, Columns, nil, nil); + Columns := DBObj.TableColumns; ChildCount := Columns.Count; end; end; @@ -8533,8 +8533,7 @@ begin lntTable: begin Item^ := TDBObject.Create(ParentObj.Connection); Item.NodeType := lntColumn; - Columns := TTableColumnList.Create(True); - ParentObj.Connection.ParseTableStructure(ParentObj.CreateCode, Columns, nil, nil); + Columns := ParentObj.TableColumns; Item.Database := ParentObj.Database; Item.Name := ParentObj.Name; Item.Column := Columns[Node.Index].Name; @@ -8611,8 +8610,11 @@ begin InvalidateVT(DataGrid, VTREE_NOTLOADED_PURGECACHE, False); try case FActiveDbObj.NodeType of - lntTable: - FActiveDbObj.Connection.ParseTableStructure(FActiveDbObj.CreateCode, SelectedTableColumns, SelectedTableKeys, SelectedTableForeignKeys); + lntTable: begin + SelectedTableColumns := FActiveDbObj.TableColumns; + SelectedTableKeys := FActiveDbObj.TableKeys; + SelectedTableForeignKeys := FActiveDbObj.TableForeignKeys; + end; lntView: FActiveDbObj.Connection.ParseViewStructure(FActiveDbObj.CreateCode, FActiveDbObj, SelectedTableColumns, DummyStr, DummyStr, DummyStr, DummyStr, DummyStr); end; @@ -9515,6 +9517,8 @@ var ForeignResults, Results: TDBQuery; Conn: TDBConnection; RowNum: PInt64; + RefDb, RefTable: String; + RefObj: TDBObject; begin VT := Sender as TVirtualStringTree; Results := GridResult(VT); @@ -9528,16 +9532,22 @@ begin idx := ForeignKey.Columns.IndexOf(DataGrid.Header.Columns[Column].Text); if idx > -1 then try // Find the first text column if available and use that for displaying in the pulldown instead of using meaningless id numbers - CreateTable := Conn.GetVar('SHOW CREATE TABLE '+Conn.QuoteIdent(ForeignKey.ReferenceTable, True, '.'), 1); - Columns := TTableColumnList.Create; - Keys := nil; - ForeignKeys := nil; - Conn.ParseTableStructure(CreateTable, Columns, Keys, ForeignKeys); + RefDb := ForeignKey.ReferenceTable.Substring(1, Pos('.', ForeignKey.ReferenceTable)); + if not RefDb.IsEmpty then begin + RefTable := ForeignKey.ReferenceTable.Substring(Length(RefDb)+1); + end else begin + RefDb := Conn.Database; + RefTable := ForeignKey.ReferenceTable; + end; + RefObj := Conn.FindObject(RefDb, RefTable); TextCol := ''; - for TblColumn in Columns do begin - if (TblColumn.DataType.Category = dtcText) and (TblColumn.Name <> ForeignKey.ForeignColumns[idx]) then begin - TextCol := TblColumn.Name; - break; + if Assigned(RefObj) then begin + Columns := RefObj.TableColumns; + for TblColumn in Columns do begin + if (TblColumn.DataType.Category = dtcText) and (TblColumn.Name <> ForeignKey.ForeignColumns[idx]) then begin + TextCol := TblColumn.Name; + break; + end; end; end; @@ -10196,8 +10206,7 @@ begin IS_objects := Conn.GetDBObjects('information_schema'); for Obj in IS_objects do begin if Obj.Name = 'PROCESSLIST' then begin - ProcessColumns := TTableColumnList.Create; - Conn.ParseTableStructure(Obj.CreateCode, ProcessColumns, nil, nil); + ProcessColumns := Obj.TableColumns; for i:=8 to ProcessColumns.Count-1 do begin Columns := Columns + ', '+Conn.QuoteIdent(ProcessColumns[i].Name); if ListProcesses.Header.Columns.Count <= i then diff --git a/source/table_editor.pas b/source/table_editor.pas index 5e34a224..d69bc03d 100644 --- a/source/table_editor.pas +++ b/source/table_editor.pas @@ -332,7 +332,9 @@ begin else SynMemoPartitions.Clear; - DBObject.Connection.ParseTableStructure(DBObject.CreateCode, FColumns, FKeys, FForeignKeys); + FColumns := DBObject.TableColumns; + FKeys := DBObject.TableKeys; + FForeignKeys := DBObject.TableForeignKeys; end; listColumns.RootNodeCount := FColumns.Count; DeInitializeVTNodes(listColumns); @@ -2374,7 +2376,7 @@ var Key, OtherKey: TForeignKey; i, j, k: Integer; NameInUse: Boolean; - RefCreateCode, RefDatabase, RefTable: String; + RefDatabase, RefTable: String; KeyColumnsSQLCode, RefColumnsSQLCode: String; Err: String; RefColumns: TTableColumnList; @@ -2429,9 +2431,7 @@ begin RefObj.Name := RefTable; RefObj.Database := RefDatabase; RefObj.NodeType := lntTable; - RefCreateCode := DBObject.Connection.GetCreateCode(RefObj); - RefColumns := TTableColumnList.Create(True); - DBObject.Connection.ParseTableStructure(RefCreateCode, RefColumns, nil, nil); + RefColumns := RefObj.TableColumns; TypesMatch := True; KeyColumnsSQLCode := ''; RefColumnsSQLCode := ''; diff --git a/source/tabletools.pas b/source/tabletools.pas index f76c59cc..1f7025f8 100644 --- a/source/tabletools.pas +++ b/source/tabletools.pas @@ -875,7 +875,7 @@ begin Columns := TTableColumnList.Create(True); case DBObj.NodeType of - lntTable: DBObj.Connection.ParseTableStructure(DBObj.CreateCode, Columns, nil, nil); + lntTable: Columns := DBObj.TableColumns; lntView: DBObj.Connection.ParseViewStructure(DBObj.CreateCode, DBObj, Columns, Dummy, Dummy, Dummy, Dummy, Dummy); lntProcedure, lntFunction: ; // TODO: Triggers + Events