diff --git a/manual/includes/_session.md b/manual/includes/_session.md index 460042cd..06b856c7 100644 --- a/manual/includes/_session.md +++ b/manual/includes/_session.md @@ -8,16 +8,37 @@ Session variables keep information about one single user, and are available to a ```yaml session: - name: MUSICPRODUCTIONMANAGER - max_life_time: 86400 - save_handler: files - save_path: /tmp/sessions + name: ${SESSION_NAME} + max_life_time: ${SESSION_MAX_LIFE_TIME} + save_handler: ${SESSION_SAVE_HANDLER} + save_path: ${SESSION_SAVE_PATH} + cookie_lifetime: ${SESSION_COOKIE_LIFETIME} + cookie_path: ${SESSION_COOKIE_PATH} + cookie_secure: ${SESSION_COOKIE_SECURE} + cookie_httponly: ${SESSION_COOKIE_HTTPONLY} + cookie_domain: ${SESSION_COOKIE_DOMAIN} ``` -- `name`: Name of the session. -- `max_life_time`: Maximum lifetime of the session in seconds (e.g., 86400 seconds = 24 hours). -- `save_handler`: Specifies the session storage mechanism (in this case, files). -- `save_path`: Directory where session files are stored. +### Explanation: + +- **name**: Specifies the name of the session. The value `${SESSION_NAME}` refers to an environment variable or a parameter that should be defined elsewhere in the application or server environment. It determines the session's name identifier. + +- **max_life_time**: Defines the maximum lifetime of the session in seconds. The value `${SESSION_MAX_LIFE_TIME}` is expected to come from an environment variable or configuration setting, controlling how long the session will last before it expires. + +- **save_handler**: Specifies the handler used to save session data. In this case, `${SESSION_SAVE_HANDLER}` likely refers to the session handler, such as `redis`, `files`, or `database`, depending on the server configuration. + +- **save_path**: Defines the path where session data will be stored. The value `${SESSION_SAVE_PATH}` is an environment variable or configuration parameter, which could specify a directory or Redis connection string (if `redis` is used as the save handler). + +- **cookie_lifetime**: Specifies the lifetime of the session cookie in seconds, which controls how long the cookie will persist in the user's browser. `${SESSION_COOKIE_LIFETIME}` is the environment variable or configuration value that sets this duration. + +- **cookie_path**: The path on the server where the session cookie is valid. `${SESSION_COOKIE_PATH}` determines the directory or URL prefix for which the session cookie is set. + +- **cookie_secure**: A boolean flag that determines whether the session cookie should be transmitted over HTTPS only. If `${SESSION_COOKIE_SECURE}` is set to `true`, the cookie will only be sent over secure connections. + +- **cookie_httponly**: A boolean flag that indicates whether the session cookie is accessible only through the HTTP protocol (i.e., not accessible via JavaScript). If `${SESSION_COOKIE_HTTPONLY}` is `true`, it increases security by preventing client-side access to the cookie. + +- **cookie_domain**: Specifies the domain to which the session cookie is valid. `${SESSION_COOKIE_DOMAIN}` can define the domain for which the session cookie should be sent, allowing cross-subdomain access to the session. + **PHP Script** @@ -68,10 +89,15 @@ For example: ```yaml session: - name: MUSICPRODUCTIONMANAGER - max_life_time: 86400 - save_handler: redis + name: ${SESSION_NAME} + max_life_time: ${SESSION_MAX_LIFE_TIME} + save_handler: ${SESSION_SAVE_HANDLER} save_path: ${SESSION_SAVE_PATH} + cookie_lifetime: ${SESSION_COOKIE_LIFETIME} + cookie_path: ${SESSION_COOKIE_PATH} + cookie_secure: ${SESSION_COOKIE_SECURE} + cookie_httponly: ${SESSION_COOKIE_HTTPONLY} + cookie_domain: ${SESSION_COOKIE_DOMAIN} ``` `${SESSION_SAVE_PATH}` contains entire of `save_path` that encrypted with you secure key. diff --git a/manual/index.html b/manual/index.html index 431c64cb..8eca2dd3 100644 --- a/manual/index.html +++ b/manual/index.html @@ -1989,15 +1989,44 @@

Session

Session with File

Yaml File

session:
-  name: MUSICPRODUCTIONMANAGER
-  max_life_time: 86400
-  save_handler: files
-  save_path: /tmp/sessions
+ name: ${SESSION_NAME} + max_life_time: ${SESSION_MAX_LIFE_TIME} + save_handler: ${SESSION_SAVE_HANDLER} + save_path: ${SESSION_SAVE_PATH} + cookie_lifetime: ${SESSION_COOKIE_LIFETIME} + cookie_path: ${SESSION_COOKIE_PATH} + cookie_secure: ${SESSION_COOKIE_SECURE} + cookie_httponly: ${SESSION_COOKIE_HTTPONLY} + cookie_domain: ${SESSION_COOKIE_DOMAIN} +

Explanation:

PHP Script

<?php
@@ -2031,10 +2060,15 @@ 

Session with Redis

You can not encrypt the ${REDIS_AUTH} value. If you want to secure the config, encrypt entire save_path instead.

For example:

session:
-  name: MUSICPRODUCTIONMANAGER
-  max_life_time: 86400
-  save_handler: redis
-  save_path: ${SESSION_SAVE_PATH}
+ name: ${SESSION_NAME} + max_life_time: ${SESSION_MAX_LIFE_TIME} + save_handler: ${SESSION_SAVE_HANDLER} + save_path: ${SESSION_SAVE_PATH} + cookie_lifetime: ${SESSION_COOKIE_LIFETIME} + cookie_path: ${SESSION_COOKIE_PATH} + cookie_secure: ${SESSION_COOKIE_SECURE} + cookie_httponly: ${SESSION_COOKIE_HTTPONLY} + cookie_domain: ${SESSION_COOKIE_DOMAIN}

${SESSION_SAVE_PATH} contains entire of save_path that encrypted with you secure key.

PHP Script

<?php
diff --git a/src/Database/PicoDatabase.php b/src/Database/PicoDatabase.php
index 77c2ee63..7b316f38 100644
--- a/src/Database/PicoDatabase.php
+++ b/src/Database/PicoDatabase.php
@@ -1056,6 +1056,33 @@ public function getDatabaseType()
         return $this->databaseType;
     }
 
+    /**
+     * Retrieves the time zone used by the database.
+     *
+     * This function calls the `getTimeZone()` method from the `databaseCredentials`
+     * object to fetch the time zone configured for the database connection.
+     *
+     * @return string The time zone of the database (e.g., "UTC", "America/New_York").
+     */
+    public function getDatabaseTimeZone()
+    {
+        return $this->databaseCredentials->getTimeZone();
+    }
+
+    /**
+     * Retrieves the time zone offset of the database connection.
+     *
+     * This function retrieves the time zone offset by calling the static method
+     * `getTimeZoneOffset()` with the `databaseConnection` as an argument.
+     * The offset is returned in seconds from UTC.
+     *
+     * @return string The time zone offset, typically in hours and minutes (e.g., "+02:00").
+     */
+    public function getDatabaseTimeZoneOffset()
+    {
+        return self::getTimeZoneOffset($this->databaseConnection);
+    }
+
     /**
      * Convert the object to a JSON string representation for debugging.
      *
diff --git a/src/Session/PicoSession.php b/src/Session/PicoSession.php
index ffcfc2ad..8540c64c 100644
--- a/src/Session/PicoSession.php
+++ b/src/Session/PicoSession.php
@@ -195,26 +195,29 @@ public function destroy()
     }
 
     /**
-     * Sets cookie parameters for the session.
+     * Sets the session cookie parameters, including lifetime, path, domain, security attributes, and SameSite settings.
      *
-     * This method sets parameters for the session cookie, including lifetime,
-     * security attributes, and SameSite settings.
+     * This method configures the session cookie parameters such as maximum lifetime, path, domain, and security settings
+     * like whether the cookie should be accessible only over HTTPS or only via HTTP. It also sets the SameSite attribute
+     * for compatibility with different browsers and PHP versions.
      *
-     * @param int $maxlifetime Maximum lifetime of the session cookie.
+     * @param int $maxlifetime Maximum lifetime of the session cookie in seconds.
+     * @param string $path The path where the cookie will be available on the server.
+     * @param string $domain The domain to which the cookie is available.
      * @param bool $secure Indicates if the cookie should only be transmitted over a secure HTTPS connection.
-     * @param bool $httponly Indicates if the cookie is accessible only through the HTTP protocol.
-     * @param string $samesite The SameSite attribute of the cookie (Lax, Strict, None).
+     * @param bool $httponly Indicates if the cookie should be accessible only through the HTTP protocol.
+     * @param string $samesite The SameSite attribute of the cookie (Lax, Strict, None). Default is 'Strict'.
      * @return self Returns the current instance for method chaining.
      */
