diff --git a/contrib/DS_Starter/93_DbRep.pm b/contrib/DS_Starter/93_DbRep.pm index 7199ee640..a78a3371d 100644 --- a/contrib/DS_Starter/93_DbRep.pm +++ b/contrib/DS_Starter/93_DbRep.pm @@ -1,5 +1,5 @@ ########################################################################################################## -# $Id: 93_DbRep.pm 18837 2019-03-09 20:13:59Z DS_Starter $ +# $Id: 93_DbRep.pm 18980 2019-03-20 20:55:44Z DS_Starter $ ########################################################################################################## # 93_DbRep.pm # @@ -45,6 +45,7 @@ use POSIX qw(strftime); use Time::HiRes qw(gettimeofday tv_interval); use Scalar::Util qw(looks_like_number); eval "use DBI;1" or my $DbRepMMDBI = "DBI"; +eval "use FHEM::Meta;1" or my $modMetaAbsent = 1; use DBI::Const::GetInfoType; use Blocking; use Color; # colorpicker Widget @@ -55,9 +56,12 @@ use IO::Uncompress::Gunzip qw(gunzip $GunzipError); # no if $] >= 5.018000, warnings => 'experimental'; no if $] >= 5.017011, warnings => 'experimental::smartmatch'; -# Versions History intern +# Version History intern our %DbRep_vNotesIntern = ( - "8.16.0" => "17.03.2019 include sortTopicNum from 99_Utils, allow PRAGMAS leading an SQLIte SQL-Statement ", + "8.17.2" => "28.03.2019 consideration of daylight saving time/leap year changed (func DbRep_corrRelTime) ", + "8.17.1" => "24.03.2019 edit Meta data, activate Meta.pm, prevent module from deactivation in case of unavailable Meta.pm ", + "8.17.0" => "20.03.2019 prepare for Meta.pm, new attribute \"sqlCmdVars\" ", + "8.16.0" => "17.03.2019 include sortTopicNum from 99_Utils, allow PRAGMAS leading an SQLIte SQL-Statement in sqlCmd, switch to DbRep_setVersionInfo ", "8.15.0" => "04.03.2019 readingsRename can rename readings of a given (optional) device ", "8.14.1" => "04.03.2019 Bugfix in deldoublets with SQLite, Forum: https://forum.fhem.de/index.php/topic,53584.msg914489.html#msg914489 ", "8.14.0" => "19.02.2019 delete Readings if !goodReadingName and featurelevel > 5.9 ", @@ -139,8 +143,11 @@ our %DbRep_vNotesIntern = ( "1.0.0" => "19.05.2016 Initial" ); -# Versions History extern: +# Version History extern: our %DbRep_vNotesExtern = ( + "8.17.0" => "20.03.2019 With new attribute \"sqlCmdVars\" you are able to set SQL session variables or SQLite PRAGMA every time ". + "before running a SQL-statement with sqlCmd command.", + "8.16.0" => "17.03.2019 allow SQLite PRAGMAS leading an SQLIte SQL-Statement in sqlCmd ", "8.15.0" => "04.03.2019 readingsRename can now rename readings of a given (optional) device instead of all found readings specified in command ", "8.13.0" => "11.02.2019 executeBeforeProc / executeAfterProc is now available for sqlCmd,sumValue, maxValue, minValue, diffValue, averageValue ", "8.11.0" => "24.01.2019 command exportToFile or attribute \"expimpfile\" accepts option \"MAXLINES=\" ", @@ -349,6 +356,7 @@ sub DbRep_Initialize($) { "showStatus ". "showTableInfo ". "sqlCmdHistoryLength:0,5,10,15,20,25,30,35,40,45,50 ". + "sqlCmdVars ". "sqlResultFormat:separated,mline,sline,table,json ". "sqlResultFieldSep:|,:,\/ ". "timeYearPeriod ". @@ -365,8 +373,10 @@ sub DbRep_Initialize($) { # $hash->{AttrRenameMap} = { "reading" => "readingFilter", # "device" => "deviceFilter", # }; - -return undef; + + eval { FHEM::Meta::InitMod( __FILE__, $hash ) }; # für Meta.pm (https://forum.fhem.de/index.php/topic,97589.0.html) + +return; } ################################################################################### @@ -389,16 +399,19 @@ sub DbRep_Define($@) { return "The specified DbLog-Device \"$a[2]\" doesn't exist."; } - $hash->{LASTCMD} = " "; - $hash->{ROLE} = AttrVal($name, "role", "Client"); - $hash->{MODEL} = $hash->{ROLE}; - $hash->{HELPER}{DBLOGDEVICE} = $a[2]; - $hash->{HELPER}{IDRETRIES} = 3; # Anzahl wie oft versucht wird initiale Daten zu holen - $hash->{VERSION} = (sortTopicNum("desc",keys %DbRep_vNotesIntern))[0]; - $hash->{NOTIFYDEV} = "global,".$name; # nur Events dieser Devices an DbRep_Notify weiterleiten - my $dbconn = $defs{$a[2]}{dbconn}; - $hash->{DATABASE} = (split(/;|=/, $dbconn))[1]; - $hash->{UTF8} = defined($defs{$a[2]}{UTF8})?$defs{$a[2]}{UTF8}:0; + $hash->{LASTCMD} = " "; + $hash->{ROLE} = AttrVal($name, "role", "Client"); + $hash->{MODEL} = $hash->{ROLE}; + $hash->{HELPER}{DBLOGDEVICE} = $a[2]; + $hash->{HELPER}{IDRETRIES} = 3; # Anzahl wie oft versucht wird initiale Daten zu holen + $hash->{HELPER}{MODMETAABSENT} = 1 if($modMetaAbsent); # Modul Meta.pm nicht vorhanden + $hash->{NOTIFYDEV} = "global,".$name; # nur Events dieser Devices an DbRep_Notify weiterleiten + my $dbconn = $defs{$a[2]}{dbconn}; + $hash->{DATABASE} = (split(/;|=/, $dbconn))[1]; + $hash->{UTF8} = defined($defs{$a[2]}{UTF8})?$defs{$a[2]}{UTF8}:0; + + # Versionsinformationen setzen + DbRep_setVersionInfo($hash); my ($err,$hl) = DbRep_getCmdFile($name."_sqlCmdList"); if(!$err) { @@ -5639,7 +5652,7 @@ sub sqlCmd_DoParse($) { my $dbpassword = $attr{"sec$dblogname"}{secret}; my $utf8 = defined($hash->{UTF8})?$hash->{UTF8}:0; my $srs = AttrVal($name, "sqlResultFieldSep", "|"); - my $err; + my ($err,@pms); # Background-Startzeit my $bst = [gettimeofday]; @@ -5652,49 +5665,70 @@ sub sqlCmd_DoParse($) { Log3 ($name, 2, "DbRep $name - $@"); return "$name|''|$opt|$cmd|''|''|$err"; } - + # only for this block because of warnings if details of readings are not set no warnings 'uninitialized'; - - my $sql = ($cmd =~ m/\;$/)?$cmd:$cmd.";"; - # split SQL-Parameter Statement falls mitgegeben - # z.B. SET @open:=NULL, @closed:=NULL; Select ... - my $set; - if($cmd =~ /^SET.*;/i) { - $cmd =~ m/^(SET.*?;)(.*)/i; - $set = $1; - $sql = $2; - } - - if($set) { - Log3($name, 4, "DbRep $name - Set SQL session variables: $set"); - eval {$dbh->do($set);}; # @\RB = Resetbit wenn neues Selektionsintervall beginnt - } - if ($@) { - $err = encode_base64($@,""); - Log3 ($name, 2, "DbRep $name - ERROR - $@"); - $dbh->disconnect; - return "$name|''|$opt|$set|''|''|$err"; - } + my $sql = ($cmd =~ m/\;$/)?$cmd:$cmd.";"; - # Abarbeitung aller Pragmas in einem SQLite Statement, SQL wird extrahiert - # Pragmas müssen im SQL vorangestellt sein - if($cmd =~ /^\s*PRAGMA.*;/i) { - my @pms = split(";",$cmd); + # Set Session Variablen "SET" oder PRAGMA aus Attribut "sqlCmdVars" + my $vars = AttrVal($name, "sqlCmdVars", ""); + if ($vars) { + @pms = split(";",$vars); foreach my $pm (@pms) { - if($pm !~ /PRAGMA/i) { - $sql = $pm; + if($pm !~ /PRAGMA|SET/i) { next; } - $pm = ltrim($pm); + $pm = ltrim($pm).";"; + Log3($name, 4, "DbRep $name - Set VARIABLE or PRAGMA: $pm"); + eval {$dbh->do($pm);}; + if ($@) { + $err = encode_base64($@,""); + Log3 ($name, 2, "DbRep $name - ERROR - $@"); + $dbh->disconnect; + return "$name|''|$opt|$sql|''|''|$err"; + } + } + } + + # Abarbeitung von Session Variablen vor einem SQL-Statement + # z.B. SET @open:=NULL, @closed:=NULL; Select ... + if($cmd =~ /^\s*SET.*;/i) { + @pms = split(";",$cmd); + foreach my $pm (@pms) { + if($pm !~ /SET/i) { + $sql = $pm.";"; + next; + } + $pm = ltrim($pm).";"; + Log3($name, 4, "DbRep $name - Set SQL session variable: $pm"); + eval {$dbh->do($pm);}; + if ($@) { + $err = encode_base64($@,""); + Log3 ($name, 2, "DbRep $name - ERROR - $@"); + $dbh->disconnect; + return "$name|''|$opt|$sql|''|''|$err"; + } + } + } + + # Abarbeitung aller Pragmas vor einem SQLite Statement, SQL wird extrahiert + # wenn Pragmas im SQL vorangestellt sind + if($cmd =~ /^\s*PRAGMA.*;/i) { + @pms = split(";",$cmd); + foreach my $pm (@pms) { + if($pm !~ /PRAGMA/i) { + $sql = $pm.";"; + next; + } + $pm = ltrim($pm).";"; Log3($name, 4, "DbRep $name - Exec PRAGMA Statement: $pm"); eval {$dbh->do($pm);}; if ($@) { $err = encode_base64($@,""); Log3 ($name, 2, "DbRep $name - ERROR - $@"); $dbh->disconnect; - return "$name|''|$opt|$set|''|''|$err"; + return "$name|''|$opt|$sql|''|''|$err"; } } } @@ -6397,7 +6431,7 @@ sub mysql_DoDumpClientSide($) { my $ead = AttrVal($name, "executeAfterProc", undef); my $mysql_commentstring = "-- "; my $character_set = "utf8"; - my $repver = $hash->{VERSION}; + my $repver = $hash->{HELPER}{VERSION}; my $sql_text = ''; my $sql_file = ''; my $dbpraefix = ""; @@ -8948,35 +8982,45 @@ sub DbRep_corrRelTime($$$) { # year als Jahre seit 1900 # $mon als 0..11 my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst); - my ($dsec,$dmin,$dhour,$dmday,$dmon,$dyear,$dwday,$dyday,$disdst); + my ($dsec,$dmin,$dhour,$dmday,$dmon,$dyear,$dwday,$dyday,$disdst,$fyear,$cyear); + (undef,undef,undef,undef,undef,$cyear,undef,undef,$isdst) = localtime(time); # aktuelles Jahr, Sommer/Winterzeit if($tdtn) { # timeDiffToNow - ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); # Startzeit Ableitung - ($dsec,$dmin,$dhour,$dmday,$dmon,$dyear,$dwday,$dyday,$disdst) = localtime(time-$tim); # Analyse Zieltimestamp timeDiffToNow + ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,undef) = localtime(time); # Istzeit + ($dsec,$dmin,$dhour,$dmday,$dmon,$dyear,$dwday,$dyday,$disdst) = localtime(time-$tim); # Istzeit abzgl. Differenzzeit = Selektionsbeginnzeit } else { # timeOlderThan - ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time-$tim); # Startzeit Ableitung - my $mints = $hash->{HELPER}{MINTS}?$hash->{HELPER}{MINTS}:"1970-01-01 01:00:00"; # Timestamp des 1. Datensatzes verwenden falls ermittelt + ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$disdst) = localtime(time-$tim); # Berechnung Selektionsendezeit + my $mints = $hash->{HELPER}{MINTS}?$hash->{HELPER}{MINTS}:"1970-01-01 01:00:00"; # Selektionsstartzeit my ($yyyy1, $mm1, $dd1, $hh1, $min1, $sec1) = ($mints =~ /(\d+)-(\d+)-(\d+) (\d+):(\d+):(\d+)/); my $tsend = timelocal($sec1, $min1, $hh1, $dd1, $mm1-1, $yyyy1-1900); - ($dsec,$dmin,$dhour,$dmday,$dmon,$dyear,$dwday,$dyday,$disdst) = localtime($tsend); # Analyse Zieltimestamp timeOlderThan + ($dsec,$dmin,$dhour,$dmday,$dmon,$dyear,$dwday,$dyday,undef) = localtime($tsend); # Timestamp Selektionsstartzeit } - $year += 1900; - $dyear += 1900; - my $k = $year - $dyear; - my $mg = ((int($mon)+1)+(($year-$dyear-1)*12)+(11-int($dmon)+1)); # Gesamtzahl der Monate des Bewertungszeitraumes + $year += 1900; # aktuelles Jahr + $dyear += 1900; # Startjahr der Selektion + $cyear += 1900; # aktuelles Jahr + if($tdtn) { + # timeDiffToNow + $fyear = $dyear; # Berechnungsjahr -> hier Selektionsbeginn + } else { + # timeOlderThan + $fyear = $year; # Berechnungsjahr -> hier Selektionsende + } + + my $k = $cyear - $fyear; # Anzahl Jahre + my $mg = ((int($mon)+1)+(($k-1)*12)+(11-int($dmon)+1)); # Gesamtzahl der Monate des Bewertungszeitraumes my $cly = 0; # Anzahl Schaltjahre innerhalb Beginn und Ende Auswertungszeitraum my $fly = 0; # erstes Schaltjahr nach Start my $lly = 0; # letzes Schaltjahr nach Start - while ($dyear+$k >= $dyear) { - my $ily = DbRep_IsLeapYear($name,$dyear+$k); + while ($fyear+$k >= $fyear) { + my $ily = DbRep_IsLeapYear($name,$fyear+$k); $cly++ if($ily); - $fly = $dyear+$k if($ily && !$fly); - $lly = $dyear+$k if($ily); + $fly = $fyear+$k if($ily && !$fly); + $lly = $fyear+$k if($ily); $k--; } - # Log3($name, 4, "DbRep $name - countleapyear: $cly firstleapyear: $fly lastleapyear: $lly totalmonth: $mg isdaylight:$isdst destdaylight:$disdst"); - if( ($fly <= $year && $mon > 1) && ($lly > $dyear || ($lly = $dyear && $dmon < 1)) ) { + + if( $mon > 1 && ($lly > $fyear || ($lly = $fyear && $dmon < 1)) ) { $tim += $cly*86400; # Log3($name, 4, "DbRep $name - leap year correction 1"); } else { @@ -8985,7 +9029,10 @@ sub DbRep_corrRelTime($$$) { } # Sommer/Winterzeitkorrektur + (undef,undef,undef,undef,undef,undef,undef,undef,$disdst) = localtime(time-$tim); $tim += ($disdst-$isdst)*3600 if($disdst != $isdst); + + Log3($name, 4, "DbRep $name - startMonth: $mon endMonth: $dmon lastleapyear: $lly baseYear: $fyear destdaylight:$disdst isdaylight:$isdst"); return $tim; } @@ -10093,6 +10140,41 @@ sub DbRep_sortVersion (@){ return @sorted; } +################################################################ +# Versionierungen des Moduls setzen +# Die Verwendung von Meta.pm und Packages wird berücksichtigt +################################################################ +sub DbRep_setVersionInfo($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + my $v = (sortTopicNum("desc",keys %DbRep_vNotesIntern))[0]; + my $type = $hash->{TYPE}; + $hash->{HELPER}{PACKAGE} = __PACKAGE__; + $hash->{HELPER}{VERSION} = $v; + + if($modules{$type}{META}{x_prereqs_src} && !$hash->{HELPER}{MODMETAABSENT}) { + # META-Daten sind vorhanden + $modules{$type}{META}{version} = "v".$v; # Version aus META.json überschreiben, Anzeige mit {Dumper $modules{SMAPortal}{META}} + if($modules{$type}{META}{x_version}) { # {x_version} ( nur gesetzt wenn $Id: 93_DbRep.pm 18980 2019-03-20 20:55:44Z DS_Starter $ im Kopf komplett! vorhanden ) + $modules{$type}{META}{x_version} =~ s/1.1.1/$v/g; + } else { + $modules{$type}{META}{x_version} = $v; + } + return $@ unless (FHEM::Meta::SetInternals($hash)); # FVERSION wird gesetzt ( nur gesetzt wenn $Id: 93_DbRep.pm 18980 2019-03-20 20:55:44Z DS_Starter $ im Kopf komplett! vorhanden ) + if(__PACKAGE__ eq "FHEM::$type" || __PACKAGE__ eq $type) { + # es wird mit Packages gearbeitet -> Perl übliche Modulversion setzen + # mit {->VERSION()} im FHEMWEB kann Modulversion abgefragt werden + use version 0.77; our $VERSION = FHEM::Meta::Get( $hash, 'version' ); + } + } else { + # herkömmliche Modulstruktur + $hash->{VERSION} = $v; + } + +return; +} + #################################################################################################### # blockierende DB-Abfrage # liefert Ergebnis sofort zurück, setzt keine Readings @@ -10335,8 +10417,8 @@ return; =pod =item helper -=item summary Reporting & Management content of DbLog-DB's. Content is depicted as readings -=item summary_DE Reporting & Management von DbLog-DB Content. Darstellung als Readings +=item summary Reporting and management of DbLog database content. +=item summary_DE Reporting und Management von DbLog-Datenbank Inhalten. =begin html @@ -11504,13 +11586,15 @@ return; will are listed.


