Ferreteria/v0.6/clade/Sys/Data/Engine/Oper/MyMar

From Woozle Writes Code
< Ferreteria‎ | v0.6‎ | clade‎ | Sys‎ | Data‎ | Engine‎ | Oper
Revision as of 20:40, 2 December 2025 by Woozle (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search
clade: Sys\Data\Engine\Oper\MyMar
Clade Family
Oper MyMar
Clade Aliases
Alias Clade
ActionIface Sys\Events\ItWent
Base* [ca,i] Sys\Data\Engine\Oper
BkupModeEnum Sys\Data\Engine\aux\ActionRq\Admin\aux\FileFormat
CommOp* [c,i] Sys\Data\Engine\aux\msgs\Maria\ItWent
DataReqIface Sys\Data\Engine\aux\CommRq
MemBufferClass IO\Aspect\Connx\Buffer\InMem
PlayerIface Data\Base\Result\Player
QObj* [c,i] Data\Mem\QVar\Obj
RecvBuffClass IO\Aspect\Connx\Buffer\File\Writer
SchemaClass Sys\Data\Engine\schema\Ops\MyMar
Schemas* [c,i] Sys\Data\aux\list\Schemas
ScribeClass Sys\Data\Xfer\Scribe\MySQL
Subpages

About

  • Purpose: class for ops that are done the same/identically in MariaDB and MySQL (and any other compatible engines that turn up).

History

  • 2025-05-30 started
  • 2025-06-01 moved all of xMaria up here, though some of it may be Maria-tuned (suboptimal for MySQL).
    • This stuff can be fine-tuned later.
  • 2025-06-10 adding EngineOpen() / EngineShut()

Functions

Code

interface iMyMar extends BaseIface {
    // LIFECYCLE
    function EngineOpen(string $sAddr, string $sPass, ?string $sSchema, ?int $nPort) : ActionIface;
    function EngineShut() : ActionIface;
    // INFO
    function SchemaExists(string $sName) : bool;    // 2025-05-30 Eventually, will want a base-class for all multi-schema engines.
}
abstract class caMyMar extends BaseClass implements iMyMar {

    // ++ CONFIG ++ //

    protected function CommOpClass() : string { return CommOpClass::class; } // specialize later
    protected function SchemaClass() : string { return SchemaClass::class; }
    protected function ScribeClass() : string { return ScribeClass::class; }

    // -- CONFIG -- //
    // ++ SETTINGS ++ //

    private $eFmt = BkupModeEnum::USE_SQL;  // DEFAULT
    public function DataFormat(?BkupModeEnum $eMode=NULL) : BkupModeEnum { return is_object($eMode) ? ($this->eFmt = $eMode) : $this->eFmt; }

    // -- SETTINGS -- //
    // ++ LIFECYCLE ++ //

    public function EngineOpen(string $sAddr, string $sPass, ?string $sSchema, ?int $nPort) : ActionIface {
        #$oAct = new ($this->ActionClass());  // result object
        $oAct = $this->NewAction();

        $onDB = new mysqli($sAddr,$sUser,$sPass,$sSchema,$nPort); // open the DB connection
        if (is_object($onDB)) {
            $oAct->SetOkay(TRUE);
            $this->QNative()->SetIt($onDB);
        } else {
            // 2022-01-26 probably want a method to do all this
            $oAct->SetOkay(FALSE);
            $oAct->SetNumber($this->ErrorNumber());
            $oAct->AddMsgString($this->ErrorMessage());
        }
        return $oAct;
    }
    public function EngineShut() : ActionIface {
        // close endpoint-to-engine connection
        $ok = $this->QNative()->GetIt()->close();
        // close code-to-endpoint connection
        #$oAct = new ($this->ActionClass());
        $oAct = $this->NewAction();
        $oAct->SetOkay($ok);
        return $oAct;
    }



    // -- LIFECYCLE -- //
    // ++ ACTION: UI requests ++ //

    public function DoDataRequest(DataReqIface $oReq) : PlayerIface {
        $this->Open();
        $sql = $oReq->SQL();

        #echo "SQL: $sql".CRLF;
        $onRes = $this->QNative()->GetIt()->query($sql);
        $oReq->Action()->SetOkay(is_object($onRes));

        $scScribe = $this->ScribeClass();
#        echo $oReq->ReflectThis()->Report();
        $oScribe = $scScribe::FromRequestAndNative($oReq,$onRes);
        $oPlay = $oReq->MakePlayer($oScribe);
        $this->Shut();
        return $oPlay;
    }

    // ++ ACTION: Schema info ++ //

    private $oaSchemas = NULL;
    public function SchemaList() : SchemasIface { return $this->oaSchemas ?? ($this->oaSchemas = $this->NewSchemaList()); }
    public function SchemaExists(string $sName) : bool { return $this->SchemaList()->HasIt($sName); }
    protected function NewSchemaList() : SchemasIface {
        #$this->AmHere("GENERATING SCHEMA LIST");
        $oConn = $this->OConn(); // get (remote or local) connection object

        $sqCmd = '"SHOW SCHEMAS;"';

        #$this->AmHere("DB COMMAND: $sqCmd");

        $oBuff = new MemBufferClass;
        #echo $oBuff->ReflectThis()->Report();
        $oBuff->Open();

        $oPlug = $oConn->OSock()->OPlug();

        $oPlug->Open();
        $oActConn = $oConn->DoCommand($sqCmd,$oBuff,new ($this->CommOpClass()));
        $oActConn->HandleResults();  // deal with standard errors/warnings
        $oPlug->Shut();

        $oActApp = $this->CommOpStatus();
        $oScrn = self::Screen();

        $oaSchemas = SchemasClass::AsNew();

        if ($oBuff->IsMore()) {
            #$this->AmHere('GOT SCHEMA LIST');
            $sNames = $oBuff->RemoveBytes();
            $arNames = explode("\n",trim($sNames)); // we might want to ensure that CRLF, CR, and LF all work as EOL.
            #$this->AmHere(' - ELEMENTS: '.count($arNames));

            $sFirst = array_shift($arNames);
            if ($sFirst == "Database") {
                $oActApp->SetOkay(TRUE);
                foreach ($arNames as $sName) {
                    $oSchema = $this->GetSchema($sName);
                    $oaSchemas->AddSchema($oSchema);
                    #$this->AmHere(" -- SCHEMA: [$sName]");
                }
            } else {
                $oActApp->SetOkay(FALSE);
                // "Database" is the standard header for list of databases (schemas).
                // If we don't see this, then something isn't right.
                $sMsg = $oScrn->ErrorIt('Problem').": Something's not right with the db list. Here's what we got:".CRLF.$sList;
                $oActApp->AddMsgString($sMsg);
            }
        } else {
            $sMsg = self::Screen()->ErrorIt('Problem').': the connection does not seem to be working.';
            $oActApp->SetOkay(FALSE);
            $oActApp->AddMsgString($sMsg);
        }

        $oConn->Shut();

        return $oaSchemas;
    }

    // TODO: MySQL/Maria -specific versions of this
    // NOTE: This and Engine\Schema\MyMar::DoExport() are both used..
    public function DoExport(string $sSchema, RecvBuffClass $oInBuff) : CommOpIface {
        $oConnDB = $this->OConn();        // DB connection
        $oSockDB = $oConnDB->OSock();
        #echo $oSockDB->ReflectThis()->Report();
        $oConnSh = $oSockDB->OPlug();
        #$oSockSh = $oConnSh->OSock();
        #$oConn3 = $oSockSh->OPlug();

        #echo 'oConn3 is: '.get_class($oConn3).CRLF; die();
        $sCred = $oConnDB->CredsForCmd(); // DB connection string from DB-local shell

        $sCmd = "mysqldump $sCred --quick $sSchema"; // "--quick" does line-at-a-time rather than buffering entire table
        // should we also try "--single-transaction"? "To dump large tables, you should combine the --single-transaction option with --quick."

        // Send dump command directly to shell, not to DB engine:
        #echo $$oConnSh->ReflectThis()->Report(); die();
        $oAct = $oConnSh->DoCommand($sCmd,$oInBuff,new CommOpClass);
        return $oAct;
    }

    // ++ ACTION: UI request callbacks ++ //

    // SEE DOCS
    public function DoTestAccess() {
        $ok = $this->TestUserAccess();
        if (!$ok) {
            echo "(Since that didn't work, let's try unconfigured root.)".CRLF;
            $ok = $this->TestRootAccess();
        }
    }
    // SEE DOCS
    public function DoUserSetup() {
        $oScrn = self::Screen();

        // 2025-03-30 has worked at least once now
        #echo $oScrn->YellowIt('Warning').': This functionality has not been tested to the point of success yet. It may not work.'.CRLF;

        $ok = $this->TestRootAccess();
        if ($ok) {
            echo $oScrn->GreenIt('Ok').': we seem to have unconfigured root access. Proceeding...'.CRLF;
        } else {
            echo $oScrn->ErrorIt('Error').': This DB engine is configured, and can only be accessed with credentials.'.CRLF;
            exit;
        }

        $oCreds = $this->Creds();
        $sDUser = $oCreds->QSUser()->GetIt();
        $sDPass = $oCreds->QSPass()->GetIt();
        $sDHost = $oCreds->QSHost()->GetIt();

        // IN-SHELL COMMAND: sudo mysql "CREATE USER '<USERNAME>'@<HOST> IDENTIFIED BY '<PASSWORD>'"
        $q = '"';
        $sql = $q."ALTER USER '$sDUser'@$sDHost IDENTIFIED BY '$sDPass';$q";
        $sCmd = "echo $sql | sudo mysql";
        // DEBUGGING ONLY (shows password)
        #echo $oScrn->BoldIt('Command').': '.$oScrn->BlueIt($sCmd).CRLF;

        $oConnx = $this->Connx(); // shell (outside) connection details

        echo "Adding initial user '$sDUser' on db server '$sDHost'...".CRLF;
        // DEBUGGING ONLY
        #echo "INSIDE COMMAND: $sCmd".CRLF; die();
        #echo $oConnx->ReflectThis()->Report(); die();

        $oBuff = new MemBufferClass;
        echo 'Running the SQL to create the first user...'.CRLF;
        $oAct = $oConnx->DoCommand($sCmd,$oBuff);

        $ok = $oAct->GetOkay();

        if (!$ok) {
            // See if we can handle this condition...
            $sMsg = $oAct->GetMessage();
            if (str_contains($sMsg,'server is running with the --skip-grant-tables option')) {
                // NOTE: It may be that what actually needs to happen is just "FLUSH PRIVILEGES;" and try again.
                echo 'Apparently the server is in permissionless mode, so using alternative SQL.'.CRLF;
                // SET PASSWORD FOR 'root'@'localhost' = PASSWORD('new_password');
                echo "(Full message: $sMsg)".CRLF;
                // FOR DEBUGGING ONLY (shows password):
                $sql = "ALTER USER '$sDUser'@'$sDHost' IDENTIFIED BY '$sDPass';";
                echo "SQL: $sql".CRLF;
                #$sCmd = "echo $sql | sudo mysql";
                $sCmd = 'echo "$sql" | sudo mysql';
                $oAct = $oConnx->DoCommand($sCmd,$oBuff);
                $ok = $oAct->GetOkay();
                if (!$ok) {
                    echo 'Meh, that failed too. Giving up for now.'.CRLF;
                }
            }
        }

        if ($ok) {
            $oAct = $oConnx->DoCommand($sCmd,$oBuff);
            $oConnx->Shut();

            echo ' - ' . (($oAct->GetOkay() ? $oScrn->GreenIt('Done.') : ($oScrn->ErrorIt('Error: ').$oAct->GetMessage()))) . CRLF;

        } else {
            echo $oScrn->ErrorIt('Error: ')
              .$oScrn->YellowIt($sMsg)
              .' - The setup failed for some reason.'
              .CRLF;
        }

        $oAct = $oBuff->Shut();
    }

    // ++ ACTION: UI pieces ++ //

    // SEE DOCS
    protected function TestUserAccess() {
        $oScrn = self::Screen();
        #echo $this->ReflectThis()->Report();
        $sName = $this->ConnSlug();
        $ftName = $oScrn->GreenIt($sName);
        echo "[$ftName]: Testing configured credentials... ";

        $sCreds = $this->OConn()->CredsForCmd();  // 2025-06-05 get this from the Creds object now.
        return $this->TestAccess($sCreds);
    }
    // SEE DOCS
    protected function TestRootAccess() : bool {
        $oScrn = self::Screen();
        $sName = $this->ObjectSlug();
        $ftName = $oScrn->GreenIt($sName);
        echo "Testing unconfigured root access on [$ftName]... ";
        return $this->TestAccess('');
    }
    protected function TestAccess(string $sCreds) : bool {
        $oScrn = self::Screen();

        $sSQL = '"SHOW SCHEMAS;"';
        $bRoot = ($sCreds === '');
        if ($bRoot) {
            $sCmd = "echo $sSQL | sudo mysql";
            $sMode = 'unconfigured root';
        } else {
            $sCmd = "echo $sSQL | mysql $sCreds";
            $sMode = 'credentialed user';
        }
        $oConnx = $this->OConn(); // shell (outside) connection details

        // DEBUGGING ONLY:
        #echo 'SQL COMMAND: '.$sCmd.CRLF;

        $oBuff = new MemBufferClass;
        #$oConnx->CommOpClass(CommOpClass::class);   // tell oConnx what class to use for Action
        $oBuff->Open(); // keep buffer open for reading data

        $oConnx->Open();
        $oAct = $oConnx->DoCommand($sCmd,$oBuff,new CommOpClass);

        $oAct->HandleResults();
        $oConnx->Shut();

        $ok = $oAct->GetOkay();
        if ($ok) {
            #echo $oScrn->GreenIt('Done.').CRLF;

            $sList = $oBuff->RemoveBytes();
            $arList = explode("\n",trim($sList)); // we might want to ensure that CRLF, CR, and LF all work as EOL.

            // "Database" is the standard header for list of databases.
            // If we don't see this, then something isn't right.
            $sFirst = array_shift($arList);
            if ($sFirst != "Database") {
                self::GotToHere("Something's not right with the db list. Here's what we got:".CRLF.$sList); die();
            }

            $ftMode = $oScrn->BoldIt($sMode);
            $ftStatus = $oScrn->GreenIt('Success');
            $sSuggest = $bRoot ? ' ("setup" should work)' : '';
            echo "$ftStatus: Access in $ftMode mode seems to be ok$sSuggest. Here's a list of schemas found:".CRLF;
            $oList = $oScrn->NewList();
            foreach ($arList as $sSchema) {
                $oList->AddLine($sSchema);
            }
            echo $oList->Render();
        } else {
            $ftStatus = $oScrn->ErrorIt('Error');
            $ftMsg = $oScrn->YellowIt($oAct->GetMessage());
            $sSuggest = $bRoot ? ' (Try "setup"?)' : '';
            echo "$ftStatus: We do not seem to have access.$sSuggest Error received was: ".CRLF.$ftMsg.CRLF;
        }
        $oBuff->Shut();
        return $ok;
    }


    // ++ ACTION: internal ++ //

    private $oStatus = NULL;
    protected function CommOpStatus() : CommOpIface { return $this->oStatus ?? ($this->oStatus = new CommOpClass); }

    // -- ACTION -- //
    // ++ OBJECTS ++ //

    private $osNative;
    protected function QNative() : QObjIface { return $this->osNative ?? ($this->osNative = QObjClass::AsNew()); }

    // -- OBJECTS -- //

}