-    public function setSessionCookieParams($maxlifetime, $secure, $httponly, $samesite = self::SAME_SITE_STRICT)
+    public function setSessionCookieParams($maxlifetime, $path, $domain, $secure, $httponly, $samesite = self::SAME_SITE_STRICT)
     {
         if (PHP_VERSION_ID < 70300) {
-            session_set_cookie_params($maxlifetime, '/; samesite=' . $samesite, $_SERVER['HTTP_HOST'], $secure, $httponly);
+            session_set_cookie_params($maxlifetime, $path.'; samesite=' . $samesite, $domain, $secure, $httponly);
         } else {
             session_set_cookie_params(array(
                 'lifetime' => $maxlifetime,
-                'path' => '/',
-                'domain' => $_SERVER['HTTP_HOST'],
+                'path' => $path,
+                'domain' => $domain,
                 'secure' => $secure,
                 'httponly' => $httponly,
                 'samesite' => $samesite
@@ -224,18 +227,19 @@ public function setSessionCookieParams($maxlifetime, $secure, $httponly, $samesi
     }
 
     /**
-     * Sets a cookie with SameSite attribute support for different PHP versions.
+     * Sets a cookie with the SameSite attribute, supporting both older and newer PHP versions.
      *
-     * This method sets a cookie with the specified SameSite attribute, supporting both older and newer PHP versions.
+     * This method sets a cookie with a specified SameSite attribute, ensuring compatibility with both PHP versions
+     * prior to and after PHP 7.3. It supports cookies with the 'Lax', 'Strict', or 'None' SameSite attributes.
      *
      * @param string $name The name of the cookie.
      * @param string $value The value of the cookie.
-     * @param int $expire The expiration time of the cookie.
-     * @param string $path The path on the server in which the cookie will be available.
-     * @param string $domain The domain that the cookie is available to.
+     * @param int $expire The expiration time of the cookie as a Unix timestamp.
+     * @param string $path The path on the server where the cookie is available.
+     * @param string $domain The domain to which the cookie is available.
      * @param bool $secure Indicates if the cookie should only be transmitted over a secure HTTPS connection.
      * @param bool $httponly Indicates if the cookie is accessible only through the HTTP protocol.
-     * @param string $samesite The SameSite attribute of the cookie (Lax, Strict, None).
+     * @param string $samesite The SameSite attribute of the cookie (Lax, Strict, None). Default is 'Strict'.
      * @return self Returns the current instance for method chaining.
      */
     public function setSessionCookieSameSite($name, $value, $expire, $path, $domain, $secure, $httponly, $samesite = self::SAME_SITE_STRICT) // NOSONAR
@@ -355,4 +359,18 @@ public function setSessionId($id)
         @session_id($id);
         return $this;
     }
+
+    /**
+     * Converts the session data to a string representation.
+     *
+     * This method returns the session data as a JSON-encoded string, which can be useful for debugging
+     * or logging the contents of the session. It encodes the global `$_SESSION` array into a JSON string.
+     *
+     * @return string The JSON-encoded string representation of the session data.
+     */
+    public function __toString()
+    {
+        return json_encode($_SESSION);
+    }
+
 }
diff --git a/src/Util/Database/PicoQueryTranslator.php b/src/Util/Database/PicoQueryTranslator.php
new file mode 100644
index 00000000..39ecff4a
--- /dev/null
+++ b/src/Util/Database/PicoQueryTranslator.php
@@ -0,0 +1,842 @@
+dbToSqlite = [
+            "int" => "INTEGER",
+            "tinyint(1)" => "BOOLEAN", // NOSONAR
+            "tinyint" => "INTEGER",
+            "smallint" => "INTEGER",
+            "mediumint" => "INTEGER",
+            "bigint" => "INTEGER",
+            "real" => "REAL",
+            "float" => "REAL",
+            "double" => "REAL",
+            "decimal" => "REAL",
+            "nvarchar" => "NVARCHAR",
+            "varchar" => "NVARCHAR",
+            "character varying" => "NVARCHAR", // NOSONAR
+            "char" => "NVARCHAR",
+            "tinytext" => "TEXT",
+            "mediumtext" => "TEXT",
+            "longtext" => "TEXT",
+            "text" => "TEXT",
+            "datetime" => "DATETIME",
+            "timestamp" => "TIMESTAMP",
+            "date" => "DATE",
+            "time" => "TIME",
+            "year" => "INTEGER",
+            "boolean" => "INTEGER",
+            "json" => "TEXT",
+            "jsonb" => "TEXT",
+            "integer" => "INTEGER",
+            "serial" => "INTEGER",
+            "bigserial" => "INTEGER",
+            "double precision" => "REAL",
+            "timestamptz" => "TIMESTAMP"
+        ];
+
+        $this->dbToMySQL = [
+            "bigint" => "BIGINT",
+            "mediumint" => "MEDIUMINT",
+            "smallint" => "SMALLINT",
+            "integer" => "INT",
+            "double" => "DOUBLE",
+            "float" => "FLOAT",
+            "real" => "DOUBLE",
+            "decimal" => "DECIMAL",
+            "numeric" => "NUMERIC",
+            "tinytext" => "TINYTEXT",
+            "mediumtext" => "MEDIUMTEXT",
+            "longtext" => "LONGTEXT",
+            "text" => "TEXT",
+            "nvarchar" => "VARCHAR",
+            "varchar" => "VARCHAR",
+            "character varying" => "VARCHAR",
+            "tinyint(1)" => "TINYINT(1)", // NOSONAR
+            "tinyint" => "TINYINT",
+            "boolean" => "TINYINT(1)",
+            "int" => "INT",
+            "datetime" => "DATETIME",
+            "date" => "DATE",
+            "timestamptz" => "TIMESTAMP",
+            "timestamp with time zone" => "TIMESTAMP",
+            "timestamp without time zone" => "DATETIME",
+            "timestamp" => "TIMESTAMPTZ",
+            "json" => "JSON",
+            "enum" => "ENUM",
+            "set" => "SET",
+            "char" => "CHAR"
+        ];
+
+        $this->dbToPostgreSQL = [
+            "bigint" => "BIGINT",
+            "mediumint" => "INTEGER",
+            "smallint" => "INTEGER",
+            "tinyint(1)" => "BOOLEAN",
+            "tinyint" => "INTEGER",
+            "integer" => "INTEGER",
+            "real" => "REAL",
+            "longtext" => "TEXT",
+            "mediumtext" => "TEXT",
+            "smalltext" => "TEXT",
+            "tinytext" => "TEXT",
+            "text" => "TEXT",
+            "character varying" => "CHARACTER VARYING", // NOSONAR
+            "nvarchar" => "CHARACTER VARYING",
+            "varchar" => "CHARACTER VARYING",
+            "char" => "CHARACTER",
+            "boolean" => "BOOLEAN",
+            "datetime" => "TIMESTAMP WITHOUT TIME ZONE",
+            "date" => "DATE",
+            "timestamptz" => "TIMESTAMP WITH TIME ZONE",
+            "timestamp" => "TIMESTAMP WITH TIME ZONE",
+            "time" => "TIME",
+            "json" => "JSONB"
+        ];
+    }
+
+    /**
+     * Replaces all occurrences of a substring in the given string.
+     * 
+     * This function uses a regular expression to replace all matches of the search pattern with the specified replacement.
+     * 
+     * @param string|null $str The string to search and replace in. If null, returns null.
+     * @param string $search The substring to search for.
+     * @param string $replacement The substring to replace the search pattern with.
+     * @return string|null The modified string, or null if the input string is null.
+     */
+    private function replaceAll($str, $search, $replacement) {
+        return str_ireplace($search, $replacement, $str);
+    }
+
+    /**
+     * Translates the provided SQL query to a specified target database format.
+     * 
+     * This method converts the provided SQL string into a format compatible with the specified target database (MySQL, PostgreSQL, or SQLite),
+     * ensuring that all relevant data types and structures are adapted.
+     *
+     * @param string $value The SQL query to be translated.
+     * @param string $targetType The target database type ('mysql', 'pgsql', 'sqlite').
+     * @return string The translated SQL query in the target database format.
+     */
+    public function translate($value, $targetType) {
+        $dropTables = [];
+        $tableInfo = $this->extractDropTableQueries($value, $targetType);
+        
+        foreach ($tableInfo as $table) {
+            $dropTables[] = "-- DROP TABLE IF EXISTS " . $table['table'] . ";";
+        }
+
+        $value = $this->replaceAll($value, '`', '');
+        $value = $this->replaceAll($value, ' timestamp with time zone', ' timestamptz');
+        $value = $this->replaceAll($value, ' timestamp without time zone', ' timestamp');
+        $value = $this->replaceAll($value, ' character varying', ' varchar');
+        $value = $this->replaceAll($value, ' COLLATE pg_catalog."default"', '');
+        $value = $this->replaceAll($value, ' TINYINT(1)', ' boolean');
+        
+        $tableParser = new PicoTableParser(); // Assuming this is a predefined class in your code
+        $tableParser->parseAll($value);
+        $tables = $tableParser->getResult();
+        
+        $lines = [];
+        foreach ($tables as $table) {
+            $convertedTable = $this->convertQuery($table, $targetType);
+            $lines[] = $convertedTable;
+            $lines[] = '';
+        }
+
+        if (!empty($dropTables)) {
+            $dropTables[] = "\r\n\r\n";
+        }
+
+        return implode("\r\n", $dropTables) . implode("\r\n", $lines);
+    }
+
+    /**
+     * Converts a table schema to a query compatible with the specified database type.
+     * 
+     * This method takes the parsed table schema and converts it to the corresponding SQL syntax for the target database.
+     * 
+     * @param array $table The table schema to convert.
+     * @param string $targetType The target database type ('mysql', 'pgsql', 'sqlite').
+     * @return string The converted table schema in the target database format.
+     */
+    private function convertQuery($table, $targetType) {
+        if ($this->isSQLite($targetType)) {
+            return $this->toSqliteOut($table, $targetType);
+        } elseif ($this->isMySQL($targetType)) {
+            return $this->toMySQLOut($table, $targetType);
+        } elseif ($this->isPGSQL($targetType)) {
+            return $this->toPostgreSQLOut($table, $targetType);
+        }
+    }
+
+    /**
+     * Converts a table schema to SQLite-specific SQL syntax.
+     * 
+     * This method adapts the provided table schema to SQLite format, including data type conversions and primary key handling.
+     * 
+     * @param array $table The table schema to convert.
+     * @param string $targetType The target database type ('sqlite').
+     * @return string The converted table schema in SQLite format.
+     */
+    private function toSqliteOut($table, $targetType) {
+        $sqliteTable = [
+            'tableName' => $table['tableName'],
+            'primaryKey' => $table['primaryKey'],
+            'columns' => array_map(function($column) {
+                $column['Type'] = $this->toSqliteType($column['Type'], $column['Length']);
+                return $column;
+            }, $table['columns'])
+        ];
+        return $this->toSqliteTable($sqliteTable, $targetType);
+    }
+
+    /**
+     * Converts a table schema to MySQL-specific SQL syntax.
+     * 
+     * This method adapts the provided table schema to MySQL format, including data type conversions and primary key handling.
+     * 
+     * @param array $table The table schema to convert.
+     * @param string $targetType The target database type ('mysql').
+     * @return string The converted table schema in MySQL format.
+     */
+    private function toMySQLOut($table, $targetType) {
+        $mysqlTable = [
+            'tableName' => $table['tableName'],
+            'primaryKey' => $table['primaryKey'],
+            'columns' => array_map(function($column) {
+                $column['Type'] = $this->toMySQLType($column['Type'], $column['Length']);
+                return $column;
+            }, $table['columns'])
+        ];
+        return $this->toMySQLTable($mysqlTable, $targetType);
+    }
+
+    /**
+     * Converts a table schema to PostgreSQL-specific SQL syntax.
+     * 
+     * This method adapts the provided table schema to PostgreSQL format, including data type conversions and primary key handling.
+     * 
+     * @param array $table The table schema to convert.
+     * @param string $targetType The target database type ('pgsql').
+     * @return string The converted table schema in PostgreSQL format.
+     */
+    private function toPostgreSQLOut($table, $targetType) {
+        $pgTable = [
+            'tableName' => $table['tableName'],
+            'primaryKey' => $table['primaryKey'],
+            'columns' => array_map(function($column) {
+                $column['Type'] = $this->toPostgreSQLType($column['Type'], $column['Length']);
+                return $column;
+            }, $table['columns'])
+        ];
+        return $this->toPostgreSQLTable($pgTable, $targetType);
+    }
+
+    
+    /**
+     * Converts a given table for SQLite target type.
+     *
+     * This method uses a common `toTable` function to convert a table for SQLite.
+     *
+     * @param string $sqliteTable The name of the SQLite table.
+     * @param string $targetType The target database type (e.g., 'sqlite').
+     * @return mixed The result of the `toTable` method.
+     */
+    private function toSqliteTable($sqliteTable, $targetType) {
+        return $this->toTable($sqliteTable, $targetType);
+    }
+
+    /**
+     * Converts a given table for MySQL target type.
+     *
+     * This method uses a common `toTable` function to convert a table for MySQL.
+     *
+     * @param string $mysqlTable The name of the MySQL table.
+     * @param string $targetType The target database type (e.g., 'mysql').
+     * @return mixed The result of the `toTable` method.
+     */
+    private function toMySQLTable($mysqlTable, $targetType) {
+        return $this->toTable($mysqlTable, $targetType);
+    }
+
+    /**
+     * Converts a given table for PostgreSQL target type.
+     *
+     * This method uses a common `toTable` function to convert a table for PostgreSQL.
+     *
+     * @param string $pgTable The name of the PostgreSQL table.
+     * @param string $targetType The target database type (e.g., 'pgsql').
+     * @return mixed The result of the `toTable` method.
+     */
+    private function toPostgreSQLTable($pgTable, $targetType) {
+        return $this->toTable($pgTable, $targetType);
+    }
+
+    /**
+     * Checks if the target type is MySQL or MariaDB.
+     *
+     * This method verifies if the target type matches MySQL or MariaDB.
+     *
+     * @param string $targetType The target database type.
+     * @return bool Returns true if the target type is 'mysql' or 'mariadb', otherwise false.
+     */
+    private function isMySQL($targetType) {
+        return $targetType === 'mysql' || $targetType === 'mariadb';
+    }
+
+    /**
+     * Checks if the target type is PostgreSQL.
+     *
+     * This method verifies if the target type matches PostgreSQL or PGSQL.
+     *
+     * @param string $targetType The target database type.
+     * @return bool Returns true if the target type is 'pgsql' or 'postgresql', otherwise false.
+     */
+    private function isPGSQL($targetType) {
+        return $targetType === 'pgsql' || $targetType === 'postgresql';
+    }
+
+    /**
+     * Checks if the target type is SQLite.
+     *
+     * This method verifies if the target type matches SQLite.
+     *
+     * @param string $targetType The target database type.
+     * @return bool Returns true if the target type is 'sqlite', otherwise false.
+     */
+    private function isSQLite($targetType) {
+        return $targetType === 'sqlite';
+    }
+
+    /**
+     * Checks if the given column type is a real number (e.g., FLOAT, DOUBLE, REAL, DECIMAL).
+     *
+     * This method checks if the column type corresponds to a real number type.
+     *
+     * @param string $columnType The column type to check.
+     * @return bool Returns true if the column type is a real number, otherwise false.
+     */
+    private function isReal($columnType) {
+        return stripos($columnType, 'FLOAT') !== false ||
+            stripos($columnType, 'DOUBLE') !== false ||
+            stripos($columnType, 'REAL') !== false ||
+            stripos($columnType, 'DECIMAL') !== false;
+    }
+
+    /**
+     * Checks if the given column type is a boolean type (e.g., BOOLEAN, BOOL, TINYINT(1)).
+     *
+     * This method checks if the column type corresponds to a boolean type.
+     *
+     * @param string $columnType The column type to check.
+     * @return bool Returns true if the column type is boolean, otherwise false.
+     */
+    private function isBoolean($columnType) {
+        return strtoupper($columnType) == 'BOOLEAN' ||
+            strtoupper($columnType) == 'BOOL' ||
+            strtoupper($columnType) == 'TINYINT(1)';
+    }
+
+    /**
+     * Converts a column type to the SQLite type format.
+     * @param string $type The original column type.
+     * @param int|null $length The column length (optional).
+     * @return string The converted SQLite column type.
+     */
+    public function toSqliteType($type, $length = null) {
+        $type = strtolower($type);
+
+        if ($type === 'tinyint' && $length === 1) {
+            return 'BOOLEAN';
+        }
+
+        $sqliteType = 'TEXT';
+        foreach ($this->dbToSqlite as $key => $value) {
+            if (strpos($type, strtolower($key)) === 0) {
+                $sqliteType = $value;
+                break;
+            }
+        }
+
+        if (strpos(strtoupper($type), 'ENUM') !== false || strpos(strtoupper($type), 'SET') !== false) {
+            $parsedEnum = $this->parseEnumValue($length);
+            $sqliteType = 'NVARCHAR(' . ($parsedEnum['maxLength'] + 2) . ')';
+        } elseif (($sqliteType === 'NVARCHAR' || $sqliteType === 'INT') && $length > 0) {
+            $sqliteType .= "($length)";
+        }
+
+        return $sqliteType;
+    }
+
+    /**
+     * Converts a column type to the MySQL type format.
+     * @param string $type The original column type.
+     * @param int|null $length The column length (optional).
+     * @return string The converted MySQL column type.
+     */
+    public function toMySQLType($type, $length = null) {
+        $type = strtolower($type);
+        $mysqlType = 'TEXT';
+
+        if ($this->isTinyInt1($type, $length)) {
+            return 'TINYINT(1)';
+        }
+
+        if ($this->isInteger($type) && $length > 0) {
+            return "{$type}($length)";
+        }
+
+        foreach ($this->dbToMySQL as $key => $value) {
+            if (strpos($type, strtolower($key)) === 0) {
+                $mysqlType = $value;
+                break;
+            }
+        }
+
+        $mysqlType = str_replace('TIMESTAMPTZ', 'TIMESTAMP', $mysqlType);
+
+        if (strpos(strtoupper($type), 'ENUM') !== false) {
+            $parsedEnum = $this->parseEnumValue($length);
+            $mysqlType = 'ENUM(\'' . implode('\',\'', $parsedEnum['resultArray']) . '\')';
+        } elseif (strpos(strtoupper($type), 'SET') !== false) {
+            $parsedEnum = $this->parseEnumValue($length);
+            $mysqlType = 'SET(\'' . implode('\',\'', $parsedEnum['resultArray']) . '\')';
+        } elseif (strpos(strtoupper($type), 'DECIMAL') !== false) {
+            $parsedNumeric = $this->parseNumericType($length);
+            $mysqlType = 'DECIMAL(' . implode(', ', $parsedNumeric['resultArray']) . ')';
+        } elseif (strpos(strtoupper($type), 'NUMERIC') !== false) {
+            $parsedNumeric = $this->parseNumericType($length);
+            $mysqlType = 'NUMERIC(' . implode(', ', $parsedNumeric['resultArray']) . ')';
+        }
+
+        if (($mysqlType === 'VARCHAR' || $mysqlType === 'CHAR') && $length > 0) {
+            $mysqlType .= "($length)";
+        }
+
+        return $mysqlType;
+    }
+
+    /**
+     * Converts a column type to the PostgreSQL type format.
+     * @param string $type The original column type.
+     * @param int|null $length The column length (optional).
+     * @return string The converted PostgreSQL column type.
+     */
+    public function toPostgreSQLType($type, $length = null) {
+        $type = strtolower($type);
+        $pgType = 'TEXT';
+
+        foreach ($this->dbToPostgreSQL as $key => $value) {
+            if (strpos($type, strtolower($key)) === 0) {
+                $pgType = $value;
+                break;
+            }
+        }
+        $pgType = strtoupper($pgType);
+        if (strpos(strtoupper($type), 'TINYINT') !== false && $length == 1) {
+            $pgType = 'BOOLEAN';
+        } elseif (strpos(strtoupper($type), 'ENUM') !== false || strpos(strtoupper($type), 'SET') !== false) {
+            $parsedEnum = $this->parseEnumValue($length);
+            $pgType = 'CHARACTER VARYING(' . ($parsedEnum['maxLength'] + 2) . ')';
+        } elseif (($pgType === 'CHARACTER VARYING' || $pgType === 'CHARACTER' || $pgType === 'CHAR') && $length > 0) {
+            $pgType .= "($length)";
+        }
+
+        return $pgType;
+    }
+
+    /**
+     * Parses an ENUM type value and extracts the values in single quotes, also calculating the maximum length.
+     * @param string $inputString The ENUM values in a string format.
+     * @return array An associative array containing the result array and maximum length of ENUM values.
+     */
+    private function parseEnumValue($inputString) {
+        preg_match_all("/'([^']+)'/", $inputString, $matches);
+        $resultArray = $matches[1];
+        $maxLength = max(array_map('strlen', $resultArray));
+
+        return ['resultArray' => $resultArray, 'maxLength' => $maxLength];
+    }
+
+    /**
+     * Parses a numeric type value like DECIMAL(6,3), NUMERIC(10,2), etc.
+     * @param string $inputString The numeric value in string format, like 'DECIMAL(6, 3)'.
+     * @return array An associative array containing the type (e.g., DECIMAL) and the length (total digits) and scale (digits after the decimal point).
+     */
+    private function parseNumericType($inputString) {
+        preg_match_all("/([A-Za-z0-9_]+)/", $inputString, $matches); // NOSONAR
+        $resultArray = $matches[1];
+        $maxLength = max(array_map('strlen', $resultArray));
+
+        return ['resultArray' => $resultArray, 'maxLength' => $maxLength];
+    }
+
+    /**
+     * Converts the table schema into a SQL CREATE TABLE statement for the specified database.
+     * 
+     * This method generates a SQL CREATE TABLE statement based on the provided table schema and target database type,
+     * adapting column definitions, default values, and other relevant attributes.
+     * 
+     * @param array $table The table schema to convert.
+     * @param string $targetType The target database type ('mysql', 'pgsql', 'sqlite').
+     * @return string The SQL CREATE TABLE statement.
+     */
+    public function toTable($table, $targetType) {
+        $tableName = $table['tableName'];
+        
+        $lines = [];
+
+        // Fix table name if necessary
+        $tableName = $this->fixTableName($tableName, $targetType);
+        
+        $lines[] = "CREATE TABLE IF NOT EXISTS $tableName (";
+        $linesCol = [];
+
+        foreach ($table['columns'] as $column) {
+            $columnName = $this->fixColumnName($column['Field'], $targetType);
+            $columnType = $column['Type'];
+            $primaryKey = ($column['Field'] === $table['primaryKey']);
+            $colDef = "\t$columnName $columnType";
+
+            // Handle primary key
+            if ($primaryKey) {
+                $colDef .= " PRIMARY KEY";             
+                if ($this->isAutoIncrement($column['AutoIncrement'], $targetType)) {
+                    $colDef .= " AUTO_INCREMENT";
+                }
+                $column['Nullable'] = false;
+            }
+            // Handle nullability
+            else if ($column['Nullable']) {
+                $colDef .= " NULL";
+            } else {
+                $colDef .= " NOT NULL";
+            }
+
+            // Handle default values
+            $defaultValue = $column['Default'];
+            if ($this->hasDefaultValue($primaryKey, $defaultValue)) {
+                $defaultValue = $this->replaceAll($defaultValue, '::character varying', '');
+                $defaultValue = $this->fixDefaultValue($defaultValue, $targetType);
+                if ($this->isNotEmpty($defaultValue)) {
+                    $colDef .= $this->getDefaultData($defaultValue, $columnType);
+                }
+            }
+
+            // Handle column comments
+            if (isset($column['Comment'])) {
+                $colDef .= " COMMENT '" . $this->addslashes($column['Comment']) . "'";
+            }
+
+            $linesCol[] = $colDef;
+        }
+        
+        $lines[] = implode(",\r\n", $linesCol);
+        $lines[] = ");";
+        
+        return implode("\r\n", $lines);
+    }
+
+    
+
+    /**
+     * Checks if the column type is TINYINT with a length of 1.
+     *
+     * This method checks if the given column type is 'TINYINT' and if its length
+     * is exactly 1, which is commonly used to represent boolean values in certain databases.
+     *
+     * @param string $type The data type of the column (e.g., 'TINYINT').
+     * @param int $length The length of the column (e.g., 1).
+     * @return bool True if the type is 'TINYINT' and length is 1, otherwise false.
+     */
+    public function isTinyInt1($type, $length) {
+        return strtoupper($type) === 'TINYINT' && $length == 1;
+    }
+
+    /**
+     * Checks if the column type is an integer (e.g., TINYINT, SMALLINT, INT, BIGINT).
+     *
+     * @param string $type The column data type (e.g., 'INT', 'BIGINT').
+     * @return bool True if the column type is an integer type, otherwise false.
+     */
+    public function isInteger($type) {
+        $type = strtoupper($type);
+        return $type === 'TINYINT' ||
+               $type === 'SMALLINT' ||
+               $type === 'MEDIUMINT' ||
+               $type === 'BIGINT' ||
+               $type === 'INTEGER' ||
+               $type === 'INT';
+    }
+
+    /**
+     * Determines if a column is auto-incremented for MySQL databases.
+     *
+     * @param bool $autoIncrement Whether the column is set to auto-increment.
+     * @param string $targetType The target database type (e.g., 'mysql', 'mariadb').
+     * @return bool True if the column is auto-incremented in MySQL or MariaDB, otherwise false.
+     */
+    public function isAutoIncrement($autoIncrement, $targetType) {
+        return $this->isMySQL($targetType) && $autoIncrement;
+    }
+
+    /**
+     * Checks if a value is not empty (not null or an empty string).
+     *
+     * @param string $value The value to check.
+     * @return bool True if the value is not empty, otherwise false.
+     */
+    public function isNotEmpty($value) {
+        return $value !== null && $value !== '';
+    }
+
+    /**
+     * Determines if a column has a default value, excluding primary keys.
+     *
+     * @param bool $primaryKey Whether the column is a primary key.
+     * @param string $defaultValue The default value of the column.
+     * @return bool True if the column has a default value, otherwise false.
+     */
+    public function hasDefaultValue($primaryKey, $defaultValue) {
+        return !$primaryKey && $defaultValue !== null && $defaultValue !== '';
+    }
+
+    /**
+     * Fixes the table name according to the target database type.
+     * 
+     * This method adjusts the table name by removing any database prefix and applying
+     * the appropriate syntax for the target database (e.g., quoting for MySQL or PostgreSQL).
+     *
+     * @param string $tableName The name of the table to fix.
+     * @param string $targetType The target database type (e.g., 'mysql', 'pgsql').
+     * @return string The fixed table name.
+     */
+    public function fixTableName($tableName, $targetType) {
+        if (strpos($tableName, '.') !== false) {
+            $tableName = explode('.', $tableName)[1];
+        }
+        
+        if ($this->isMySQL($targetType)) {
+            $tableName = '`' . $tableName . '`';
+        } 
+        else if ($this->isPGSQL($targetType)) {
+            $tableName = '"' . $tableName . '"';
+        }
+        
+        return $tableName;
+    }
+
+    /**
+     * Fixes the column name according to the target database type.
+     * 
+     * This method applies proper quoting for column names based on the target database
+     * (e.g., MySQL uses backticks for column names).
+     *
+     * @param string $columnName The name of the column to fix.
+     * @param string $targetType The target database type (e.g., 'mysql').
+     * @return string The fixed column name.
+     */
+    public function fixColumnName($columnName, $targetType) {
+        if ($this->isMySQL($targetType)) {
+            $columnName = '`' . $columnName . '`';
+        }
+        
+        return $columnName;
+    }
+
+    /**
+     * Generates the default value SQL for a column based on its type.
+     * 
+     * This method returns the appropriate default value syntax for the column's type,
+     * handling different types such as BOOLEAN, INTEGER, and REAL.
+     *
+     * @param string $defaultValue The default value to apply to the column.
+     * @param string $columnType The type of the column (e.g., 'BOOLEAN', 'INT').
+     * @return string The default value SQL definition for the column.
+     */
+    public function getDefaultData($defaultValue, $columnType) {
+        $colDef = "";
+        
+        if (strtoupper($defaultValue) == 'NULL') {
+            $colDef .= ' DEFAULT NULL'; // NOSONAR
+        } 
+        else if ($this->isBoolean($columnType)) {
+            $colDef .= ' DEFAULT ' . $this->convertToBoolean($defaultValue); // NOSONAR
+        } 
+        else if (strpos(strtoupper($columnType), 'INT') !== false) {
+            $colDef .= ' DEFAULT ' . $this->convertToInteger($defaultValue); // NOSONAR
+        } 
+        else if ($this->isReal($columnType)) {
+            $colDef .= ' DEFAULT ' . $this->convertToReal($defaultValue); // NOSONAR
+        } 
+        else {
+            $colDef .= ' DEFAULT ' . $defaultValue;
+        }
+        
+        return $colDef;
+    }
+
+    /**
+     * Converts a value to a boolean format.
+     *
+     * @param mixed $value The value to convert.
+     * @return string The converted boolean value ('TRUE' or 'FALSE').
+     */
+    public function convertToBoolean($value) {
+        return (strtolower($value) == 'true' || $value === 1) ? 'TRUE' : 'FALSE';
+    }
+
+    /**
+     * Converts a value to an integer format.
+     *
+     * @param mixed $value The value to convert.
+     * @return int The converted integer value.
+     */
+    public function convertToInteger($value) {
+        return (int) $value;
+    }
+
+    /**
+     * Converts a value to a real number format.
+     *
+     * @param mixed $value The value to convert.
+     * @return float The converted real number value.
+     */
+    public function convertToReal($value) {
+        return (float) $value;
+    }
+
+    /**
+     * Fixes default value for SQLite.
+     * 
+     * @param string $defaultValue The default value to fix.
+     * @param string $targetType The target database type.
+     * @return string The fixed default value.
+     */
+    public function fixDefaultValue($defaultValue, $targetType) {
+        if ($this->isSQLite($targetType) && stripos($defaultValue, 'now(') !== false) {
+            $defaultValue = '';
+            
+        }
+        return $defaultValue;
+    }
+
+    /**
+     * Escapes special characters in a string for use in SQL statements.
+     *
+     * This method wraps the PHP `addslashes()` function, which adds backslashes before characters
+     * that need to be escaped in SQL, such as quotes, backslashes, and NULL characters.
+     *
+     * @param string $text The input string to escape.
+     * @return string The escaped string with special characters properly handled.
+     */
+    public function addslashes($text)
+    {
+        return addslashes($text);
+    }
+
+    /**
+     * Extracts the DROP TABLE IF EXISTS queries from the provided SQL string.
+     * 
+     * @param string $sql The SQL string to be processed.
+     * @param string $targetType The type of database ('pgsql', 'mysql', or 'mariadb') to format the table names accordingly.
+     * @return array An array of objects, each containing the name of a table to be dropped.
+     */
+    public function extractDropTableQueries($sql, $targetType) {
+
+        // Remove backticks (`) from the entire SQL string before processing
+        $sqlWithoutBackticks = str_replace('`', '', $sql);
+        $result = [];
+        try
+        {
+            // Regular expression to capture DROP TABLE IF EXISTS command
+            $regex = '/DROP\s+TABLE\s+IF\s+EXISTS\s+([^\s]+)/i';
+            preg_match_all($regex, $sqlWithoutBackticks, $matches);
+
+            // Loop through all matches found
+            foreach ($matches[1] as $match) {
+                // Store the result in the desired format
+                $tableName = $this->extractTableName($match);
+                
+                // Format the table name based on the target database type
+                if ($this->isPGSQL($targetType)) {
+                    $tableName = '"' . $tableName . '"';
+                } else if ($this->isMySQL($targetType)) {
+                    $tableName = '`' . $tableName . '`';
+                }
+                $result[] = [
+                    'table' => $tableName    // Table name
+                ];
+            }
+        }
+        catch(Exception $e)
+        {
+            // Do nothing
+        }
+
+        return $result;
+    }
+
+    /**
+     * Extracts the table name from the input string, removing schema if present.
+     * 
+     * @param string $input The input string (may contain schema.table or just table).
+     * @return string The extracted table name without schema.
+     */
+    public function extractTableName($input) {
+        // Check if the input contains a dot (indicating a schema)
+        if (strpos($input, '.') !== false) {
+            // If there is a dot, take the part after the dot as the table name
+            $input = explode('.', $input)[1];
+        }
+        // If there is no dot, it means the input is just the table name
+        return preg_replace('/[^a-zA-Z0-9_]/', '', $input); // NOSONAR
+    }
+
+    
+}
diff --git a/src/Util/Database/PicoTableParser.php b/src/Util/Database/PicoTableParser.php
new file mode 100644
index 00000000..bd0287ab
--- /dev/null
+++ b/src/Util/Database/PicoTableParser.php
@@ -0,0 +1,414 @@
+getResult();
+ * 
+ * The returned result contains structured information about each table, including
+ * columns, their types, constraints, and additional attributes.
+ * 
+ * @package MagicObject\Util\Database
+ */
+class PicoTableParser {
+
+    /**
+     * List of valid SQL data types used for column validation.
+     *
+     * @var array
+     */
+    private $typeList = array();
+
+    /**
+     * Holds information about the parsed tables, including columns and their properties.
+     *
+     * @var array
+     */
+    private $tableInfo = array();
+
+    /**
+     * PicoTableParser constructor.
+     *
+     * Initializes the PicoTableParser instance and optionally parses an SQL string.
+     * 
+     * @param string|null $sql Optional SQL string to parse during initialization.
+     */
+    public function __construct($sql = null) {
+        $this->init();
+
+        if ($sql) {
+            $this->parseAll($sql);
+        }
+    }
+
+    /**
+     * Initializes the type list for valid SQL column types.
+     *
+     * This function sets up an array of valid SQL column types, which will be used
+     * to validate column data types during parsing.
+     */
+    private function init() {
+        $typeList = 'TIMESTAMPTZ,TIMESTAMP,SERIAL4,BIGSERIAL,INT2,INT4,INT8,TINYINT,BIGINT,LONGTEXT,MEDIUMTEXT,TEXT,NVARCHAR,VARCHAR,ENUM,SET,NUMERIC,DECIMAL,CHAR,REAL,FLOAT,INTEGER,INT,DATETIME,DATE,DOUBLE,BOOLEAN,BOOL,TIME,UUID,MONEY,BLOB,BIT,JSON';
+        $this->typeList = explode(',', $typeList);
+    }
+
+    /**
+     * Checks if a value exists in an array.
+     *
+     * @param array $haystack The array to search.
+     * @param string $needle The value to search for.
+     * @return bool Returns true if the needle is found in the haystack, otherwise false.
+     */
+    private function inArray($haystack, $needle) {
+        return in_array($needle, $haystack);
+    }
+
+    /**
+     * Checks if a field is a primary key.
+     *
+     * @param string $field The field definition.
+     * @return bool True if the field is a primary key, otherwise false.
+     */
+    private function isPrimaryKey($field) {
+        $f = strtoupper(trim(preg_replace('/\s+/', ' ', $field))); // NOSONAR
+        return strpos($f, 'PRIMARY KEY') !== false;
+    }
+
+    /**
+     * Checks if a field is auto-incremented.
+     *
+     * @param string $line The field definition.
+     * @return bool True if the field is auto-incremented, otherwise false.
+     */
+    private function isAutoIncrement($line) {
+        $f = strtoupper(trim(preg_replace('/\s+/', ' ', $line)));
+        return strpos($f, 'AUTO_INCREMENT') !== false || 
+               strpos($f, 'SERIAL') !== false || 
+               strpos($f, 'BIGSERIAL') !== false || 
+               strpos($f, 'NEXTVAL') !== false;
+    }
+
+    /**
+     * Parses a CREATE TABLE SQL statement and extracts table and column information.
+     *
+     * @param string $sql The SQL string representing a CREATE TABLE statement.
+     * @return array An array containing table name, columns, and primary key information.
+     */
+    public function parseTable($sql) // NOSONAR
+    {
+        $rg_tb = '/(create\s+table\s+if\s+not\s+exists|create\s+table)\s(?.*)\s\(/i';
+        $rg_fld = '/(\w+\s+key.*|\w+\s+bigserial|\w+\s+serial4|\w+\s+serial8|\w+\s+tinyint.*|\w+\s+bigint.*|\w+\s+longtext.*|\w+\s+mediumtext.*|\w+\s+text.*|\w+\s+nvarchar.*|\w+\s+varchar.*|\w+\s+char.*|\w+\s+real.*|\w+\s+float.*|\w+\s+integer.*|\w+\s+int.*|\w+\s+datetime.*|\w+\s+date.*|\w+\s+double.*|\w+\s+timestamp.*|\w+\s+timestamptz.*|\w+\s+boolean.*|\w+\s+bool.*|\w+\s+enum\s*\(.*\)|\w+\s+set\s*\(.*\)|\w+\s+numeric\s*\(.*\)|\w+\s+decimal\s*\(.*\)|\w+\s+int2.*|\w+\s+int4.*|\w+\s+int8.*|\w+\s+time.*|\w+\s+uuid.*|\w+\s+money.*|\w+\s+blob.*|\w+\s+bit.*|\w+\s+json.*)/i'; // NOSONAR
+        $rg_fld2 = '/(?\w+)\s+(?\w+)(?.*)/i';
+        $rg_enum = '/enum\s*\(([^)]+)\)/i';
+        $rg_set = '/set\s*\(([^)]+)\)/i';
+        $rg_not_null = '/not\s+null/i';
+        $rg_pk = '/primary\s+key/i';
+        $rg_fld_def = '/default\s+([^\'"]+|\'[^\']*\'|\"[^\"]*\")\s*(comment\s+\'[^\']*\')?/i';
+        $rg_fld_comment = '/COMMENT\s*\'([^\']*)\'/i';
+        $rg_pk2 = '/(PRIMARY|UNIQUE) KEY[a-zA-Z_0-9\s]+\(([a-zA-Z_0-9,\s]+)\)/i'; // NOSONAR
+        
+        preg_match($rg_tb, $sql, $result);
+        $tableName = $result['tb'];
+
+        $fieldList = [];
+        $primaryKey = null;
+        $columnList = [];
+        $primaryKeyList = [];
+
+        preg_match_all($rg_fld, $sql, $matches);
+
+        foreach ($matches[0] as $f) {
+            $line = $f;
+            preg_match($rg_fld2, $f, $fld_def);
+            $dataTypeRaw = $fld_def[0];
+            $dataType = $fld_def['ftype'];
+            $dataTypeOriginal = $dataType;
+            $isPk = false;
+            $enumValues = null;
+            $enumArray = null;
+
+            if (preg_match($rg_enum, $dataTypeRaw, $matches)) {
+                $enumValues = $matches[1];
+                $enumArray = array_map('trim', explode(',', $enumValues));
+            }
+            if (preg_match($rg_set, $dataTypeRaw, $matches)) {
+                $enumValues = $matches[1];
+                $enumArray = array_map('trim', explode(',', $enumValues));
+            }
+
+            if ($this->isValidType($dataType) || $this->isValidType($dataTypeOriginal)) {
+                $attr = trim(str_replace(',', '', $fld_def['fattr']));
+                $nullable = !preg_match($rg_not_null, $attr);
+                $attr2 = str_replace($rg_not_null, '', $attr);
+                
+                $isPk = preg_match($rg_pk, $attr2) || $this->isPrimaryKey($line);
+                $isAi = $this->isAutoIncrement($line);
+
+                preg_match($rg_fld_def, $attr2, $def);
+                $defaultValue = isset($def[1]) ? trim($def[1]) : null;
+                $defaultValue = $this->fixDefaultValue($defaultValue);
+
+                preg_match($rg_fld_comment, $attr2, $cmn);
+                $comment = isset($cmn[1]) ? trim($cmn[1]) : null;
+
+                $dataType = trim($dataType);
+                $length = $this->getLength($attr);
+
+                $columnName = trim($fld_def['fname']);
+                if ($isPk) 
+                {
+                    $primaryKeyList[] = $columnName;
+                }
+                if (!in_array($columnName, $columnList)) {
+                    $fieldList[] = [
+                        'Field' => $columnName,
+                        'Type' => $dataType,
+                        'Length' => $length,
+                        'Key' => $isPk,
+                        'Nullable' => $nullable,
+                        'Default' => $defaultValue,
+                        'AutoIncrement' => $isAi,
+                        'EnumValues' => $enumArray,
+                        'Comment' => $comment
+                    ];
+                    $columnList[] = $columnName;
+                }
+            } else if ($this->isPrimaryKey($line)) {
+                preg_match('/\((.*)\)/', $f, $matches);
+                if ($primaryKey == null) {
+                    $primaryKey = isset($matches[1]) ? $matches[1] : null;
+                }
+            }
+
+            if ($primaryKey != null) {
+                $primaryKey = str_replace(['(', ')'], '', $primaryKey);
+                foreach ($fieldList as &$column) // NOSONAR
+                {
+                    if ($column['Field'] == $primaryKey) {
+                        $column['Key'] = true;
+                    }
+                }
+            }
+
+            if (preg_match($rg_pk2, $f) && preg_match($rg_pk, $f)) {
+                $x = str_replace(preg_match($rg_pk, $f)[0], '', $f);
+                $x = str_replace(['(', ')'], '', $x);
+                $pkeys = array_map('trim', explode(',', $x));
+                foreach ($fieldList as &$column) {
+                    if (in_array($column['Field'], $pkeys)) {
+                        $column['Key'] = true;
+                    }
+                }
+            }
+        }
+
+        if ($primaryKey == null) {
+            $primaryKey = $primaryKeyList[0] ?? null;
+        }
+
+        return ['tableName' => $tableName, 'columns' => $fieldList, 'primaryKey' => $primaryKey];
+    }
+
+    /**
+     * Fixes and normalizes default values in SQL statements.
+     *
+     * @param string $defaultValue The raw default value from SQL.
+     * @return string|null The normalized default value or null if empty.
+     */
+    private function fixDefaultValue($defaultValue) {
+        $defaultValue = trim($defaultValue);
+        return empty($defaultValue) || $defaultValue == 'null' ? null : $defaultValue;
+    }
+
+    /**
+     * Validates if a type is one of the known types.
+     *
+     * @param string $type The data type to validate.
+     * @return bool True if the type is valid, otherwise false.
+     */
+    private function isValidType($type) {
+        return $this->inArray($this->typeList, $type);
+    }
+
+    /**
+     * Extracts the length for types that require it, such as varchar.
+     *
+     * @param string $attr The attributes of the field.
+     * @return int|null The length of the field or null if not applicable.
+     */
+    private function getLength($attr) {
+        preg_match('/\((\d+)\)/', $attr, $matches);
+        return isset($matches[1]) ? (int) trim($matches[1], '()') : null;
+    }
+
+    /**
+     * Parses multiple SQL statements.
+     *
+     * @param string $sql The SQL statements.
+     */
+    public function parseAll($sql) {
+        
+        $tables = $this->parseSQL($sql);
+
+        foreach ($tables as $table) {
+            if (isset($table['query'])) {
+                try
+                {
+                    $info = $this->parseTable($table['query']);
+
+                    $this->tableInfo[] = $info;
+                }
+                catch(Exception $e)
+                {
+                    // Do nothing
+                }
+            }
+        }
+    }
+
+    /**
+     * Parses a SQL text, splits it into individual SQL queries, and processes them by handling 
+     * delimiters, comments, and empty lines. It returns an array of queries, each with its delimiter.
+     *
+     * This method processes the SQL text by:
+     * 1. Normalizing newlines.
+     * 2. Removing comment lines and empty lines.
+     * 3. Splitting the SQL text into queries based on the delimiter.
+     * 4. Handling multiple queries that may span across multiple lines.
+     * 5. Changing the delimiter if a new one is specified within the SQL text.
+     * 
+     * @param string $sqlText The input SQL text to be parsed.
+     * 
+     * @return array An array of queries, each containing the SQL query string and its corresponding delimiter.
+     *               Each item in the array is an associative array with two keys:
+     *               - 'query': the SQL query string.
+     *               - 'delimiter': the delimiter used for that query (e.g., ";").
+     */
+    public function parseSQL($sqlText)  // NOSONAR
+    {
+        $sqlText = str_replace("\n", "\r\n", $sqlText);
+        $sqlText = str_replace("\r\r\n", "\r\n", $sqlText);
+        $arr = explode("\r\n", $sqlText);
+        $arr2 = array();
+        foreach($arr as $key=>$val)
+        {
+            $arr[$key] = ltrim($val);
+            if (stripos($arr[$key], "-- ") !== 0 && $arr[$key] !== "--" && $arr[$key] !== "") {
+                $arr2[] = $arr[$key];
+            }
+        }
+        $arr = $arr2;
+        unset($arr2);
+        
+        $append = 0;
+        $skip = 0;
+        $start = 1;
+        $nquery = -1;
+        $delimiter = ";";
+        $query_array = array();
+        $delimiter_array = array();
+        
+        foreach($arr as $line=>$text)
+        {
+            if($text == "" && $append == 1)
+            {
+                $query_array[$nquery] .= "\r\n";
+            }
+            if($append == 0)
+            {
+                if(stripos(ltrim($text, " \t "), "--") === 0)
+                {
+                    $skip = 1;
+                    $nquery++;
+                    $start = 1;
+                    $append = 0;
+                }
+                else
+                {
+                    $skip = 0;
+                }
+            }
+            if($skip == 0)
+            {
+                if($start == 1)
+                {
+                    $nquery++;
+                    $query_array[$nquery] = "";
+                    $delimiter_array[$nquery] = $delimiter;
+                    $start = 0;
+                }
+                $query_array[$nquery] .= $text."\r\n";
+                $delimiter_array[$nquery] = $delimiter;
+                $text = ltrim($text, " \t ");
+                $start = strlen($text)-strlen($delimiter)-1;
+                if(stripos(substr($text, $start), $delimiter) !== false || $text == $delimiter)
+                {
+                    $nquery++;
+                    $start = 1;
+                    $append = 0;
+                }
+                else
+                {
+                    $start = 0;
+                    $append = 1;
+                }
+                $delimiter_array[$nquery] = $delimiter;
+                if(stripos($text, "delimiter ") !== false)
+                {
+                    $text = trim(preg_replace("/\s+/"," ",$text));
+                    $arr2 = explode(" ", $text);
+                    $delimiter = $arr2[1];
+                    $nquery++;
+                    $delimiter_array[$nquery] = $delimiter;
+                    $start = 1;
+                    $append = 0;
+                }
+            }
+        }
+        $result = array();
+        foreach($query_array as $line=>$sql)
+        {
+            $delimiter = $delimiter_array[$line];
+            if (stripos($sql, "delimiter ") !== 0) {
+                $sql = rtrim($sql, " \r\n\t ");
+                $sql = substr($sql, 0, strlen($sql) - strlen($delimiter));			
+                $result[] = array("query" => $sql, "delimiter" => $delimiter);
+            }
+        }
+        return $result;
+    }
+
+    /**
+     * Returns the table information parsed from the SQL statements.
+     *
+     * @return array An array of parsed table information.
+     */
+    public function getTableInfo() {
+        return $this->tableInfo;
+    }
+
+     /**
+     * Get the value of tableInfo.
+     *
+     * @return array The table information.
+     */ 
+    public function getResult()
+    {
+        return $this->getTableInfo();
+    }
+}
diff --git a/tutorial.md b/tutorial.md
index d9f6186c..b3e1bb02 100644
--- a/tutorial.md
+++ b/tutorial.md
@@ -2294,16 +2294,37 @@ Session variables keep information about one single user, and are available to a
 
 ```yaml
 session:
-  name: MUSICPRODUCTIONMANAGER
-  max_life_time: 86400
-  save_handler: files
-  save_path: /tmp/sessions
+  name: ${SESSION_NAME}
+  max_life_time: ${SESSION_MAX_LIFE_TIME}
+  save_handler: ${SESSION_SAVE_HANDLER}
+  save_path: ${SESSION_SAVE_PATH}
+  cookie_lifetime: ${SESSION_COOKIE_LIFETIME}
+  cookie_path: ${SESSION_COOKIE_PATH}
+  cookie_secure: ${SESSION_COOKIE_SECURE}
+  cookie_httponly: ${SESSION_COOKIE_HTTPONLY}
+  cookie_domain: ${SESSION_COOKIE_DOMAIN}
 ```
 
