diff --git a/4dev/tests/DB/CoreLibsDBSqLiteTest.php b/4dev/tests/DB/CoreLibsDBSqLiteTest.php
new file mode 100644
index 00000000..14eff9fe
--- /dev/null
+++ b/4dev/tests/DB/CoreLibsDBSqLiteTest.php
@@ -0,0 +1,46 @@
+markTestSkipped(
+ 'The SqLite extension is not available.'
+ );
+ }
+ }
+
+ /**
+ * Undocumented function
+ *
+ * @testdox DB\SqLite Class tests
+ *
+ * @return void
+ */
+ public function testSqLite()
+ {
+ $this->markTestIncomplete(
+ 'DB\SqLite Tests have not yet been implemented'
+ );
+ }
+}
+
+// __END__
diff --git a/www/admin/class_test.db.sqlite.php b/www/admin/class_test.db.sqlite.php
new file mode 100644
index 00000000..f481344d
--- /dev/null
+++ b/www/admin/class_test.db.sqlite.php
@@ -0,0 +1,157 @@
+ BASE . LOG,
+ 'log_file_id' => $LOG_FILE_ID,
+ 'log_per_date' => true,
+]);
+// db connection and attach logger
+$db = new CoreLibs\DB\SqLite($log, "sqlite:" . $sql_file);
+$db->log->debug('START', '=============================>');
+
+$PAGE_NAME = 'TEST CLASS: DB: SqLite';
+print "";
+print "
" . $PAGE_NAME . "";
+print "";
+print '';
+
+print "
";
+
+echo "Create Tables on demand
";
+
+$query = <<dbExec($query);
+// **********************
+$query = <<dbExec($query);
+
+print "
";
+
+$table = 'test';
+echo "Table info for: " . $table . "
";
+
+if (($table_info = $db->dbShowTableMetaData($table)) === false) {
+ print "Read problem for: $table
";
+} else {
+ print "TABLE INFO: " . print_r($table_info, true) . "
";
+}
+
+print "
";
+
+echo "Insert into 'test'
";
+
+$query = <<dbExecParams($query, [
+ 'test', rand(1, 100), true,
+ date('Y-m-d H:i:s'), date_format(date_create("now"), 'Y-m-d H:i:s.u'), date('Y-m-d'),
+ // julianday pass through
+ date('Y-m-d H:i:s'),
+ // use "U" if no unixepoch in query
+ date('U'), date('Y-m-d H:i:s'),
+ 1.5, 10.5, 'Anything'
+]);
+
+print "
";
+
+echo "Insert into 'test_no_pk'
";
+
+$query = <<dbExecParams($query, ['test no pk', rand(100, 200)]);
+
+print "
";
+
+$query = <<dbReturnArray($query))) {
+ print "ROW: PK(test_id): " . $row["test_id"]
+ . ", Text: " . $row["c_text"] . ", Int: " . $row["c_integer"]
+ . ", Int Default: " . $row["c_integer_default"]
+ . ", Date Default: " . $row["c_datetime_default"]
+ . "
";
+}
+
+echo "
";
+
+$query = <<dbReturnArray($query))) {
+ print "ROW[CURSOR]: PK(rowid): " . $row["rowid"]
+ . ", Text: " . $row["c_text"] . ", Int: " . $row["c_integer"]
+ . "
";
+}
+
+print "";
+
+// __END__
diff --git a/www/admin/class_test.php b/www/admin/class_test.php
index 315effd3..7276f51d 100644
--- a/www/admin/class_test.php
+++ b/www/admin/class_test.php
@@ -73,6 +73,7 @@ $test_files = [
'class_test.db.query-placeholder.php' => 'Class Test: DB query placeholder convert',
'class_test.db.dbReturn.php' => 'Class Test: DB dbReturn',
'class_test.db.single.php' => 'Class Test: DB single query tests',
+ 'class_test.db.sqlite.php' => 'Class Test: DB: SqLite',
'class_test.convert.colors.php' => 'Class Test: CONVERT COLORS',
'class_test.check.colors.php' => 'Class Test: CHECK COLORS',
'class_test.mime.php' => 'Class Test: MIME',
diff --git a/www/lib/CoreLibs/DB/Interface/DatabaseInterface.php b/www/lib/CoreLibs/DB/Interface/DatabaseInterface.php
new file mode 100644
index 00000000..6960237e
--- /dev/null
+++ b/www/lib/CoreLibs/DB/Interface/DatabaseInterface.php
@@ -0,0 +1,90 @@
+>|false
+ */
+ public function dbShowTableMetaData(string $table): array|false;
+
+ /**
+ * for reading or simple execution, no return data
+ *
+ * @param string $query
+ * @return int|false
+ */
+ public function dbExec(string $query): int|false;
+
+ /**
+ * Run a simple query and return its statement
+ *
+ * @param string $query
+ * @return \PDOStatement|false
+ */
+ public function dbQuery(string $query): \PDOStatement|false;
+
+ /**
+ * Execute one query with params
+ *
+ * @param string $query
+ * @param array $params
+ * @return \PDOStatement|false
+ */
+ public function dbExecParams(string $query, array $params): \PDOStatement|false;
+
+ /**
+ * Prepare query
+ *
+ * @param string $query
+ * @return \PDOStatement|false
+ */
+ public function dbPrepare(string $query): \PDOStatement|false;
+
+ /**
+ * execute a cursor
+ *
+ * @param \PDOStatement $cursor
+ * @param array $params
+ * @return bool
+ */
+ public function dbCursorExecute(\PDOStatement $cursor, array $params): bool;
+
+ /**
+ * return array with data, when finshed return false
+ * also returns false on error
+ *
+ * TODO: This is currently a one time run
+ * if the same query needs to be run again, the cursor_ext must be reest
+ * with dbCacheReset
+ *
+ * @param string $query
+ * @param array $params
+ * @return array|false
+ */
+ public function dbReturnArray(string $query, array $params = []): array|false;
+
+ /**
+ * get current db handler
+ * this is for raw access
+ *
+ * @return \PDO
+ */
+ public function getDbh(): \PDO;
+}
+
+// __END__
diff --git a/www/lib/CoreLibs/DB/SqLite.php b/www/lib/CoreLibs/DB/SqLite.php
new file mode 100644
index 00000000..89e348f9
--- /dev/null
+++ b/www/lib/CoreLibs/DB/SqLite.php
@@ -0,0 +1,432 @@
+ extended cursoers string index with content */
+ private array $cursor_ext = [];
+
+ /**
+ * init database system
+ *
+ * @param \CoreLibs\Logging\Logging $log
+ * @param string $dsn
+ */
+ public function __construct(
+ \CoreLibs\Logging\Logging $log,
+ string $dsn
+ ) {
+ $this->log = $log;
+ // open new connection
+ if ($this->__connectToDB($dsn) === false) {
+ throw new \ErrorException("Cannot load database: " . $dsn, 1);
+ }
+ }
+
+ // *********************************************************************
+ // MARK: PRIVATE METHODS
+ // *********************************************************************
+
+ /**
+ * Get a cursor dump with all info
+ *
+ * @param \PDOStatement $cursor
+ * @return string|false
+ */
+ private function __dbGetCursorDump(\PDOStatement $cursor): string|false
+ {
+ // get the cursor info
+ ob_start();
+ $cursor->debugDumpParams();
+ $cursor_dump = ob_get_contents();
+ ob_end_clean();
+ return $cursor_dump;
+ }
+
+ /**
+ * fetch rows from a cursor (post execute)
+ *
+ * @param \PDOStatement $cursor
+ * @return array|false
+ */
+ private function __dbFetchArray(\PDOStatement $cursor): array|false
+ {
+ try {
+ // on empty array return false
+ // TODO make that more elegant?
+ return empty($row = $cursor->fetch(mode:\PDO::FETCH_NAMED)) ? false : $row;
+ } catch (\PDOException $e) {
+ $this->log->error(
+ "Cannot fetch from cursor",
+ [
+ "dsn" => $this->dsn,
+ "DumpParams" => $this->__dbGetCursorDump($cursor),
+ "PDOException" => $e
+ ]
+ );
+ return false;
+ }
+ }
+
+ // MARK: open database
+
+ /**
+ * Open database
+ * reports errors for wrong DSN or failed connection
+ *
+ * @param string $dsn
+ * @return bool
+ */
+ private function __connectToDB(string $dsn): bool
+ {
+ // check if dsn starts with ":"
+ if (!str_starts_with($dsn, "sqlite:")) {
+ $this->log->error(
+ "Invalid dsn string",
+ [
+ "dsn" => $dsn
+ ]
+ );
+ return false;
+ }
+ // TODO: if not ":memory:" check if path to file is writeable by system
+ // avoid double open
+ if (!empty($this->dsn) && $dsn == $this->dsn && $this->dbh instanceof \PDO) {
+ $this->log->info(
+ "Connection already establisehd with this dsn",
+ [
+ "dsn" => $dsn,
+ ]
+ );
+ return true;
+ }
+ // TODO: check that folder is writeable
+ // set DSN and open connection
+ $this->dsn = $dsn;
+ try {
+ $this->dbh = new \PDO($this->dsn);
+ } catch (\PDOException $e) {
+ $this->log->error(
+ "Cannot open database",
+ [
+ "dsn" => $this->dsn,
+ "PDOException" => $e
+ ]
+ );
+ return false;
+ }
+ return true;
+ }
+
+ // *********************************************************************
+ // MARK: PUBLIC METHODS
+ // *********************************************************************
+
+ // MARK: db meta data (table info)
+
+ /**
+ * Table meta data
+ * Note that if columns have multi
+ *
+ * @param string $table
+ * @return array>|false
+ */
+ public function dbShowTableMetaData(string $table): array|false
+ {
+ $table_info = [];
+ $query = <<dbReturnArray($query, [$table]))) {
+ $table_info[] = [
+ 'cid' => $row['cid'],
+ 'name' => $row['name'],
+ 'type' => $row['type'],
+ 'notnull' => $row['notnull'],
+ 'dflt_value' => $row['dflt_value'],
+ 'pk' => $row['pk'],
+ 'idx_name' => $row['idx_name'],
+ 'idx_unique' => $row['idx_unique'],
+ 'idx_origin' => $row['idx_origin'],
+ 'idx_partial' => $row['idx_partial'],
+ ];
+ }
+
+ if (!$table_info) {
+ return false;
+ }
+ return $table_info;
+ }
+
+ // MARK: db exec
+
+ /**
+ * for reading or simple execution, no return data
+ *
+ * @param string $query
+ * @return int|false
+ */
+ public function dbExec(string $query): int|false
+ {
+ try {
+ return $this->dbh->exec($query);
+ } catch (\PDOException $e) {
+ $this->log->error(
+ "Cannot execute query",
+ [
+ "dsn" => $this->dsn,
+ "query" => $query,
+ "PDOException" => $e
+ ]
+ );
+ return false;
+ }
+ }
+
+ // MARK: db query
+
+ /**
+ * Run a simple query and return its statement
+ *
+ * @param string $query
+ * @return \PDOStatement|false
+ */
+ public function dbQuery(string $query): \PDOStatement|false
+ {
+ try {
+ return $this->dbh->query($query, \PDO::FETCH_NAMED);
+ } catch (\PDOException $e) {
+ $this->log->error(
+ "Cannot run query",
+ [
+ "dsn" => $this->dsn,
+ "query" => $query,
+ "PDOException" => $e
+ ]
+ );
+ return false;
+ }
+ }
+
+ // MARK: db prepare & execute calls
+
+ /**
+ * Execute one query with params
+ *
+ * @param string $query
+ * @param array $params
+ * @return \PDOStatement|false
+ */
+ public function dbExecParams(string $query, array $params): \PDOStatement|false
+ {
+ // prepare query
+ if (($cursor = $this->dbPrepare($query)) === false) {
+ return false;
+ }
+ // execute the query, on failure return false
+ if ($this->dbCursorExecute($cursor, $params) === false) {
+ return false;
+ }
+ return $cursor;
+ }
+
+ /**
+ * Prepare query
+ *
+ * @param string $query
+ * @return \PDOStatement|false
+ */
+ public function dbPrepare(string $query): \PDOStatement|false
+ {
+ try {
+ // store query with cursor so we can reference?
+ return $this->dbh->prepare($query);
+ } catch (\PDOException $e) {
+ $this->log->error(
+ "Cannot open cursor",
+ [
+ "dsn" => $this->dsn,
+ "query" => $query,
+ "PDOException" => $e
+ ]
+ );
+ return false;
+ }
+ }
+
+ /**
+ * execute a cursor
+ *
+ * @param \PDOStatement $cursor
+ * @param array $params
+ * @return bool
+ */
+ public function dbCursorExecute(\PDOStatement $cursor, array $params): bool
+ {
+ try {
+ return $cursor->execute($params);
+ } catch (\PDOException $e) {
+ // write error log
+ $this->log->error(
+ "Cannot execute prepared query",
+ [
+ "dsn" => $this->dsn,
+ "params" => $params,
+ "DumpParams" => $this->__dbGetCursorDump($cursor),
+ "PDOException" => $e
+ ]
+ );
+ return false;
+ }
+ }
+
+ // MARK: db return array
+
+ /**
+ * Returns hash for query
+ * Hash is used in all internal storage systems for return data
+ *
+ * @param string $query The query to create the hash from
+ * @param array $params If the query is params type we need params
+ * data to create a unique call one, optional
+ * @return string Hash, as set by hash long
+ */
+ public function dbGetQueryHash(string $query, array $params = []): string
+ {
+ return Hash::__hashLong(
+ $query . (
+ $params !== [] ?
+ '#' . json_encode($params) : ''
+ )
+ );
+ }
+
+ /**
+ * resets all data stored to this query
+ * @param string $query The Query whose cache should be cleaned
+ * @param array $params If the query is params type we need params
+ * data to create a unique call one, optional
+ * @return bool False if query not found, true if success
+ */
+ public function dbCacheReset(string $query, array $params = []): bool
+ {
+ $query_hash = $this->dbGetQueryHash($query, $params);
+ // clears cache for this query
+ if (empty($this->cursor_ext[$query_hash]['query'])) {
+ $this->log->error('Cannot reset cursor_ext with given query and params', [
+ "query" => $query,
+ "params" => $params,
+ ]);
+ return false;
+ }
+ unset($this->cursor_ext[$query_hash]);
+ return true;
+ }
+
+ /**
+ * return array with data, when finshed return false
+ * also returns false on error
+ *
+ * TODO: This is currently a one time run
+ * if the same query needs to be run again, the cursor_ext must be reest
+ * with dbCacheReset
+ *
+ * @param string $query
+ * @param array $params
+ * @return array|false
+ */
+ public function dbReturnArray(string $query, array $params = []): array|false
+ {
+ $query_hash = $this->dbGetQueryHash($query, $params);
+ if (!isset($this->cursor_ext[$query_hash])) {
+ $this->cursor_ext[$query_hash] = [
+ // cursor null: unset, if set \PDOStatement
+ 'cursor' => null,
+ // the query used in this call
+ 'query' => $query,
+ // parameter
+ 'params' => $params,
+ // how many rows have been read from db
+ 'read_rows' => 0,
+ // when fetch array or cache read returns false
+ // in loop read that means dbReturn retuns false without error
+ 'finished' => false,
+ ];
+ if (!empty($params)) {
+ if (($cursor = $this->dbExecParams($query, $params)) === false) {
+ return false;
+ }
+ } else {
+ if (($cursor = $this->dbQuery($query)) === false) {
+ return false;
+ }
+ }
+ $this->cursor_ext[$query_hash]['cursor'] = $cursor;
+ }
+ // flag finished if row is false
+ $row = $this->__dbFetchArray($this->cursor_ext[$query_hash]['cursor']);
+ if ($row === false) {
+ $this->cursor_ext[$query_hash]['finished'] = true;
+ } else {
+ $this->cursor_ext[$query_hash]['read_rows']++;
+ }
+ return $row;
+ }
+
+ // MARK other interface
+
+ /**
+ * get current db handler
+ * this is for raw access
+ *
+ * @return \PDO
+ */
+ public function getDbh(): \PDO
+ {
+ return $this->dbh;
+ }
+}
+
+// __END__