From 9fc7150a9fc15d400a836df8b20765a1864a9c80 Mon Sep 17 00:00:00 2001 From: Ansgar Becker Date: Sun, 21 Apr 2019 15:40:18 +0200 Subject: [PATCH] Issue #74: attempt to support all kinds of expressions in column DEFAULT + ON UPDATE clauses --- source/apphelpers.pas | 11 +- source/dbconnection.pas | 133 ++++++++++++----------- source/editvar.pas | 2 +- source/grideditlinks.pas | 227 ++++++++++++++++++++++----------------- source/main.pas | 4 +- source/table_editor.pas | 68 +++++++----- 6 files changed, 245 insertions(+), 200 deletions(-) diff --git a/source/apphelpers.pas b/source/apphelpers.pas index e8f9ac5a..26dc3a11 100644 --- a/source/apphelpers.pas +++ b/source/apphelpers.pas @@ -275,7 +275,8 @@ type function MakeInt(Str: String) : Int64; function MakeFloat(Str: String): Extended; function CleanupNumber(Str: String): String; - function IsNumeric(Str: String): Boolean; + function IsInt(Str: String): Boolean; + function IsFloat(Str: String): Boolean; function esc(Text: String; ProcessJokerChars: Boolean=false; DoQuote: Boolean=True): String; function ScanLineBreaks(Text: String): TLineBreaks; function CountLineBreaks(Text: String; LineBreak: TLineBreaks=lbsWindows): Cardinal; @@ -637,12 +638,18 @@ begin end; -function IsNumeric(Str: String): Boolean; +function IsInt(Str: String): Boolean; begin Result := IntToStr(MakeInt(Str)) = Str; end; +function IsFloat(Str: String): Boolean; +begin + Result := FloatToStr(MakeFloat(Str)) = Str; +end; + + {*** Convert a string-number to an floatingpoint-number diff --git a/source/dbconnection.pas b/source/dbconnection.pas index ceefae62..5c160462 100644 --- a/source/dbconnection.pas +++ b/source/dbconnection.pas @@ -86,7 +86,7 @@ type // General purpose editing status flag TEditingStatus = (esUntouched, esModified, esDeleted, esAddedUntouched, esAddedModified, esAddedDeleted); - TColumnDefaultType = (cdtNothing, cdtText, cdtTextUpdateTS, cdtNull, cdtNullUpdateTS, cdtCurTS, cdtCurTSUpdateTS, cdtAutoInc); + TColumnDefaultType = (cdtNothing, cdtText, cdtNull, cdtAutoInc, cdtExpression); // Column object, many of them in a TObjectList TTableColumn = class(TObject) @@ -100,6 +100,8 @@ type Unsigned, AllowNull, ZeroFill, LengthCustomized: Boolean; DefaultType: TColumnDefaultType; DefaultText: String; + OnUpdateType: TColumnDefaultType; + OnUpdateText: String; Comment, Charset, Collation, Expression, Virtuality: String; FStatus: TEditingStatus; constructor Create(AOwner: TDBConnection); @@ -5068,8 +5070,8 @@ procedure TDBConnection.ParseTableStructure(CreateTable: String; Columns: TTable var ColSpec, Quotes: String; rx, rxCol: TRegExpr; - i, LiteralStart: Integer; - InLiteral, IsLiteral: Boolean; + i: Integer; + InLiteral: Boolean; Col: TTableColumn; Key: TTableKey; ForeignKey: TForeignKey; @@ -5091,9 +5093,13 @@ begin rx.Expression := '^\s+['+Quotes+']'; rxCol := TRegExpr.Create; rxCol.ModifierI := True; + // Make it ungreedy, so words on the right don't become a part of left matches + rxCol.ModifierG := False; if rx.Exec(CreateTable) then while true do begin if not Assigned(Columns) then - break; + Break; + // Will also break if .ExecNext at the end fails + ColSpec := Copy(CreateTable, rx.MatchPos[0], SIZE_MB); ColSpec := Copy(ColSpec, 1, Pos(#10, ColSpec)); ColSpec := Trim(ColSpec); @@ -5127,29 +5133,33 @@ begin // Unsigned if UpperCase(Copy(ColSpec, 1, 8)) = 'UNSIGNED' then begin Col.Unsigned := True; - Delete(ColSpec, 1, 9); + Delete(ColSpec, 1, 8); + ColSpec := Trim(ColSpec); end else Col.Unsigned := False; // Zero fill if UpperCase(Copy(ColSpec, 1, 8)) = 'ZEROFILL' then begin Col.ZeroFill := True; - Delete(ColSpec, 1, 9); + Delete(ColSpec, 1, 8); + ColSpec := Trim(ColSpec); end else Col.ZeroFill := False; // Charset - rxCol.Expression := '^CHARACTER SET (\w+)\b\s*'; + rxCol.Expression := '^CHARACTER SET (\w+)\b'; if rxCol.Exec(ColSpec) then begin Col.Charset := rxCol.Match[1]; Delete(ColSpec, 1, rxCol.MatchLen[0]); + ColSpec := Trim(ColSpec); end; // Collation - probably not present when charset present - rxCol.Expression := '^COLLATE (\w+)\b\s*'; + rxCol.Expression := '^COLLATE\s+(\w+)\b'; if rxCol.Exec(ColSpec) then begin Col.Collation := rxCol.Match[1]; Delete(ColSpec, 1, rxCol.MatchLen[0]); + ColSpec := Trim(ColSpec); end; if Col.Collation = '' then begin if Assigned(Collations) then begin @@ -5165,76 +5175,68 @@ begin end; // Virtual columns - rxCol.Expression := '^(GENERATED ALWAYS)?\s*AS\s+\((.+)\)\s+(VIRTUAL|PERSISTENT|STORED)\s*'; + rxCol.Expression := '^(GENERATED ALWAYS\s+)?AS\s+\((.+)\)\s+(VIRTUAL|PERSISTENT|STORED)'; if rxCol.Exec(ColSpec) then begin Col.Expression := rxCol.Match[2]; Col.Virtuality := rxCol.Match[3]; Delete(ColSpec, 1, rxCol.MatchLen[0]); + ColSpec := Trim(ColSpec); end; // Allow NULL if UpperCase(Copy(ColSpec, 1, 8)) = 'NOT NULL' then begin Col.AllowNull := False; - Delete(ColSpec, 1, 9); + Delete(ColSpec, 1, 8); end else begin Col.AllowNull := True; // Sporadically there is a "NULL" found at this position. if UpperCase(Copy(ColSpec, 1, 4)) = 'NULL' then - Delete(ColSpec, 1, 5); + Delete(ColSpec, 1, 4); end; + ColSpec := Trim(ColSpec); - // Default value + // Default value detection + // Should detect auto increment (without "default" keyword), null, quoted text, and expressions like CURRENT_TIMESTAMP Col.DefaultType := cdtNothing; Col.DefaultText := ''; - rxCol.Expression := '(NULL|CURRENT_TIMESTAMP(\(\d*\))?|\''[^\'']+\'')(\s+ON\s+UPDATE\s+CURRENT_TIMESTAMP(\(\d*\))?)?'; + Col.OnUpdateType := cdtNothing; + Col.OnUpdateText := ''; if UpperCase(Copy(ColSpec, 1, 14)) = 'AUTO_INCREMENT' then begin Col.DefaultType := cdtAutoInc; Col.DefaultText := 'AUTO_INCREMENT'; Delete(ColSpec, 1, 15); end else if UpperCase(Copy(ColSpec, 1, 8)) = 'DEFAULT ' then begin Delete(ColSpec, 1, 8); - // Literal values may match the regex as well. See http://www.heidisql.com/forum.php?t=17862 - IsLiteral := (ColSpec[1] = '''') or (Copy(ColSpec, 1, 2) = 'b''') or (Copy(ColSpec, 1, 2) = '('''); - if rxCol.Exec(ColSpec) and (not IsLiteral) then begin - if rxCol.Match[1] = 'NULL' then begin - Col.DefaultType := cdtNull; - Col.DefaultText := 'NULL'; - if rxCol.Match[3] <> '' then - Col.DefaultType := cdtNullUpdateTS; - Delete(ColSpec, 1, rxCol.MatchLen[0]); - end else if StartsText('CURRENT_TIMESTAMP', rxCol.Match[1]) then begin - Col.DefaultType := cdtCurTS; - Col.DefaultText := rxCol.Match[1]; - if rxCol.Match[3] <> '' then - Col.DefaultType := cdtCurTSUpdateTS; - Delete(ColSpec, 1, rxCol.MatchLen[0]); - end else begin + ColSpec := Trim(ColSpec); + + // To retrieve the default value, get everything up to the end or up to a potential keywords mentioned on + // https://mariadb.com/kb/en/library/create-table/#column-definitions + rxCol.Expression := '^(.*)($|\s+(ON UPDATE|COLUMN_FORMAT|COMMENT|INVISIBLE)\b)'; + if rxCol.Exec(ColSpec) then begin + Col.DefaultText := Trim(rxCol.Match[1]); + Col.DefaultText := Col.DefaultText.TrimRight([',']); + + if Col.DefaultText.StartsWith('''') then begin Col.DefaultType := cdtText; - Col.DefaultText := ExtractLiteral(ColSpec, ''); - if Col.DefaultText.IsEmpty then - Col.DefaultText := RegExprGetMatch('\s*(\S+)', ColSpec, 1, True); - if rxCol.Match[3] <> '' then - Col.DefaultType := cdtTextUpdateTS; + Col.DefaultText := ExtractLiteral(Col.DefaultText, ''); + end else if UpperCase(Col.DefaultText) = 'NULL' then begin + Col.DefaultType := cdtNull + end else begin + Col.DefaultType := cdtExpression; end; - end else if IsLiteral then begin - InLiteral := True; - LiteralStart := Pos('''', ColSpec)+1; - for i:=LiteralStart to Length(ColSpec) do begin - if ColSpec[i] = '''' then - InLiteral := not InLiteral - else if not InLiteral then - break; + Delete(ColSpec, 1, rxCol.MatchLen[1]); + + // Do the same for a potentially existing ON UPDATE clause + rxCol.Expression := '^\s*ON UPDATE\s+(.*)($|\s+(COLUMN_FORMAT|COMMENT|INVISIBLE)\b)'; + if rxCol.Exec(ColSpec) then begin + Col.OnUpdateText := Trim(rxCol.Match[1]); + Col.OnUpdateText := Col.OnUpdateText.TrimRight([',']); + Col.OnUpdateType := cdtExpression; + Delete(ColSpec, 1, rxCol.MatchLen[1]); end; - Col.DefaultType := cdtText; - Col.DefaultText := Copy(ColSpec, LiteralStart, i-LiteralStart-1); - // A linefeed needs to display as "\n" but a single quote must not contain a backslash here - Col.DefaultText := EscapeString(UnescapeString(Col.DefaultText), False, False); - Col.DefaultText := StringReplace(Col.DefaultText, '\''', '''', [rfReplaceAll]); - Delete(ColSpec, 1, i); - end else begin - Col.DefaultType := cdtText; - Col.DefaultText := getFirstWord(ColSpec, False); + end; + end; // Comment @@ -5400,10 +5402,10 @@ begin Col.DefaultType := cdtNull else Col.DefaultType := cdtNothing; - end else if Col.DataType.Index = dtTimestamp then - Col.DefaultType := cdtCurTSUpdateTS + end else if Col.DataType.Category = dtcText then + Col.DefaultType := cdtText else - Col.DefaultType := cdtText; + Col.DefaultType := cdtExpression; Results.Next; end; rx.Free; @@ -6630,8 +6632,8 @@ begin c.OldIsNull := False; ColAttr := ColAttributes(i); if Assigned(ColAttr) then begin - c.OldIsNull := ColAttr.DefaultType in [cdtNull, cdtNullUpdateTS, cdtAutoInc]; - if ColAttr.DefaultType in [cdtText, cdtTextUpdateTS] then + c.OldIsNull := ColAttr.DefaultType in [cdtNull, cdtAutoInc]; + if ColAttr.DefaultType in [cdtText] then c.OldText := FConnection.UnescapeString(ColAttr.DefaultText); end; c.NewText := c.OldText; @@ -7457,22 +7459,23 @@ begin end; if DefaultType <> cdtNothing then begin Text := esc(DefaultText); - // Support BIT syntax in MySQL - if (DataType.Index = dtBit) and FConnection.Parameters.IsMySQL then - Text := 'b'+Text; TSLen := ''; if LengthSet <> '' then TSLen := '('+LengthSet+')'; Result := Result + ' '; case DefaultType of - // cdtNothing: - cdtText: Result := Result + 'DEFAULT '+Text; - cdtTextUpdateTS: Result := Result + 'DEFAULT '+Text+' ON UPDATE CURRENT_TIMESTAMP'+TSLen; + // cdtNothing: leave out whole clause + cdtText: Result := Result + 'DEFAULT '+esc(DefaultText); cdtNull: Result := Result + 'DEFAULT NULL'; - cdtNullUpdateTS: Result := Result + 'DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP'+TSLen; - cdtCurTS: Result := Result + 'DEFAULT CURRENT_TIMESTAMP'+TSLen; - cdtCurTSUpdateTS: Result := Result + 'DEFAULT CURRENT_TIMESTAMP'+TSLen+' ON UPDATE CURRENT_TIMESTAMP'+TSLen; cdtAutoInc: Result := Result + 'AUTO_INCREMENT'; + cdtExpression: Result := Result + 'DEFAULT '+DefaultText; + end; + case OnUpdateType of + // cdtNothing: leave out whole clause + // cdtText: not supported, but may be valid in MariaDB? + // cdtNull: not supported, but may be valid in MariaDB? + // cdtAutoInc: not valid in ON UPDATE + cdtExpression: Result := Result + ' ON UPDATE '+OnUpdateText; end; Result := TrimRight(Result); // Remove whitespace for columns without default value end; diff --git a/source/editvar.pas b/source/editvar.pas index 8be08ad3..68e58980 100644 --- a/source/editvar.pas +++ b/source/editvar.pas @@ -95,7 +95,7 @@ var begin // Verify variable type by value FVarType := vtString; - if IsNumeric(FVarValue) then + if IsInt(FVarValue) then FVarType := vtNumeric; if (FVar.EnumValues <> '') and (Pos(UpperCase(FVarValue), UpperCase(FVar.EnumValues))>0) then FVarType := vtEnum; diff --git a/source/grideditlinks.pas b/source/grideditlinks.pas index a5f6999a..4075993e 100644 --- a/source/grideditlinks.pas +++ b/source/grideditlinks.pas @@ -157,20 +157,22 @@ type TColumnDefaultEditorLink = class(TBaseGridEditorLink) private FPanel: TPanel; - FRadioNothing, FRadioText, FRadioNULL, FRadioCurTS, FRadioAutoInc: TAllKeysRadioButton; - FCheckCurTS: TAllKeysCheckbox; - FCustomEdit: TButtonedEdit; - FCustomDropDown: TPopupMenu; + FRadioNothing, FRadioText, FRadioNULL, FRadioExpression, FRadioAutoInc: TAllKeysRadioButton; + FlblOnUpdate: TLabel; + FTextEdit: TButtonedEdit; + FTextDropDown: TPopupMenu; + FExpressionEdit: TButtonedEdit; + FOnUpdateEdit: TButtonedEdit; FBtnOK, FBtnCancel: TButton; FEndTimer: TTimer; procedure RadioClick(Sender: TObject); - procedure CustomEditChange(Sender: TObject); - procedure CustomDropDownClick(Sender: TObject); + procedure EditChange(Sender: TObject); + procedure EditDropDownClick(Sender: TObject); procedure BtnOkClick(Sender: TObject); procedure BtnCancelClick(Sender: TObject); public - DefaultType: TColumnDefaultType; - DefaultText: String; + DefaultType, OnUpdateType: TColumnDefaultType; + DefaultText, OnUpdateText: String; constructor Create(Tree: TVirtualStringTree); override; destructor Destroy; override; function BeginEdit: Boolean; override; @@ -1196,7 +1198,7 @@ end; constructor TColumnDefaultEditorLink.Create(Tree: TVirtualStringTree); const - m = 3; + m = 5; begin inherited Create(Tree); @@ -1204,7 +1206,7 @@ begin FPanel.Hide; FPanel.Parent := FParentForm; FPanel.OnExit := DoEndEdit; - FPanel.Width := 200; + FPanel.Width := GetParentForm(FPanel).Canvas.TextWidth('CURRENT_TIMESTAMP()') + 4*m; FPanel.ParentBackground := False; FPanel.Color := GetThemeColor(clWindow); FPanel.BevelKind := bkFlat; @@ -1227,50 +1229,64 @@ begin FRadioText.Width := FRadioText.Parent.Width - 2 * FRadioText.Left; FRadioText.OnClick := RadioClick; FRadioText.OnKeyDown := DoKeyDown; - FRadioText.Caption := _('Custom')+':'; + FRadioText.Caption := _('Custom text')+':'; - FCustomDropDown := TPopupMenu.Create(FPanel); + FTextDropDown := TPopupMenu.Create(FPanel); - FCustomEdit := TButtonedEdit.Create(FPanel); - FCustomEdit.Parent := FPanel; - FCustomEdit.Top := FRadioText.Top + FRadioText.Height + m; - FCustomEdit.Left := 2*m; - FCustomEdit.Width := FCustomEdit.Parent.Width - FCustomEdit.Left - m; - FCustomEdit.OnChange := CustomEditChange; - FCustomEdit.Images := Tree.Images; - FCustomEdit.RightButton.ImageIndex := 75; // Drop down arrow - FCustomEdit.RightButton.DropDownMenu := FCustomDropDown; + FTextEdit := TButtonedEdit.Create(FPanel); + FTextEdit.Parent := FPanel; + FTextEdit.Top := FRadioText.Top + FRadioText.Height + m; + FTextEdit.Left := 2*m; + FTextEdit.Width := FTextEdit.Parent.Width - 2*FTextEdit.Left; + FTextEdit.OnChange := EditChange; + FTextEdit.Images := Tree.Images; + FTextEdit.RightButton.ImageIndex := 75; // Drop down arrow + FTextEdit.RightButton.DropDownMenu := FTextDropDown; FRadioNull := TAllKeysRadioButton.Create(FPanel); FRadioNull.Parent := FPanel; - FRadioNull.Top := FCustomEdit.Top + FCustomEdit.Height + 2*m; + FRadioNull.Top := FTextEdit.Top + FTextEdit.Height + 2*m; FRadioNull.Left := m; FRadioNull.Width := FRadioNull.Parent.Width - 2 * FRadioNull.Left; FRadioNull.OnClick := RadioClick; FRadioNull.OnKeyDown := DoKeyDown; FRadioNull.Caption := 'NULL'; - FRadioCurTS := TAllKeysRadioButton.Create(FPanel); - FRadioCurTS.Parent := FPanel; - FRadioCurTS.Top := FRadioNull.Top + FRadioNull.Height + m; - FRadioCurTS.Left := m; - FRadioCurTS.Width := FRadioCurTS.Parent.Width - 2 * FRadioCurTS.Left; - FRadioCurTS.OnClick := RadioClick; - FRadioCurTS.OnKeyDown := DoKeyDown; - FRadioCurTS.Caption := 'CURRENT_TIMESTAMP'; + FRadioExpression := TAllKeysRadioButton.Create(FPanel); + FRadioExpression.Parent := FPanel; + FRadioExpression.Top := FRadioNull.Top + FRadioNull.Height + m; + FRadioExpression.Left := m; + FRadioExpression.Width := FRadioExpression.Parent.Width - 2 * FRadioExpression.Left; + FRadioExpression.OnClick := RadioClick; + FRadioExpression.OnKeyDown := DoKeyDown; + FRadioExpression.Caption := _('Expression')+':'; - FCheckCurTS := TAllKeysCheckbox.Create(FPanel); - FCheckCurTS.Parent := FPanel; - FCheckCurTS.Top := FRadioCurTS.Top + FRadioCurTS.Height + m; - FCheckCurTS.Left := m; - FCheckCurTS.Width := FCheckCurTS.Parent.Width - 2 * FCheckCurTS.Left; - FCheckCurTS.OnClick := RadioClick; - FCheckCurTS.OnKeyDown := DoKeyDown; - FCheckCurTS.Caption := 'ON UPDATE CURRENT_TIMESTAMP'; + FExpressionEdit := TButtonedEdit.Create(FPanel); + FExpressionEdit.Parent := FPanel; + FExpressionEdit.Top := FRadioExpression.Top + FRadioExpression.Height + m; + FExpressionEdit.Left := 2*m; + FExpressionEdit.Width := FExpressionEdit.Parent.Width - 2*FExpressionEdit.Left; + FExpressionEdit.OnChange := EditChange; + FExpressionEdit.Images := Tree.Images; + + FlblOnUpdate := TLabel.Create(FPanel); + FlblOnUpdate.Parent := FPanel; + FlblOnUpdate.Top := FExpressionEdit.Top + FExpressionEdit.Height + m; + FlblOnUpdate.Left := 2*m; + FlblOnUpdate.Width := FlblOnUpdate.Parent.Width - 2*FlblOnUpdate.Left; + FlblOnUpdate.Caption := _('On update') + ':'; + + FOnUpdateEdit := TButtonedEdit.Create(FPanel); + FOnUpdateEdit.Parent := FPanel; + FOnUpdateEdit.Top := FlblOnUpdate.Top + FlblOnUpdate.Height + m; + FOnUpdateEdit.Left := 2*m; + FOnUpdateEdit.Width := FOnUpdateEdit.Parent.Width - 2*FOnUpdateEdit.Left; + FOnUpdateEdit.OnChange := EditChange; + FOnUpdateEdit.Images := Tree.Images; FRadioAutoInc := TAllKeysRadioButton.Create(FPanel); FRadioAutoInc.Parent := FPanel; - FRadioAutoInc.Top := FCheckCurTS.Top + FCheckCurTS.Height + m; + FRadioAutoInc.Top := FOnUpdateEdit.Top + FOnUpdateEdit.Height + m; FRadioAutoInc.Left := m; FRadioAutoInc.Width := FRadioAutoInc.Parent.Width - 2 * FRadioAutoInc.Left; FRadioAutoInc.OnClick := RadioClick; @@ -1281,7 +1297,7 @@ begin FBtnOk.Parent := FPanel; FBtnOk.Width := 60; FBtnOk.Top := FRadioAutoInc.Top + FRadioAutoInc.Height + m; - FBtnOk.Left := FPanel.Width - 2*m - 2*FBtnOk.Width; + FBtnOk.Left := FPanel.Width - 3*m - 2*FBtnOk.Width - 2*FPanel.BorderWidth; FBtnOk.OnClick := BtnOkClick; FBtnOk.Default := True; FBtnOk.Caption := _('OK'); @@ -1299,17 +1315,16 @@ begin FEndTimer.Interval := 50; FEndTimer.Enabled := False; - FPanel.Height := FBtnOk.Top + FBtnOk.Height + m; + FPanel.Height := 2*FPanel.BorderWidth + FBtnOk.Top + FBtnOk.Height + 2*m; FRadioNothing.Anchors := [akLeft, akTop, akRight]; FRadioText.Anchors := [akLeft, akTop, akRight]; - FCustomEdit.Anchors := [akLeft, akTop, akRight, akBottom]; + FTextEdit.Anchors := [akLeft, akTop, akRight, akBottom]; FRadioNull.Anchors := [akLeft, akBottom, akRight]; - FRadioCurTS.Anchors := [akLeft, akBottom, akRight]; - FCheckCurTS.Anchors := [akLeft, akBottom, akRight]; + FRadioExpression.Anchors := [akLeft, akBottom, akRight]; + FExpressionEdit.Anchors := [akLeft, akBottom, akRight]; FRadioAutoInc.Anchors := [akLeft, akBottom, akRight]; FBtnOk.Anchors := [akBottom, akRight]; FBtnCancel.Anchors := FBtnOk.Anchors; - FPanel.Width := GetParentForm(FPanel).Canvas.TextWidth(FCheckCurTS.Caption) + 2*FCheckCurTS.Left + 24; end; @@ -1330,36 +1345,37 @@ begin // Check relevant radio button FRadioNothing.Checked := DefaultType = cdtNothing; - FRadioText.Checked := DefaultType in [cdtText, cdtTextUpdateTS]; - FRadioNull.Checked := DefaultType in [cdtNull, cdtNullUpdateTS]; - FRadioCurTS.Checked := DefaultType in [cdtCurTS, cdtCurTSUpdateTS]; - FCheckCurTS.Checked := DefaultType in [cdtTextUpdateTS, cdtNullUpdateTS, cdtCurTSUpdateTS]; + FRadioText.Checked := DefaultType = cdtText; + FRadioNull.Checked := DefaultType = cdtNull; + FRadioExpression.Checked := DefaultType = cdtExpression; FRadioAutoInc.Checked := DefaultType = cdtAutoInc; - if FRadioText.Checked then - FCustomEdit.Text := DefaultText; + if FRadioText.Checked then begin + FTextEdit.Text := DefaultText; + end; + + if FRadioExpression.Checked then begin + FExpressionEdit.Text := DefaultText; + end; + + FOnUpdateEdit.Text := OnUpdateText; // Disable non working default options per data type - // But leave checked option enabled, regardless of if that is a valid default option or not - FRadioCurTS.Enabled := FRadioCurTS.Checked - or (FTableColumn.DataType.Index = dtTimestamp) - or ((FTableColumn.Connection.ServerVersionInt >= 50605) and (FTableColumn.DataType.Index = dtDateTime)); - FCheckCurTS.Enabled := FCheckCurTS.Checked or FRadioCurTS.Enabled; FRadioAutoInc.Enabled := FRadioAutoInc.Checked or (FTableColumn.DataType.Category = dtcInteger); // Provide items with a check mark for ENUM and SET columns if FTableColumn.DataType.Index in [dtEnum, dtSet] then begin - FCustomEdit.RightButton.Visible := True; + FTextEdit.RightButton.Visible := True; ValueList := FTableColumn.ValueList; - SelectedValues := Explode(',', FCustomEdit.Text); + SelectedValues := Explode(',', FTextEdit.Text); for i:=0 to ValueList.Count-1 do begin - Item := TMenuItem.Create(FCustomDropDown); + Item := TMenuItem.Create(FTextDropDown); Item.Caption := ValueList[i]; Item.RadioItem := FTableColumn.DataType.Index = dtEnum; Item.Checked := SelectedValues.IndexOf(Item.Caption) > -1; - Item.OnClick := CustomDropDownClick; - FCustomDropDown.Items.Add(Item); + Item.OnClick := EditDropDownClick; + FTextDropDown.Items.Add(Item); end; ValueList.Free; @@ -1398,9 +1414,9 @@ begin if Result then begin FPanel.Show; if FRadioNothing.Checked then FRadioNothing.SetFocus - else if FRadioText.Checked then FRadioText.SetFocus + else if FRadioText.Checked then FTextEdit.SetFocus else if FRadioNull.Checked then FRadioNull.SetFocus - else if FRadioCurTS.Checked then FRadioCurTS.SetFocus + else if FRadioExpression.Checked then FExpressionEdit.SetFocus else if FRadioAutoInc.Checked then FRadioAutoInc.SetFocus; end; end; @@ -1408,41 +1424,41 @@ end; function TColumnDefaultEditorLink.EndEdit: Boolean; stdcall; var - newText: String; - newDefaultType: TColumnDefaultType; + Col: PTableColumn; begin Result := not FStopping; if Result then begin FStopping := True; - if FRadioNothing.Checked then - newDefaultType := cdtNothing - else if FRadioText.Checked and FCheckCurTS.Checked then - newDefaultType := cdtTextUpdateTS - else if FRadioText.Checked then - newDefaultType := cdtText - else if FRadioNull.Checked and FCheckCurTS.Checked then - newDefaultType := cdtNullUpdateTS - else if FRadioNull.Checked then - newDefaultType := cdtNull - else if FRadioCurTS.Checked and FCheckCurTS.Checked then - newDefaultType := cdtCurTSUpdateTS - else if FRadioCurTS.Checked then - newDefaultType := cdtCurTS - else if FRadioAutoInc.Checked then - newDefaultType := cdtAutoInc - else - newDefaultType := cdtText; + Col := FTree.GetNodeData(FNode); - case newDefaultType of - cdtNothing: newText := ''; - cdtText, cdtTextUpdateTS: newText := FCustomEdit.Text; - cdtNull, cdtNullUpdateTS: newText := 'NULL'; - cdtCurTS, cdtCurTSUpdateTS: newText := 'CURRENT_TIMESTAMP'; - cdtAutoInc: newText := 'AUTO_INCREMENT'; + if FRadioNothing.Checked then + Col.DefaultType := cdtNothing + else if FRadioText.Checked then + Col.DefaultType := cdtText + else if FRadioNull.Checked then + Col.DefaultType := cdtNull + else if FRadioExpression.Checked then + Col.DefaultType := cdtExpression + else if FRadioAutoInc.Checked then + Col.DefaultType := cdtAutoInc + else + Col.DefaultType := cdtText; + + case Col.DefaultType of + cdtNothing: Col.DefaultText := ''; + cdtText: Col.DefaultText := FTextEdit.Text; + cdtNull: Col.DefaultText := 'NULL'; + cdtExpression: Col.DefaultText := FExpressionEdit.Text; + cdtAutoInc: Col.DefaultText := 'AUTO_INCREMENT'; end; - newText := IntToStr(Integer(newDefaultType)) + newText; - if newtext <> IntToStr(Integer(DefaultType)) + DefaultText then - FTree.Text[FNode, FColumn] := newtext; + + if FOnUpdateEdit.Text <> '' then + Col.OnUpdateType := cdtExpression + else + Col.OnUpdateType := cdtNothing; + Col.OnUpdateText := FOnUpdateEdit.Text; + + FTree.Text[FNode, FColumn] := Col.DefaultText; if FTree.CanFocus then FTree.SetFocus; end; @@ -1452,25 +1468,34 @@ end; procedure TColumnDefaultEditorLink.RadioClick(Sender: TObject); begin if not FRadioText.Checked then - FCustomEdit.Color := GetThemeColor(clBtnFace) + FTextEdit.Color := clBtnFace else begin - FCustomEdit.Color := GetThemeColor(clWindow); - if FCustomEdit.CanFocus then - FCustomEdit.SetFocus; + FTextEdit.Color := clWindow; + if FTextEdit.CanFocus then + FTextEdit.SetFocus; + end; + if not FRadioExpression.Checked then + FExpressionEdit.Color := clBtnFace + else begin + FExpressionEdit.Color := clWindow; + if FExpressionEdit.CanFocus then + FExpressionEdit.SetFocus; end; - FCheckCurTS.Enabled := not FRadioNothing.Checked; FModified := True; end; -procedure TColumnDefaultEditorLink.CustomEditChange(Sender: TObject); +procedure TColumnDefaultEditorLink.EditChange(Sender: TObject); begin - FRadioText.Checked := True; + if Sender = FTextEdit then + FRadioText.SetChecked(True) + else if Sender = FExpressionEdit then + FRadioExpression.SetChecked(True); FModified := True; end; -procedure TColumnDefaultEditorLink.CustomDropDownClick(Sender: TObject); +procedure TColumnDefaultEditorLink.EditDropDownClick(Sender: TObject); var Item: TMenuItem; NewValue: String; @@ -1485,7 +1510,7 @@ begin end; if not IsEmpty(NewValue) then Delete(NewValue, Length(NewValue), 1); - FCustomEdit.Text := NewValue; + FTextEdit.Text := NewValue; FModified := True; end; diff --git a/source/main.pas b/source/main.pas index 7634c916..a8b89bcd 100644 --- a/source/main.pas +++ b/source/main.pas @@ -7941,7 +7941,7 @@ begin end else if Column in [1, 2] then begin SessionVal := vt.Text[Node, 1]; GlobalVal := vt.Text[Node, 2]; - if IsNumeric(SessionVal) or IsNumeric(GlobalVal) then + if IsInt(SessionVal) or IsInt(GlobalVal) then dcat := dtcInteger else if (tmp > -1) and ((MySQLVariables[tmp].EnumValues <> '') or (Pos(UpperCase(SessionVal), 'ON,OFF,0,1,YES,NO')>0)) then dcat := dtcOther @@ -11484,7 +11484,7 @@ begin dtcInteger, dtcReal: Val := '0'; dtcText, dtcOther: begin Val := esc(Column.DefaultText); - if Column.DefaultType in [cdtNull, cdtNullUpdateTS] then + if Column.DefaultType in [cdtNull] then Val := esc('') else Val := esc(Column.DefaultText); diff --git a/source/table_editor.pas b/source/table_editor.pas index fc5bbeb7..99b3256b 100644 --- a/source/table_editor.pas +++ b/source/table_editor.pas @@ -6,7 +6,7 @@ uses Windows, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComCtrls, ToolWin, VirtualTrees, SynRegExpr, ActiveX, ExtCtrls, SynEdit, SynMemo, Menus, Clipbrd, Math, System.UITypes, - grideditlinks, mysql_structures, dbconnection, apphelpers, gnugettext; + grideditlinks, mysql_structures, dbconnection, apphelpers, gnugettext, StrUtils; type TFrame = TDBObjectEditor; @@ -1089,6 +1089,7 @@ var begin // Display column text Col := Sender.GetNodeData(Node); + CellText := ''; case Column of 0: CellText := IntToStr(Node.Index+1); 1: CellText := Col.Name; @@ -1097,14 +1098,19 @@ begin 4, 5, 6: CellText := ''; // Checkbox 7: begin case Col.DefaultType of - cdtNothing: CellText := _('No default'); - cdtText, cdtTextUpdateTS: CellText := Col.DefaultText; - cdtNull, cdtNullUpdateTS: CellText := 'NULL'; - cdtCurTS, cdtCurTSUpdateTS: CellText := 'CURRENT_TIMESTAMP'; - cdtAutoInc: CellText := 'AUTO_INCREMENT'; + cdtNothing: CellText := _('No default'); + cdtText: CellText := Col.Connection.EscapeString(Col.DefaultText); + cdtNull: CellText := 'NULL'; + cdtExpression: CellText := Col.DefaultText; + cdtAutoInc: CellText := 'AUTO_INCREMENT'; + end; + case Col.OnUpdateType of + // cdtNothing: leave clause away + // cdtText: not supported + // cdtNull: not supported + cdtExpression: CellText := CellText + ' ON UPDATE ' + Col.OnUpdateText; + // cdtAutoInc: invalid here end; - if Col.DefaultType in [cdtTextUpdateTS, cdtNullUpdateTS, cdtCurTSUpdateTS] then - CellText := CellText + ' ON UPDATE CURRENT_TIMESTAMP'; end; 8: CellText := Col.Comment; 9: begin @@ -1163,12 +1169,10 @@ begin 2: TextColor := DatatypeCategories[Col.DataType.Category].Color; 7: case Col.DefaultType of - cdtNothing, cdtNull, cdtNullUpdateTS: - TextColor := clGray; - cdtCurTS, cdtCurTSUpdateTS: - TextColor := DatatypeCategories[dtcTemporal].Color; - cdtAutoInc: - TextColor := DatatypeCategories[dtcInteger].Color; + cdtNothing, cdtNull: + TextColor := DatatypeCategories[Col.DataType.Category].NullColor; + else + TextColor := DatatypeCategories[Col.DataType.Category].Color; end; end; TargetCanvas.Font.Color := TextColor; @@ -1213,18 +1217,23 @@ begin Col.LengthSet := Col.DataType.DefLengthSet; // Auto-fix user selected default type which can be invalid now case Col.DataType.Category of - dtcInteger, dtcReal: begin - if Col.DefaultType in [cdtCurTS, cdtCurTSUpdateTS] then - Col.DefaultType := cdtNothing; - if Col.DefaultType = cdtTextUpdateTS then - Col.DefaultType := cdtText; - if Col.DefaultType = cdtNullUpdateTS then - Col.DefaultType := cdtNull; + dtcInteger: begin + Col.DefaultType := cdtExpression; + if Col.AllowNull then + Col.DefaultType := cdtNull + else + Col.DefaultText := IntToStr(MakeInt(Col.DefaultText)); + end; + dtcReal: begin + Col.DefaultType := cdtExpression; + if Col.AllowNull then + Col.DefaultType := cdtNull + else + Col.DefaultText := FloatToStr(MakeFloat(Col.DefaultText)); end; dtcText, dtcBinary, dtcSpatial, dtcOther: begin - if Col.DefaultType in [cdtCurTS, cdtCurTSUpdateTS, cdtAutoInc] then - Col.DefaultType := cdtNothing; - if Col.DefaultType = cdtNullUpdateTS then + Col.DefaultType := cdtText; + if Col.AllowNull then Col.DefaultType := cdtNull; end; dtcTemporal: begin @@ -1245,9 +1254,8 @@ begin end; // 4 + 5 are checkboxes - handled in OnClick 7: begin // Default value - Col.DefaultText := NewText; - Col.DefaultType := GetColumnDefaultType(Col.DefaultText); - if Col.DefaultType in [cdtNull, cdtNullUpdateTS] then + // DefaultText/Type and OnUpdateText/Type are set in TColumnDefaultEditorLink.EndEdit + if Col.DefaultType = cdtNull then Col.AllowNull := True; end; 8: Col.Comment := NewText; @@ -1318,7 +1326,7 @@ begin 5: begin Col.AllowNull := not Col.AllowNull; // Switch default value from NULL to Text if Allow Null is off - if (not Col.AllowNull) and (Col.DefaultType in [cdtNull, cdtNullUpdateTS]) then begin + if (not Col.AllowNull) and (Col.DefaultType = cdtNull) then begin Col.DefaultType := cdtNothing; Col.DefaultText := ''; end; @@ -1369,8 +1377,10 @@ begin end; 7: begin DefaultEditor := TColumnDefaultEditorLink.Create(VT); - DefaultEditor.DefaultText := Col.DefaultText; DefaultEditor.DefaultType := Col.DefaultType; + DefaultEditor.DefaultText := Col.DefaultText; + DefaultEditor.OnUpdateType := Col.OnUpdateType; + DefaultEditor.OnUpdateText := Col.OnUpdateText; EditLink := DefaultEditor; end; 11: begin // Virtuality pulldown