-
  • sqlCmd - executes an arbitrary user specific command.
    +
  • sqlCmd - Execute an arbitrary user specific command.
    If the command contains a operation to delete data, the attribute "allowDeletion" has to be set for security reason.
    The statement doesn't consider limitations by attributes "device", "reading", "time.*" respectively "aggregation".
    - This command also accept the setting of SQL session variables like "SET @open:=NULL, - @closed:=NULL;".
    + This command also accept the setting of MySQL session variables like "SET @open:=NULL, + @closed:=NULL;" or the usage of SQLite PRAGMA before execution the SQL-Statement. + If the session variable or PRAGMA has to be set every time before executing a SQL statement, the + attribute "sqlCmdVars" can be set.
    If the attribute "timestamp_begin" respectively "timestamp_end" is assumed in the statement, it is possible to use placeholder "§timestamp_begin§" respectively "§timestamp_end§" on suitable place.

    @@ -11536,8 +11620,8 @@ return;
    - Here you can see an example of a more complex statement (MySQL) with setting SQL session - variables:

    + Here you can see examples of a more complex statement (MySQL) with setting SQL session + variables and the SQLite PRAGMA usage:


    @@ -11573,6 +11659,7 @@ return; sqlResultFormat : determines presentation style of command result sqlResultFieldSep : choice of a useful field separator for result sqlCmdHistoryLength : activates command history and length + sqlCmdVars : set SQL session variable or PRAGMA before execute the SQL statement
    @@ -12313,7 +12400,7 @@ sub bdump {
  • -
  • showTableInfo - limits the tablename which is selected by command "get <name> tableinfo". SQL-Wildcard +
  • showTableInfo - Determine the tablenames which are selected by command "get <name> tableinfo". SQL-Wildcard (%) can be used.

    @@ -12326,8 +12413,20 @@ sub bdump {
  • sqlCmdHistoryLength - - activates the command history of "sqlCmd" and determines the length of it

  • + - Activates the command history of "sqlCmd" and determines the length of it.
    + +
  • sqlCmdVars - Set a SQL session variable or a PRAGMA every time before executing a SQL + statement with "set <name> sqlCmd".

    + + +
    +

  • +
  • sqlResultFieldSep - determines the used field separator (default: "|") in the result of some sql-commands.

  • @@ -13885,7 +13984,11 @@ sub bdump { Bei der Ausführung dieses Kommandos werden keine Einschränkungen durch gesetzte Attribute "device", "reading", "time.*" bzw. "aggregation" berücksichtigt.
    Dieses Kommando akzeptiert ebenfalls das Setzen von SQL Session Variablen wie z.B. - "SET @open:=NULL, @closed:=NULL;".
    + "SET @open:=NULL, @closed:=NULL;" oder die Verwendung von SQLite PRAGMA vor der + Ausführung des SQL-Statements. + Soll die Session Variable oder das PRAGMA vor jeder Ausführung eines SQL Statements + gesetzt werden, kann dafür das Attribut "sqlCmdVars" + verwendet werden.
    Sollen die im Modul gesetzten Attribute "timestamp_begin" bzw. "timestamp_end" im Statement berücksichtigt werden, können die Platzhalter "§timestamp_begin§" bzw. "§timestamp_end§" dafür verwendet werden.

    @@ -13911,8 +14014,8 @@ sub bdump {
    - Nachfolgend noch ein Beispiel für ein komplexeres Statement (MySQL) unter Mitgabe von - SQL Session Variablen:

    + Nachfolgend Beispiele für ein komplexeres Statement (MySQL) unter Mitgabe von + SQL Session Variablen und die SQLite PRAGMA-Verwendung:


    @@ -13951,6 +14056,7 @@ sub bdump { sqlResultFormat : legt die Darstellung des Kommandoergebnis fest sqlResultFieldSep : Auswahl Feldtrenner im Ergebnis sqlCmdHistoryLength : Aktivierung Kommando-Historie und deren Umfang + sqlCmdVars : setzt SQL Session Variablen oder PRAGMA vor jeder Ausführung des SQL-Statements
    @@ -14708,15 +14814,27 @@ sub bdump { # Es werden nur Information der Tabellen "current" und "history" angezeigt

    - - -
  • sqlResultFieldSep - legt den verwendeten Feldseparator (default: "|") im Ergebnis des Kommandos - "set ... sqlCmd" fest.

  • sqlCmdHistoryLength - aktiviert die Kommandohistorie von "sqlCmd" und legt deren Länge fest

  • + + +
  • sqlCmdVars - Setzt eine SQL Session Variable oder PRAGMA vor jedem mit sqlCmd ausgeführten + SQL-Statement

    + + +
    +

  • + +
  • sqlResultFieldSep - legt den verwendeten Feldseparator (default: "|") im Ergebnis des Kommandos + "set ... sqlCmd" fest.

  • +
  • sqlResultFormat - legt die Formatierung des Ergebnisses des Kommandos "set <name> sqlCmd" fest. Mögliche Optionen sind:

    @@ -15077,4 +15195,66 @@ sub bdump { =end html_DE -=cu \ No newline at end of file + +=for :application/json;q=META.json 93_DbRep.pm +{ + "abstract": "Reporting and management of DbLog database content.", + "x_lang": { + "de": { + "abstract": "Reporting und Management von DbLog Datenbankinhalten." + } + }, + "keywords": [ + "dblog", + "database", + "reporting", + "logging", + "analyze" + ], + "version": "v1.1.1", + "release_status": "stable", + "author": [ + "Heiko Maaz " + ], + "x_fhem_maintainer": [ + "DS_Starter" + ], + "x_fhem_maintainer_github": [ + "nasseeder1" + ], + "prereqs": { + "runtime": { + "requires": { + "FHEM": 5.00918799, + "perl": 5.014, + "POSIX": 0, + "Time::HiRes": 0, + "Scalar::Util": 0, + "DBI": 0, + "DBI::Const::GetInfoType": 0, + "Blocking": 0, + "Color": 0, + "Time::Local": 0, + "Encode": 0 + }, + "recommends": { + "Net::FTPSSL": 0, + "Net::FTP": 0, + "IO::Compress::Gzip": 0, + "IO::Uncompress::Gunzip": 0, + "FHEM::Meta": 0 + }, + "suggests": { + } + } + }, + "resources": { + "x_wiki": { + "web": "https://wiki.fhem.de/wiki/DbRep_-_Reporting_und_Management_von_DbLog-Datenbankinhalten", + "title": "DbRep - Reporting und Management von DbLog-Datenbankinhalten" + } + } +} +=end :application/json;q=META.json + +=cut \ No newline at end of file