diff --git a/fhem/CHANGED b/fhem/CHANGED index eb7014d50..dde762ac2 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - change: 93_DbRep: new design of sqlCmdHistory, minor fixes - feature: 14_SD_WS07.pm protocol definition 7.1 for Mebus HQ7312-2 (#1050) - feature: 14_SD_WS.pm: diff --git a/fhem/FHEM/93_DbRep.pm b/fhem/FHEM/93_DbRep.pm index c019b00db..975bebbc2 100644 --- a/fhem/FHEM/93_DbRep.pm +++ b/fhem/FHEM/93_DbRep.pm @@ -57,6 +57,8 @@ no if $] >= 5.017011, warnings => 'experimental::smartmatch'; # Version History intern my %DbRep_vNotesIntern = ( + "8.47.0" => "17.01.2022 new design of sqlCmdHistory, minor fixes ", + "8.46.13" => "12.01.2022 more code refacturing, minor fixes ", "8.46.12" => "10.01.2022 more code refacturing, minor fixes, change usage of placeholder §device§, §reading§ in sqlCmd ", "8.46.11" => "03.01.2022 more code refacturing, minor fixes ", "8.46.10" => "02.01.2022 more code refacturing, minor fixes ", @@ -64,7 +66,7 @@ my %DbRep_vNotesIntern = ( "8.46.8" => "30.12.2021 some code refacturing and minor bug fixing ", "8.46.7" => "27.12.2021 some code improvements, insert accept a multiline string ", "8.46.6" => "26.12.2021 sub DbRep_sqlCmd uses credentials dependend of attr useAdminCredentials ". - "fix warnings in sub export/import file, fix error of expfile_DoParse filedefinition ". + "fix warnings in sub export/import file, fix error of DbRep_expfile filedefinition ". "change retrieve minTimestamp forum:#124987", "8.46.5" => "19.12.2021 some code improvements ", "8.46.4" => "14.12.2021 fix tableCurrentPurge ", @@ -77,7 +79,7 @@ my %DbRep_vNotesIntern = ( "8.44.0" => "21.11.2021 new attr numDecimalPlaces ", "8.43.1" => "02.11.2021 fix SQL statement if devspec can't be resolved, Forum:https://forum.fhem.de/index.php/topic,53584.msg1184155.html#msg1184155 ", "8.43.0" => "22.09.2021 consider attr device, reading in sqlCmd, remove length limit of attr device, reading ", - "8.42.9" => "05.09.2021 minor fixes, change SQL for SQLite in deldoublets_DoParse ", + "8.42.9" => "05.09.2021 minor fixes, change SQL for SQLite in DbRep_deldoublets ", "8.42.8" => "17.07.2021 more log data verbose 5, delete whitespaces in sub getInitData ", "8.42.7" => "27.02.2021 fix attribute sqlCmdVars is not working in sqlCmdBlocking Forum: /topic,53584.msg1135528.html#msg1135528", "8.42.6" => "25.02.2021 fix commandref ", @@ -224,7 +226,7 @@ my %DbRep_vNotesExtern = ( "5.3.1" => "28.06.2017 vacuum for SQLite added, readings enhanced for optimizeTables / vacuum, commandref revised ", "5.3.0" => "26.06.2017 change of DbRep_mysqlOptimizeTables, new command optimizeTables ", "5.0.6" => "13.06.2017 add Aria engine to DbRep_mysqlOptimizeTables ", - "5.0.3" => "07.06.2017 mysql_DoDumpServerSide added ", + "5.0.3" => "07.06.2017 DbRep_mysql_DumpServerSide added ", "5.0.1" => "05.06.2017 dependencies between dumpMemlimit and dumpSpeed created, enhanced verbose 5 logging ", "5.0.0" => "04.06.2017 MySQL Dump nonblocking added ", "4.16.1" => "22.05.2017 encode json without JSON module, requires at least fhem.pl 14348 2017-05-22 20:25:06Z ", @@ -405,7 +407,7 @@ sub DbRep_Initialize { "showVariables ". "showStatus ". "showTableInfo ". - "sqlCmdHistoryLength:0,5,10,15,20,25,30,35,40,45,50 ". + "sqlCmdHistoryLength:slider,0,1,200 ". "sqlCmdVars ". "sqlResultFormat:separated,mline,sline,table,json ". "sqlResultFieldSep:|,:,\/ ". @@ -471,14 +473,8 @@ sub DbRep_Define { $hash->{DATABASE} = (split(/;|=/, $dbconn))[1]; $hash->{UTF8} = defined($defs{$a[2]}{UTF8}) ? $defs{$a[2]}{UTF8} : 0; # wird in DbRep_getInitData aus DB abgefragt und neu gesetzt - # Versionsinformationen setzen - DbRep_setVersionInfo($hash); - - my ($err,$hl) = DbRep_getCmdFile($name."_sqlCmdList"); - if(!$err) { - $hash->{HELPER}{SQLHIST} = $hl; - Log3 ($name, 4, "DbRep $name - history sql commandlist read from file ".$attr{global}{modpath}."/FHEM/FhemUtils/cacheDbRep"); - } + DbRep_setVersionInfo ($hash); # Versionsinformationen setzen + DbRep_initSQLcmdCache ($name); # SQL Kommando Cache initialisieren Log3 ($name, 4, "DbRep $name - initialized"); ReadingsSingleUpdateValue ($hash, 'state', 'initialized', 1); @@ -532,8 +528,11 @@ sub DbRep_Set { my $cj = @bkps ? join(",",reverse(sort @bkps)) : " "; # Drop-Down Liste bisherige Befehle in "sqlCmd" erstellen - my $hl; - $hl = $hash->{HELPER}{SQLHIST}.",___purge_historylist___" if($hash->{HELPER}{SQLHIST}); + my (undef, $hl) = DbRep_listSQLcmdCache ($name); + if ($hl) { + $hl .= "___purge_sqlhistory___"; + $hl .= ",___list_sqlhistory___"; + } my $setlist = "Unknown argument $opt, choose one of ". "eraseReadings:noArg ". @@ -555,7 +554,7 @@ sub DbRep_Set { (($hash->{ROLE} ne "Agent" && $dbmodel =~ /MYSQL/) ? "adminCredentials " : ""). (($hash->{ROLE} ne "Agent") ? "insert ":""). (($hash->{ROLE} ne "Agent") ? "reduceLog ":""). - (($hash->{ROLE} ne "Agent") ? "sqlCmd:textField-long ":""). + (($hash->{ROLE} ne "Agent") ? "sqlCmd:textField-long ": ""). (($hash->{ROLE} ne "Agent" && $hl) ? "sqlCmdHistory:".$hl." " : ""). (($hash->{ROLE} ne "Agent") ? "sqlSpecial:50mostFreqLogsLast2days,allDevCount,allDevReadCount,recentReadingsOfDevice".(($dbmodel eq "MYSQL")?",readingsDifferenceByTimeDelta":"")." " : ""). (($hash->{ROLE} ne "Agent") ? "syncStandby " : ""). @@ -938,13 +937,80 @@ sub DbRep_Set { my $sqlcmd; if($opt eq "sqlSpecial") { + my ($tq,$gcl); + + if($prop eq "50mostFreqLogsLast2days") { + $prop = "select Device, reading, count(0) AS `countA` from history where ( TIMESTAMP > (now() - interval 2 day)) group by DEVICE, READING order by countA desc, DEVICE limit 50;" if($dbmodel =~ /MYSQL/); + $prop = "select Device, reading, count(0) AS `countA` from history where ( TIMESTAMP > ('now' - '2 days')) group by DEVICE, READING order by countA desc, DEVICE limit 50;" if($dbmodel =~ /SQLITE/); + $prop = "select Device, reading, count(0) AS countA from history where ( TIMESTAMP > (NOW() - INTERVAL '2' DAY)) group by DEVICE, READING order by countA desc, DEVICE limit 50;" if($dbmodel =~ /POSTGRESQL/); + } + elsif ($prop eq "allDevReadCount") { + $prop = "select device, reading, count(*) from history group by DEVICE, READING;"; + } + elsif ($prop eq "allDevCount") { + $prop = "select device, count(*) from history group by DEVICE;"; + } + elsif ($prop eq "recentReadingsOfDevice") { + if($dbmodel =~ /MYSQL/) {$tq = "NOW() - INTERVAL 1 DAY"; $gcl = "READING"}; + if($dbmodel =~ /SQLITE/) {$tq = "datetime('now','-1 day')"; $gcl = "READING"}; + if($dbmodel =~ /POSTGRESQL/) {$tq = "CURRENT_TIMESTAMP - INTERVAL '1 day'"; $gcl = "READING,DEVICE"}; + + $prop = "SELECT t1.TIMESTAMP,t1.DEVICE,t1.READING,t1.VALUE + FROM history t1 + INNER JOIN + (select max(TIMESTAMP) AS TIMESTAMP,DEVICE,READING + from history where §device§ AND TIMESTAMP > ".$tq." group by ".$gcl.") x + ON x.TIMESTAMP = t1.TIMESTAMP AND + x.DEVICE = t1.DEVICE AND + x.READING = t1.READING;"; + } + elsif ($prop eq "readingsDifferenceByTimeDelta") { + $prop = "SET \@diff=0; + SET \@delta=NULL; + SELECT t1.TIMESTAMP,t1.READING,t1.VALUE,t1.DIFF,t1.TIME_DELTA + FROM (SELECT TIMESTAMP,READING,VALUE, + cast((VALUE-\@diff) AS DECIMAL(12,4)) AS DIFF, + \@diff:=VALUE AS curr_V, + TIMESTAMPDIFF(MINUTE,\@delta,TIMESTAMP) AS TIME_DELTA, + \@delta:=TIMESTAMP AS curr_T + FROM history + WHERE §device§ AND + §reading§ AND + TIMESTAMP >= §timestamp_begin§ AND + TIMESTAMP <= §timestamp_end§ + ORDER BY TIMESTAMP + ) t1;"; + } + + $data{DbRep}{$name}{sqlcache}{temp} = $prop; # SQL incl. Formatierung zwischenspeichern + + my @cmd = split(/\s+/, $prop); + $prop = join " ", @cmd; $sqlcmd = $prop; } if($opt eq "sqlCmd") { my @cmd = @a; shift @cmd; shift @cmd; - $sqlcmd = join(" ", @cmd); + + $sqlcmd = join(" ", @cmd); + $sqlcmd = DbRep_trim ($sqlcmd); + + if ($sqlcmd =~ /^ckey:/ix) { + my $key = (split ":", $sqlcmd)[1]; + + if (exists $data{DbRep}{$name}{sqlcache}{cmd}{$key}) { + $sqlcmd = $data{DbRep}{$name}{sqlcache}{cmd}{$key}; + } + else { + return qq{SQL statement with key "$key" doesn't exists in history}; + } + } + + $sqlcmd .= ";" if ($sqlcmd !~ m/\;$/x); + + $data{DbRep}{$name}{sqlcache}{temp} = $sqlcmd; # SQL incl. Formatierung zwischenspeichern + @cmd = split(/\s/, $sqlcmd); $sqlcmd = join(" ", @cmd); # $sqlcmd =~ tr/ A-Za-z0-9!"#$§%&'()*+,-.\/:;<=>?@[\\]^_`{|}~äöüÄÖÜ߀/ /cs; # V8.36.0 20.03.2020 @@ -958,11 +1024,15 @@ sub DbRep_Set { $prop =~ s/(\x20)*\xbc/,/g; # Forum: https://forum.fhem.de/index.php/topic,103908.0.html $sqlcmd = $prop; - if($sqlcmd eq "___purge_historylist___") { - delete($hash->{HELPER}{SQLHIST}); - DbRep_setCmdFile($name."_sqlCmdList","",$hash); # Löschen der sql History Liste im DbRep-Keyfile + if($sqlcmd eq "___purge_sqlhistory___") { + DbRep_deleteSQLcmdCache ($name); return "SQL command historylist of $name deleted."; } + + if($sqlcmd eq "___list_sqlhistory___") { + my ($cache) = DbRep_listSQLcmdCache ($name); + return $cache; + } } $hash->{LASTCMD} = $sqlcmd ? "$opt $sqlcmd" : "$opt"; @@ -973,7 +1043,7 @@ sub DbRep_Set { DbRep_beforeproc ($hash, "sqlCmd"); DbRep_Main ($hash, $opt, $sqlcmd); - } + } elsif ($opt =~ /changeValue/) { shift @a; shift @a; @@ -1013,11 +1083,11 @@ sub DbRep_Set { if(!exists($defs{$prop}) || $defs{$prop}->{TYPE} ne "DbLog") { return "The device \"$prop\" doesn't exist or is not a DbLog-device. "; } - $hash->{LASTCMD} = $prop?"$opt $prop":"$opt"; + $hash->{LASTCMD} = $prop ? "$opt $prop" : "$opt"; DbRep_beforeproc ($hash, "syncStandby"); - DbRep_Main ($hash,$opt,$prop); - } + DbRep_Main ($hash, $opt, $prop); + } else { return "$setlist"; } @@ -1046,12 +1116,12 @@ sub DbRep_Get { "blockinginfo:noArg ". "minTimestamp:noArg ". "initData:noArg ". - (($dbmodel =~ /MYSQL/)?"storedCredentials:noArg ":""). "sqlCmdBlocking:textField-long ". - (($dbmodel eq "MYSQL")?"dbstatus:noArg ":""). - (($dbmodel eq "MYSQL")?"tableinfo:noArg ":""). - (($dbmodel eq "MYSQL")?"procinfo:noArg ":""). - (($dbmodel eq "MYSQL")?"dbvars:noArg ":""). + (($dbmodel =~ /MYSQL/) ? "storedCredentials:noArg " : ""). + (($dbmodel eq "MYSQL") ? "dbstatus:noArg " : ""). + (($dbmodel eq "MYSQL") ? "tableinfo:noArg " : ""). + (($dbmodel eq "MYSQL") ? "procinfo:noArg " : ""). + (($dbmodel eq "MYSQL") ? "dbvars:noArg " : ""). "versionNotes " ; @@ -1059,40 +1129,36 @@ sub DbRep_Get { if ($dbloghash->{HELPER}{REOPEN_RUNS} && $opt !~ /\?|procinfo|blockinginfo/) { my $ro = $dbloghash->{HELPER}{REOPEN_RUNS_UNTIL}; + Log3 ($name, 3, "DbRep $name - connection $dblogdevice to db $dbname is closed until $ro - $opt postponed"); + ReadingsSingleUpdateValue ($hash, "state", "connection $dblogdevice to $dbname is closed until $ro - $opt postponed", 1); + return; } if ($opt =~ /dbvars|dbstatus|tableinfo|procinfo/) { - return "Dump is running - try again later !" if($hash->{HELPER}{RUNNING_BACKUP_CLIENT}); - $hash->{LASTCMD} = $prop?"$opt $prop":"$opt"; - return "The operation \"$opt\" isn't available with database type $dbmodel" if ($dbmodel ne 'MYSQL'); - ReadingsSingleUpdateValue ($hash, "state", "running", 1); - DbRep_delread($hash); # Readings löschen die nicht in der Ausnahmeliste (Attr readingPreventFromDel) stehen - $hash->{HELPER}{RUNNING_PID} = BlockingCall("dbmeta_DoParse", "$name|$opt", "dbmeta_ParseDone", $to, "DbRep_ParseAborted", $hash); - if($hash->{HELPER}{RUNNING_PID}) { - $hash->{HELPER}{RUNNING_PID}{loglevel} = 5; # Forum #77057 - Log3 ($name, 5, qq{DbRep $name - start BlockingCall with PID "$hash->{HELPER}{RUNNING_PID}{pid}"}); - } + return "Dump is running - try again later !" if($hash->{HELPER}{RUNNING_BACKUP_CLIENT}); + return "The operation \"$opt\" isn't available with database type $dbmodel" if($dbmodel ne 'MYSQL'); + + $hash->{LASTCMD} = $prop ? "$opt $prop" : "$opt"; + + DbRep_Main ($hash, $opt, $prop); } elsif ($opt eq "svrinfo") { return "Dump is running - try again later !" if($hash->{HELPER}{RUNNING_BACKUP_CLIENT}); - $hash->{LASTCMD} = $prop?"$opt $prop":"$opt"; - DbRep_delread($hash); - ReadingsSingleUpdateValue ($hash, "state", "running", 1); - $hash->{HELPER}{RUNNING_PID} = BlockingCall("dbmeta_DoParse", "$name|$opt", "dbmeta_ParseDone", $to, "DbRep_ParseAborted", $hash); - if($hash->{HELPER}{RUNNING_PID}) { - $hash->{HELPER}{RUNNING_PID}{loglevel} = 5; # Forum #77057 - Log3 ($name, 5, qq{DbRep $name - start BlockingCall with PID "$hash->{HELPER}{RUNNING_PID}{pid}"}); - } + + $hash->{LASTCMD} = $prop ? "$opt $prop" : "$opt"; + + DbRep_Main ($hash, $opt, $prop); } elsif ($opt eq "blockinginfo") { return "Dump is running - try again later !" if($hash->{HELPER}{RUNNING_BACKUP_CLIENT}); + $hash->{LASTCMD} = $prop ? "$opt $prop" : "$opt"; - DbRep_delread($hash); + DbRep_delread ($hash); ReadingsSingleUpdateValue ($hash, "state", "running", 1); - DbRep_getblockinginfo($hash); + DbRep_getblockinginfo ($hash); } elsif ($opt eq "minTimestamp" || $opt eq "initData") { return "Dump is running - try again later !" if($hash->{HELPER}{RUNNING_BACKUP_CLIENT}); @@ -1128,12 +1194,11 @@ sub DbRep_Get { return DbRep_sqlCmdBlocking($name,$sqlcmd); } - elsif ($opt eq "storedCredentials") { - # Credentials abrufen + elsif ($opt eq "storedCredentials") { # Credentials abrufen my $atxt; - my $username = $defs{$defs{$name}->{HELPER}{DBLOGDEVICE}}->{dbuser}; - my $dblogname = $defs{$defs{$name}->{HELPER}{DBLOGDEVICE}}->{NAME}; - my $password = $attr{"sec$dblogname"}{secret}; + my $username = $defs{$defs{$name}->{HELPER}{DBLOGDEVICE}}->{dbuser}; + my $dblogname = $defs{$defs{$name}->{HELPER}{DBLOGDEVICE}}->{NAME}; + my $password = $attr{"sec$dblogname"}{secret}; my ($success,$admusername,$admpassword) = DbRep_getcredentials($hash,"adminCredentials"); if($success) { @@ -1172,32 +1237,40 @@ sub DbRep_Get { $ret .= "
$key | $val0 | " ); $ret .= "