-- `name`: Name of the session.
-- `max_life_time`: Maximum lifetime of the session in seconds (e.g., 86400 seconds = 24 hours).
-- `save_handler`: Specifies the session storage mechanism (in this case, files).
-- `save_path`: Directory where session files are stored.
+### Explanation:
+  
+-   **name**: Specifies the name of the session. The value `${SESSION_NAME}` refers to an environment variable or a parameter that should be defined elsewhere in the application or server environment. It determines the session's name identifier.
+    
+-   **max_life_time**: Defines the maximum lifetime of the session in seconds. The value `${SESSION_MAX_LIFE_TIME}` is expected to come from an environment variable or configuration setting, controlling how long the session will last before it expires.
+    
+-   **save_handler**: Specifies the handler used to save session data. In this case, `${SESSION_SAVE_HANDLER}` likely refers to the session handler, such as `redis`, `files`, or `database`, depending on the server configuration.
+    
+-   **save_path**: Defines the path where session data will be stored. The value `${SESSION_SAVE_PATH}` is an environment variable or configuration parameter, which could specify a directory or Redis connection string (if `redis` is used as the save handler).
+    
+-   **cookie_lifetime**: Specifies the lifetime of the session cookie in seconds, which controls how long the cookie will persist in the user's browser. `${SESSION_COOKIE_LIFETIME}` is the environment variable or configuration value that sets this duration.
+    
+-   **cookie_path**: The path on the server where the session cookie is valid. `${SESSION_COOKIE_PATH}` determines the directory or URL prefix for which the session cookie is set.
+    
+-   **cookie_secure**: A boolean flag that determines whether the session cookie should be transmitted over HTTPS only. If `${SESSION_COOKIE_SECURE}` is set to `true`, the cookie will only be sent over secure connections.
+    
+-   **cookie_httponly**: A boolean flag that indicates whether the session cookie is accessible only through the HTTP protocol (i.e., not accessible via JavaScript). If `${SESSION_COOKIE_HTTPONLY}` is `true`, it increases security by preventing client-side access to the cookie.
+    
+-   **cookie_domain**: Specifies the domain to which the session cookie is valid. `${SESSION_COOKIE_DOMAIN}` can define the domain for which the session cookie should be sent, allowing cross-subdomain access to the session.
+
 
 **PHP Script**
 
@@ -2354,10 +2375,15 @@ For example:
 
 ```yaml
 session:
-  name: MUSICPRODUCTIONMANAGER
-  max_life_time: 86400
-  save_handler: redis
+  name: ${SESSION_NAME}
+  max_life_time: ${SESSION_MAX_LIFE_TIME}
+  save_handler: ${SESSION_SAVE_HANDLER}
   save_path: ${SESSION_SAVE_PATH}
+  cookie_lifetime: ${SESSION_COOKIE_LIFETIME}
+  cookie_path: ${SESSION_COOKIE_PATH}
+  cookie_secure: ${SESSION_COOKIE_SECURE}
+  cookie_httponly: ${SESSION_COOKIE_HTTPONLY}
+  cookie_domain: ${SESSION_COOKIE_DOMAIN}
 ```
 
 `${SESSION_SAVE_PATH}` contains entire of `save_path` that encrypted with you secure key.