├── src ├── Definition │ ├── Table │ │ ├── Indexes │ │ │ ├── Keys │ │ │ │ ├── PrimaryKey.php │ │ │ │ ├── Key.php │ │ │ │ ├── UniqueKey.php │ │ │ │ ├── SpatialKey.php │ │ │ │ ├── FulltextKey.php │ │ │ │ ├── ConstraintKey.php │ │ │ │ └── ForeignKey.php │ │ │ ├── Index.php │ │ │ └── IndexDefinition.php │ │ ├── Columns │ │ │ ├── Numeric │ │ │ │ ├── BigintColumn.php │ │ │ │ ├── IntColumn.php │ │ │ │ ├── TinyintColumn.php │ │ │ │ ├── BooleanColumn.php │ │ │ │ ├── BitColumn.php │ │ │ │ ├── SmallintColumn.php │ │ │ │ ├── MediumintColumn.php │ │ │ │ ├── FloatColumn.php │ │ │ │ ├── DecimalColumn.php │ │ │ │ └── NumericDataType.php │ │ │ ├── String │ │ │ │ ├── JsonColumn.php │ │ │ │ ├── LongtextColumn.php │ │ │ │ ├── TinytextColumn.php │ │ │ │ ├── MediumtextColumn.php │ │ │ │ ├── VarcharColumn.php │ │ │ │ ├── BlobColumn.php │ │ │ │ ├── CharColumn.php │ │ │ │ ├── LongblobColumn.php │ │ │ │ ├── TextColumn.php │ │ │ │ ├── TinyblobColumn.php │ │ │ │ ├── MediumblobColumn.php │ │ │ │ ├── EnumColumn.php │ │ │ │ ├── SetColumn.php │ │ │ │ ├── VarbinaryColumn.php │ │ │ │ ├── BinaryColumn.php │ │ │ │ └── StringDataType.php │ │ │ ├── DateTime │ │ │ │ ├── DateColumn.php │ │ │ │ ├── TimeColumn.php │ │ │ │ ├── YearColumn.php │ │ │ │ ├── DatetimeColumn.php │ │ │ │ └── TimestampColumn.php │ │ │ ├── Geometry │ │ │ │ ├── PointColumn.php │ │ │ │ ├── PolygonColumn.php │ │ │ │ ├── GeometryColumn.php │ │ │ │ ├── LinestringColumn.php │ │ │ │ ├── MultipointColumn.php │ │ │ │ ├── MultipolygonColumn.php │ │ │ │ ├── MultilinestringColumn.php │ │ │ │ └── GeometryCollectionColumn.php │ │ │ ├── Traits │ │ │ │ ├── ListLength.php │ │ │ │ └── DecimalLength.php │ │ │ ├── Column.php │ │ │ └── ColumnDefinition.php │ │ ├── DefinitionPart.php │ │ ├── Constraint.php │ │ ├── Check.php │ │ ├── TableDefinition.php │ │ └── TableStatement.php │ ├── DropSchema.php │ ├── CreateSchema.php │ ├── CreateTable.php │ ├── DropTable.php │ └── AlterSchema.php ├── Debug │ ├── DatabaseCollection.php │ ├── icons │ │ └── database.svg │ └── DatabaseCollector.php ├── Manipulation │ ├── Traits │ │ ├── Select.php │ │ ├── Explain.php │ │ ├── Values.php │ │ ├── Set.php │ │ ├── OrderBy.php │ │ └── GroupBy.php │ ├── With.php │ ├── Delete.php │ ├── Update.php │ ├── Replace.php │ ├── Statement.php │ ├── Insert.php │ └── LoadData.php ├── Statement.php ├── Result │ ├── Explain.php │ └── Field.php ├── PreparedStatement.php └── Result.php ├── README.md ├── LICENSE ├── composer.json └── .phpstorm.meta.php /src/Definition/Table/Indexes/Keys/PrimaryKey.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Indexes\Keys; 11 | 12 | /** 13 | * Class PrimaryKey. 14 | * 15 | * @package database 16 | */ 17 | final class PrimaryKey extends ConstraintKey 18 | { 19 | protected string $type = 'PRIMARY KEY'; 20 | } 21 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Numeric/BigintColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Numeric; 11 | 12 | /** 13 | * Class BigintColumn. 14 | * 15 | * @package database 16 | */ 17 | final class BigintColumn extends NumericDataType 18 | { 19 | protected string $type = 'bigint'; 20 | } 21 | -------------------------------------------------------------------------------- /src/Definition/Table/Indexes/Keys/Key.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Indexes\Keys; 11 | 12 | use Framework\Database\Definition\Table\Indexes\Index; 13 | 14 | /** 15 | * Class Key. 16 | * 17 | * @package database 18 | */ 19 | final class Key extends Index 20 | { 21 | protected string $type = 'KEY'; 22 | } 23 | -------------------------------------------------------------------------------- /src/Debug/DatabaseCollection.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Debug; 11 | 12 | use Framework\Debug\Collection; 13 | 14 | /** 15 | * Class DatabaseCollection. 16 | * 17 | * @package database 18 | */ 19 | class DatabaseCollection extends Collection 20 | { 21 | protected string $iconPath = __DIR__ . '/icons/database.svg'; 22 | } 23 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Numeric/IntColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Numeric; 11 | 12 | /** 13 | * Class IntColumn. 14 | * 15 | * @package database 16 | */ 17 | final class IntColumn extends NumericDataType 18 | { 19 | protected string $type = 'int'; 20 | protected int $maxLength = 11; 21 | } 22 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Numeric/TinyintColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Numeric; 11 | 12 | /** 13 | * Class TinyintColumn. 14 | * 15 | * @package database 16 | */ 17 | final class TinyintColumn extends NumericDataType 18 | { 19 | protected string $type = 'tinyint'; 20 | protected int $maxLength = 127; 21 | } 22 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/JsonColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | /** 13 | * Class JsonColumn. 14 | * 15 | * @see https://mariadb.com/kb/en/json-data-type/ 16 | * 17 | * @package database 18 | */ 19 | final class JsonColumn extends StringDataType 20 | { 21 | protected string $type = 'json'; 22 | } 23 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Numeric/BooleanColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Numeric; 11 | 12 | /** 13 | * Class BooleanColumn. 14 | * 15 | * @see https://mariadb.com/kb/en/boolean/ 16 | * 17 | * @package database 18 | */ 19 | final class BooleanColumn extends NumericDataType 20 | { 21 | protected string $type = 'boolean'; 22 | } 23 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/LongtextColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | /** 13 | * Class LongtextColumn. 14 | * 15 | * @see https://mariadb.com/kb/en/longtext/ 16 | * 17 | * @package database 18 | */ 19 | final class LongtextColumn extends StringDataType 20 | { 21 | protected string $type = 'longtext'; 22 | } 23 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/TinytextColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | /** 13 | * Class TinytextColumn. 14 | * 15 | * @see https://mariadb.com/kb/en/tinytext/ 16 | * 17 | * @package database 18 | */ 19 | final class TinytextColumn extends StringDataType 20 | { 21 | protected string $type = 'tinytext'; 22 | } 23 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/MediumtextColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | /** 13 | * Class MediumtextColumn. 14 | * 15 | * @see https://mariadb.com/kb/en/mediumtext/ 16 | * 17 | * @package database 18 | */ 19 | final class MediumtextColumn extends StringDataType 20 | { 21 | protected string $type = 'mediumtext'; 22 | } 23 | -------------------------------------------------------------------------------- /src/Definition/Table/Indexes/Keys/UniqueKey.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Indexes\Keys; 11 | 12 | /** 13 | * Class UniqueKey. 14 | * 15 | * @see https://mariadb.com/kb/en/getting-started-with-indexes/#unique-index 16 | * 17 | * @package database 18 | */ 19 | final class UniqueKey extends ConstraintKey 20 | { 21 | protected string $type = 'UNIQUE KEY'; 22 | } 23 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Numeric/BitColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Numeric; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class BitColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/bit/ 18 | * 19 | * @package database 20 | */ 21 | final class BitColumn extends Column 22 | { 23 | protected string $type = 'bit'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/VarcharColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | /** 13 | * Class VarcharColumn. 14 | * 15 | * @see https://mariadb.com/kb/en/varchar/ 16 | * 17 | * @package database 18 | */ 19 | final class VarcharColumn extends StringDataType 20 | { 21 | protected string $type = 'varchar'; 22 | protected int $maxLength = 65535; 23 | } 24 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/DateTime/DateColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\DateTime; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class EnumColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/date/ 18 | * 19 | * @package database 20 | */ 21 | final class DateColumn extends Column 22 | { 23 | protected string $type = 'date'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/DateTime/TimeColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\DateTime; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class TimeColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/time/ 18 | * 19 | * @package database 20 | */ 21 | final class TimeColumn extends Column 22 | { 23 | protected string $type = 'time'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/BlobColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class BlobColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/blob/ 18 | * 19 | * @package database 20 | */ 21 | final class BlobColumn extends Column 22 | { 23 | protected string $type = 'blob'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Geometry/PointColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Geometry; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class PointColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/point/ 18 | * 19 | * @package database 20 | */ 21 | final class PointColumn extends Column 22 | { 23 | protected string $type = 'point'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Numeric/SmallintColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Numeric; 11 | 12 | /** 13 | * Class SmallintColumn. 14 | * 15 | * @see https://mariadb.com/kb/en/smallint/ 16 | * 17 | * @package database 18 | */ 19 | final class SmallintColumn extends NumericDataType 20 | { 21 | protected string $type = 'smallint'; 22 | protected int $maxLength = 127; 23 | } 24 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/DateTime/YearColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\DateTime; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class YearColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/year-data-type/ 18 | * 19 | * @package database 20 | */ 21 | final class YearColumn extends Column 22 | { 23 | protected string $type = 'year'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Numeric/MediumintColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Numeric; 11 | 12 | /** 13 | * Class MediumintColumn. 14 | * 15 | * @see https://mariadb.com/kb/en/mediumint/ 16 | * 17 | * @package database 18 | */ 19 | final class MediumintColumn extends NumericDataType 20 | { 21 | protected string $type = 'mediumint'; 22 | protected int $maxLength = 127; 23 | } 24 | -------------------------------------------------------------------------------- /src/Definition/Table/Indexes/Keys/SpatialKey.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Indexes\Keys; 11 | 12 | use Framework\Database\Definition\Table\Indexes\Index; 13 | 14 | /** 15 | * Class SpatialKey. 16 | * 17 | * @see https://mariadb.com/kb/en/spatial-index/ 18 | * 19 | * @package database 20 | */ 21 | final class SpatialKey extends Index 22 | { 23 | protected string $type = 'SPATIAL KEY'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Geometry/PolygonColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Geometry; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class PolygonColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/polygon/ 18 | * 19 | * @package database 20 | */ 21 | final class PolygonColumn extends Column 22 | { 23 | protected string $type = 'polygon'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/DateTime/DatetimeColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\DateTime; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class DatetimeColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/datetime/ 18 | * 19 | * @package database 20 | */ 21 | final class DatetimeColumn extends Column 22 | { 23 | protected string $type = 'datetime'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Geometry/GeometryColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Geometry; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class GeometryColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/geometry/ 18 | * 19 | * @package database 20 | */ 21 | final class GeometryColumn extends Column 22 | { 23 | protected string $type = 'geometry'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/CharColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | /** 13 | * Class CharColumn. 14 | * 15 | * @see https://mariadb.com/kb/en/char/ 16 | * 17 | * @package database 18 | */ 19 | final class CharColumn extends StringDataType 20 | { 21 | protected string $type = 'char'; 22 | protected int $minLength = 0; 23 | protected int $maxLength = 255; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/LongblobColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class LongblobColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/longblob/ 18 | * 19 | * @package database 20 | */ 21 | final class LongblobColumn extends Column 22 | { 23 | protected string $type = 'longblob'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/TextColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | /** 13 | * Class TextColumn. 14 | * 15 | * @see https://mariadb.com/kb/en/text/ 16 | * 17 | * @package database 18 | */ 19 | final class TextColumn extends StringDataType 20 | { 21 | protected string $type = 'text'; 22 | protected int $minLength = 0; 23 | protected int $maxLength = 65535; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/TinyblobColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class TinyblobColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/tinyblob/ 18 | * 19 | * @package database 20 | */ 21 | final class TinyblobColumn extends Column 22 | { 23 | protected string $type = 'tinyblob'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/DateTime/TimestampColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\DateTime; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class TimestampColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/timestamp/ 18 | * 19 | * @package database 20 | */ 21 | final class TimestampColumn extends Column 22 | { 23 | protected string $type = 'timestamp'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Indexes/Keys/FulltextKey.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Indexes\Keys; 11 | 12 | use Framework\Database\Definition\Table\Indexes\Index; 13 | 14 | /** 15 | * Class FulltextKey. 16 | * 17 | * @see https://mariadb.com/kb/en/full-text-index-overview/ 18 | * 19 | * @package database 20 | */ 21 | final class FulltextKey extends Index 22 | { 23 | protected string $type = 'FULLTEXT KEY'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Geometry/LinestringColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Geometry; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class LinestringColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/linestring/ 18 | * 19 | * @package database 20 | */ 21 | final class LinestringColumn extends Column 22 | { 23 | protected string $type = 'linestring'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Geometry/MultipointColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Geometry; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class MultipointColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/multipoint/ 18 | * 19 | * @package database 20 | */ 21 | final class MultipointColumn extends Column 22 | { 23 | protected string $type = 'multipoint'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/MediumblobColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class MediumblobColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/mediumblob/ 18 | * 19 | * @package database 20 | */ 21 | final class MediumblobColumn extends Column 22 | { 23 | protected string $type = 'mediumblob'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Geometry/MultipolygonColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Geometry; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class MultipolygonColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/multipolygon/ 18 | * 19 | * @package database 20 | */ 21 | final class MultipolygonColumn extends Column 22 | { 23 | protected string $type = 'multipolygon'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/EnumColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | use Framework\Database\Definition\Table\Columns\Traits\ListLength; 13 | 14 | /** 15 | * Class EnumColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/enum/ 18 | * 19 | * @package database 20 | */ 21 | final class EnumColumn extends StringDataType 22 | { 23 | use ListLength; 24 | protected string $type = 'enum'; 25 | } 26 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/SetColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | use Framework\Database\Definition\Table\Columns\Traits\ListLength; 13 | 14 | /** 15 | * Class SetColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/set-data-type/ 18 | * 19 | * @package database 20 | */ 21 | final class SetColumn extends StringDataType 22 | { 23 | use ListLength; 24 | protected string $type = 'set'; 25 | } 26 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Geometry/MultilinestringColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Geometry; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class MultilinestringColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/multilinestring/ 18 | * 19 | * @package database 20 | */ 21 | final class MultilinestringColumn extends Column 22 | { 23 | protected string $type = 'multilinestring'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/VarbinaryColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class VarbinaryColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/varbinary/ 18 | * 19 | * @package database 20 | */ 21 | final class VarbinaryColumn extends Column 22 | { 23 | protected string $type = 'varbinary'; 24 | protected int $maxLength = 65535; 25 | } 26 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Geometry/GeometryCollectionColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Geometry; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class GeometryCollectionColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/geometrycollection/ 18 | * 19 | * @package database 20 | */ 21 | final class GeometryCollectionColumn extends Column 22 | { 23 | protected string $type = 'geometrycollection'; 24 | } 25 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Numeric/FloatColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Numeric; 11 | 12 | use Framework\Database\Definition\Table\Columns\Traits\DecimalLength; 13 | 14 | /** 15 | * Class FloatColumn. 16 | * 17 | * @package database 18 | */ 19 | final class FloatColumn extends NumericDataType 20 | { 21 | use DecimalLength; 22 | protected string $type = 'float'; 23 | protected int $maxLength = 11; 24 | protected float $decimal; 25 | } 26 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Numeric/DecimalColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Numeric; 11 | 12 | use Framework\Database\Definition\Table\Columns\Traits\DecimalLength; 13 | 14 | /** 15 | * Class DecimalColumn. 16 | * 17 | * @package database 18 | */ 19 | final class DecimalColumn extends NumericDataType 20 | { 21 | use DecimalLength; 22 | protected string $type = 'decimal'; 23 | protected int $maxLength = 11; 24 | protected float $decimal; 25 | } 26 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/BinaryColumn.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class BinaryColumn. 16 | * 17 | * @see https://mariadb.com/kb/en/binary/ 18 | * 19 | * @package database 20 | */ 21 | final class BinaryColumn extends Column 22 | { 23 | protected string $type = 'binary'; 24 | protected int $minLength = 0; 25 | protected int $maxLength = 255; 26 | } 27 | -------------------------------------------------------------------------------- /src/Debug/icons/database.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/Definition/Table/Indexes/Keys/ConstraintKey.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Indexes\Keys; 11 | 12 | use Framework\Database\Definition\Table\Constraint; 13 | use Framework\Database\Definition\Table\Indexes\Index; 14 | 15 | /** 16 | * Class ConstraintKey. 17 | * 18 | * @package database 19 | */ 20 | abstract class ConstraintKey extends Index 21 | { 22 | use Constraint; 23 | 24 | protected function renderType() : string 25 | { 26 | return $this->renderConstraint() . parent::renderType(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Traits/ListLength.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Traits; 11 | 12 | /** 13 | * Trait ListLength. 14 | * 15 | * @package database 16 | */ 17 | trait ListLength 18 | { 19 | protected function renderLength() : ?string 20 | { 21 | if (empty($this->length)) { 22 | return null; 23 | } 24 | $values = []; 25 | foreach ($this->length as $length) { 26 | $values[] = $this->database->quote($length); 27 | } 28 | $values = \implode(', ', $values); 29 | return "({$values})"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Traits/DecimalLength.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Traits; 11 | 12 | /** 13 | * Trait DecimalLength. 14 | * 15 | * @package database 16 | */ 17 | trait DecimalLength 18 | { 19 | protected function renderLength() : ?string 20 | { 21 | if (!isset($this->length[0])) { 22 | return null; 23 | } 24 | $maximum = $this->database->quote($this->length[0]); 25 | if (isset($this->length[1])) { 26 | $decimals = $this->database->quote($this->length[1]); 27 | $maximum .= ",{$decimals}"; 28 | } 29 | return "({$maximum})"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Definition/Table/DefinitionPart.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table; 11 | 12 | use BadMethodCallException; 13 | 14 | /** 15 | * Class DefinitionPart. 16 | * 17 | * @package database 18 | */ 19 | abstract class DefinitionPart 20 | { 21 | /** 22 | * @param string $method 23 | * @param array $arguments 24 | * 25 | * @return mixed 26 | */ 27 | public function __call(string $method, array $arguments) : mixed 28 | { 29 | if ($method === 'sql') { 30 | return $this->sql(...$arguments); 31 | } 32 | throw new BadMethodCallException("Method not found or not allowed: {$method}"); 33 | } 34 | 35 | abstract protected function sql() : string; 36 | } 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Aplus Framework Database Library 2 | 3 | # Aplus Framework Database Library 4 | 5 | - [Home](https://aplus-framework.com/packages/database) 6 | - [User Guide](https://docs.aplus-framework.com/guides/libraries/database/index.html) 7 | - [API Documentation](https://docs.aplus-framework.com/packages/database.html) 8 | 9 | [![tests](https://github.com/aplus-framework/database/actions/workflows/tests.yml/badge.svg)](https://github.com/aplus-framework/database/actions/workflows/tests.yml) 10 | [![coverage](https://coveralls.io/repos/github/aplus-framework/database/badge.svg?branch=master)](https://coveralls.io/github/aplus-framework/database?branch=master) 11 | [![packagist](https://img.shields.io/packagist/v/aplus/database)](https://packagist.org/packages/aplus/database) 12 | [![open-source](https://img.shields.io/badge/open--source-sponsor-magenta)](https://aplus-framework.com/sponsor) 13 | -------------------------------------------------------------------------------- /src/Definition/Table/Constraint.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table; 11 | 12 | /** 13 | * Trait Constraint. 14 | * 15 | * @package database 16 | */ 17 | trait Constraint 18 | { 19 | protected ?string $constraint = null; 20 | 21 | /** 22 | * @param string $name 23 | * 24 | * @return static 25 | */ 26 | public function constraint(string $name) : static 27 | { 28 | $this->constraint = $name; 29 | return $this; 30 | } 31 | 32 | protected function renderConstraint() : ?string 33 | { 34 | if ($this->constraint === null) { 35 | return null; 36 | } 37 | $constraint = $this->database->protectIdentifier($this->constraint); 38 | return " CONSTRAINT {$constraint}"; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Natan Felles 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/Definition/Table/Check.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table; 11 | 12 | use Closure; 13 | use Framework\Database\Database; 14 | 15 | /** 16 | * Class Check. 17 | * 18 | * @see https://mariadb.com/kb/en/constraint/#check-constraints 19 | * 20 | * @package database 21 | */ 22 | class Check extends DefinitionPart 23 | { 24 | use Constraint; 25 | 26 | protected Database $database; 27 | protected Closure $check; 28 | 29 | public function __construct(Database $database, Closure $check) 30 | { 31 | $this->database = $database; 32 | $this->check = $check; 33 | } 34 | 35 | protected function renderCheck() : ?string 36 | { 37 | return ' CHECK (' . ($this->check)($this->database) . ')'; 38 | } 39 | 40 | protected function sql() : string 41 | { 42 | $sql = $this->renderConstraint(); 43 | $sql .= $this->renderCheck(); 44 | return $sql; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Manipulation/Traits/Select.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Manipulation\Traits; 11 | 12 | use Closure; 13 | use Framework\Database\Manipulation\Select as SelectStatement; 14 | use LogicException; 15 | 16 | /** 17 | * Trait Select. 18 | * 19 | * @package database 20 | */ 21 | trait Select 22 | { 23 | /** 24 | * Sets the SELECT statement part. 25 | * 26 | * @param Closure $select 27 | * 28 | * @see https://mariadb.com/kb/en/insert-select/ 29 | * 30 | * @return static 31 | */ 32 | public function select(Closure $select) : static 33 | { 34 | $this->sql['select'] = $select(new SelectStatement($this->database)); 35 | return $this; 36 | } 37 | 38 | protected function renderSelect() : ?string 39 | { 40 | if (!isset($this->sql['select'])) { 41 | return null; 42 | } 43 | if (isset($this->sql['values'])) { 44 | throw new LogicException('SELECT statement is not allowed when VALUES is set'); 45 | } 46 | if (isset($this->sql['set'])) { 47 | throw new LogicException('SELECT statement is not allowed when SET is set'); 48 | } 49 | return " {$this->sql['select']}"; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Statement.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database; 11 | 12 | /** 13 | * Class Statement. 14 | * 15 | * @package database 16 | */ 17 | abstract class Statement implements \Stringable 18 | { 19 | protected Database $database; 20 | /** 21 | * SQL clauses and parts. 22 | * 23 | * @var array 24 | */ 25 | protected array $sql = []; 26 | 27 | /** 28 | * Statement constructor. 29 | * 30 | * @param Database $database 31 | */ 32 | public function __construct(Database $database) 33 | { 34 | $this->database = $database; 35 | } 36 | 37 | public function __toString() : string 38 | { 39 | return $this->sql(); 40 | } 41 | 42 | /** 43 | * Resets SQL clauses and parts. 44 | * 45 | * @param string|null $sql A part name or null to reset all 46 | * 47 | * @see Statement::$sql 48 | * 49 | * @return static 50 | */ 51 | public function reset(?string $sql = null) : static 52 | { 53 | if ($sql === null) { 54 | unset($this->sql); 55 | return $this; 56 | } 57 | unset($this->sql[$sql]); 58 | return $this; 59 | } 60 | 61 | /** 62 | * Renders the SQL statement. 63 | * 64 | * @return string 65 | */ 66 | abstract public function sql() : string; 67 | 68 | /** 69 | * Runs the SQL statement. 70 | * 71 | * @return mixed 72 | */ 73 | abstract public function run() : mixed; 74 | } 75 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/String/StringDataType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\String; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class StringDataType. 16 | * 17 | * @package database 18 | */ 19 | abstract class StringDataType extends Column 20 | { 21 | protected string $charset; 22 | protected string $collation; 23 | 24 | /** 25 | * @param string $charset 26 | * 27 | * @return static 28 | */ 29 | public function charset(string $charset) : static 30 | { 31 | $this->charset = $charset; 32 | return $this; 33 | } 34 | 35 | protected function renderCharset() : ?string 36 | { 37 | if (!isset($this->charset)) { 38 | return null; 39 | } 40 | return ' CHARACTER SET ' . $this->database->quote($this->charset); 41 | } 42 | 43 | /** 44 | * @param string $collation 45 | * 46 | * @return static 47 | */ 48 | public function collate(string $collation) : static 49 | { 50 | $this->collation = $collation; 51 | return $this; 52 | } 53 | 54 | protected function renderCollate() : ?string 55 | { 56 | if (!isset($this->collation)) { 57 | return null; 58 | } 59 | return ' COLLATE ' . $this->database->quote($this->collation); 60 | } 61 | 62 | protected function renderTypeAttributes() : ?string 63 | { 64 | return $this->renderCharset() . $this->renderCollate(); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Definition/DropSchema.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition; 11 | 12 | use Framework\Database\Statement; 13 | use LogicException; 14 | 15 | /** 16 | * Class DropSchema. 17 | * 18 | * @see https://mariadb.com/kb/en/drop-database/ 19 | * 20 | * @package database 21 | */ 22 | class DropSchema extends Statement 23 | { 24 | /** 25 | * @return static 26 | */ 27 | public function ifExists() : static 28 | { 29 | $this->sql['if_exists'] = true; 30 | return $this; 31 | } 32 | 33 | protected function renderIfExists() : ?string 34 | { 35 | if (!isset($this->sql['if_exists'])) { 36 | return null; 37 | } 38 | return ' IF EXISTS'; 39 | } 40 | 41 | /** 42 | * @param string $schemaName 43 | * 44 | * @return static 45 | */ 46 | public function schema(string $schemaName) : static 47 | { 48 | $this->sql['schema'] = $schemaName; 49 | return $this; 50 | } 51 | 52 | protected function renderSchema() : string 53 | { 54 | if (isset($this->sql['schema'])) { 55 | return ' ' . $this->database->protectIdentifier($this->sql['schema']); 56 | } 57 | throw new LogicException('SCHEMA name must be set'); 58 | } 59 | 60 | public function sql() : string 61 | { 62 | $sql = 'DROP SCHEMA' . $this->renderIfExists(); 63 | $sql .= $this->renderSchema() . \PHP_EOL; 64 | return $sql; 65 | } 66 | 67 | /** 68 | * Runs the CREATE SCHEMA statement. 69 | * 70 | * @return int|string The number of affected rows 71 | */ 72 | public function run() : int | string 73 | { 74 | return $this->database->exec($this->sql()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/Manipulation/Traits/Explain.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Manipulation\Traits; 11 | 12 | use Framework\Database\Result\Explain as Result; 13 | use InvalidArgumentException; 14 | 15 | /** 16 | * Trait Explain. 17 | * 18 | * @see https://mariadb.com/kb/en/explain/ 19 | * 20 | * @package database 21 | * 22 | * @since 4 23 | */ 24 | trait Explain 25 | { 26 | /** 27 | * @see https://mariadb.com/kb/en/explain/#explain-extended 28 | */ 29 | public const string EXP_EXTENDED = 'EXTENDED'; 30 | /** 31 | * https://mariadb.com/kb/en/partition-pruning-and-selection/. 32 | */ 33 | public const string EXP_PARTITIONS = 'PARTITIONS'; 34 | /** 35 | * @see https://mariadb.com/kb/en/explain-format-json/ 36 | */ 37 | public const string EXP_FORMAT_JSON = 'FORMAT=JSON'; 38 | 39 | /** 40 | * EXPLAIN provides information about statements. 41 | * 42 | * @param string|null $option 43 | * 44 | * @see https://mariadb.com/kb/en/explain/ 45 | * 46 | * @return array 47 | */ 48 | public function explain(?string $option = null) : array 49 | { 50 | if ($option !== null) { 51 | $opt = \strtoupper($option); 52 | if (!\in_array($opt, [ 53 | static::EXP_EXTENDED, 54 | static::EXP_FORMAT_JSON, 55 | static::EXP_PARTITIONS, 56 | ], true)) { 57 | throw new InvalidArgumentException('Invalid EXPLAIN option: ' . $option); 58 | } 59 | $option = ' ' . $opt; 60 | } 61 | $sql = 'EXPLAIN' . $option . \PHP_EOL . $this->sql(); 62 | $results = []; 63 | foreach ($this->database->query($sql)->fetchAll() as $row) { 64 | $results[] = new Result($row); // @phpstan-ignore-line 65 | } 66 | return $results; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aplus/database", 3 | "description": "Aplus Framework Database Library", 4 | "license": "MIT", 5 | "type": "library", 6 | "keywords": [ 7 | "database", 8 | "mariadb", 9 | "mysql", 10 | "mysqli", 11 | "data-definition", 12 | "data-manipulation" 13 | ], 14 | "authors": [ 15 | { 16 | "name": "Natan Felles", 17 | "email": "natanfelles@gmail.com", 18 | "homepage": "https://natanfelles.github.io" 19 | } 20 | ], 21 | "homepage": "https://aplus-framework.com/packages/database", 22 | "support": { 23 | "email": "support@aplus-framework.com", 24 | "issues": "https://github.com/aplus-framework/database/issues", 25 | "forum": "https://aplus-framework.com/forum", 26 | "source": "https://github.com/aplus-framework/database", 27 | "docs": "https://docs.aplus-framework.com/guides/libraries/database/" 28 | }, 29 | "funding": [ 30 | { 31 | "type": "Aplus Sponsor", 32 | "url": "https://aplus-framework.com/sponsor" 33 | } 34 | ], 35 | "require": { 36 | "php": ">=8.3", 37 | "ext-mysqli": "*", 38 | "aplus/debug": "^4.3", 39 | "aplus/log": "^4.0" 40 | }, 41 | "require-dev": { 42 | "ext-xdebug": "*", 43 | "aplus/coding-standard": "^2.8", 44 | "ergebnis/composer-normalize": "^2.25", 45 | "jetbrains/phpstorm-attributes": "^1.0", 46 | "phpmd/phpmd": "^2.13", 47 | "phpstan/phpstan": "^1.9", 48 | "phpunit/phpunit": "^10.5" 49 | }, 50 | "minimum-stability": "dev", 51 | "prefer-stable": true, 52 | "autoload": { 53 | "psr-4": { 54 | "Framework\\Database\\": "src/" 55 | } 56 | }, 57 | "autoload-dev": { 58 | "psr-4": { 59 | "Tests\\Database\\": "tests/" 60 | } 61 | }, 62 | "config": { 63 | "allow-plugins": { 64 | "ergebnis/composer-normalize": true 65 | }, 66 | "optimize-autoloader": true, 67 | "preferred-install": "dist", 68 | "sort-packages": true 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Manipulation/Traits/Values.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Manipulation\Traits; 11 | 12 | use Closure; 13 | use LogicException; 14 | 15 | /** 16 | * Trait Values. 17 | * 18 | * @package database 19 | */ 20 | trait Values 21 | { 22 | /** 23 | * Adds a row of values to the VALUES clause. 24 | * 25 | * @param Closure|array>|float|int|string|null $value 26 | * @param Closure|float|int|string|null ...$values 27 | * 28 | * @return static 29 | */ 30 | public function values( 31 | Closure | array | float | int | string | null $value, 32 | Closure | float | int | string | null ...$values 33 | ) : static { 34 | if (!\is_array($value)) { 35 | $this->sql['values'][] = [$value, ...$values]; 36 | return $this; 37 | } 38 | if ($values) { 39 | throw new LogicException( 40 | 'The method ' . static::class . '::values' 41 | . ' must have only one argument when the first parameter is passed as array' 42 | ); 43 | } 44 | foreach ($value as $row) { 45 | $this->sql['values'][] = $row; 46 | } 47 | return $this; 48 | } 49 | 50 | /** 51 | * Renders the VALUES clause. 52 | * 53 | * @return string|null The VALUES part or null if none was set 54 | */ 55 | protected function renderValues() : ?string 56 | { 57 | if (!isset($this->sql['values'])) { 58 | return null; 59 | } 60 | $values = []; 61 | foreach ($this->sql['values'] as $value) { 62 | foreach ($value as &$item) { 63 | $item = $this->renderValue($item); 64 | } 65 | unset($item); 66 | $values[] = ' (' . \implode(', ', $value) . ')'; 67 | } 68 | $values = \implode(',' . \PHP_EOL, $values); 69 | return " VALUES{$values}"; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Definition/Table/Indexes/Index.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Indexes; 11 | 12 | use Framework\Database\Database; 13 | use Framework\Database\Definition\Table\DefinitionPart; 14 | use LogicException; 15 | 16 | /** 17 | * Class Index. 18 | * 19 | * @see https://mariadb.com/kb/en/getting-started-with-indexes/ 20 | * 21 | * @package database 22 | */ 23 | abstract class Index extends DefinitionPart 24 | { 25 | protected Database $database; 26 | /** 27 | * @var array 28 | */ 29 | protected array $columns; 30 | protected string $type = ''; 31 | protected ?string $name; 32 | 33 | public function __construct(Database $database, ?string $name, string $column, string ...$columns) 34 | { 35 | $this->database = $database; 36 | $this->name = $name; 37 | $this->columns = $columns ? \array_merge([$column], $columns) : [$column]; 38 | } 39 | 40 | protected function renderType() : string 41 | { 42 | if (empty($this->type)) { 43 | throw new LogicException('Key type is empty'); 44 | } 45 | return " {$this->type}"; 46 | } 47 | 48 | protected function renderName() : ?string 49 | { 50 | if ($this->name === null) { 51 | return null; 52 | } 53 | return ' ' . $this->database->protectIdentifier($this->name); 54 | } 55 | 56 | protected function renderColumns() : string 57 | { 58 | $columns = []; 59 | foreach ($this->columns as $column) { 60 | $columns[] = $this->database->protectIdentifier($column); 61 | } 62 | $columns = \implode(', ', $columns); 63 | return " ({$columns})"; 64 | } 65 | 66 | protected function renderTypeAttributes() : ?string 67 | { 68 | return null; 69 | } 70 | 71 | protected function sql() : string 72 | { 73 | $sql = $this->renderType(); 74 | $sql .= $this->renderName(); 75 | $sql .= $this->renderColumns(); 76 | $sql .= $this->renderTypeAttributes(); 77 | return $sql; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Manipulation/Traits/Set.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Manipulation\Traits; 11 | 12 | use Closure; 13 | use LogicException; 14 | 15 | /** 16 | * Trait Set. 17 | * 18 | * @package database 19 | */ 20 | trait Set 21 | { 22 | /** 23 | * Sets the SET clause. 24 | * 25 | * @param array|object $columns Array 26 | * of columns => values or an object to be cast to array 27 | * 28 | * @return static 29 | */ 30 | public function set(array | object $columns) : static 31 | { 32 | $this->sql['set'] = (array) $columns; 33 | return $this; 34 | } 35 | 36 | /** 37 | * Renders the SET clause. 38 | * 39 | * @return string|null The SET clause null if it was not set 40 | */ 41 | protected function renderSet() : ?string 42 | { 43 | if (!$this->hasSet()) { 44 | return null; 45 | } 46 | $set = []; 47 | foreach ($this->sql['set'] as $column => $value) { 48 | $set[] = $this->renderAssignment($column, $value); 49 | } 50 | $set = \implode(', ', $set); 51 | return " SET {$set}"; 52 | } 53 | 54 | /** 55 | * Renders the SET clause checking conflicts. 56 | * 57 | * @throws LogicException if SET was set with columns or with the VALUES clause 58 | * 59 | * @return string|null The SET part or null if it was not set 60 | */ 61 | protected function renderSetCheckingConflicts() : ?string 62 | { 63 | $part = $this->renderSet(); 64 | if ($part === null) { 65 | return null; 66 | } 67 | if (isset($this->sql['columns'])) { 68 | throw new LogicException('SET clause is not allowed when columns are set'); 69 | } 70 | if (isset($this->sql['values'])) { 71 | throw new LogicException('SET clause is not allowed when VALUES is set'); 72 | } 73 | return $part; 74 | } 75 | 76 | /** 77 | * Tells if the SET clause was set. 78 | * 79 | * @return bool True if was set, otherwise false 80 | */ 81 | protected function hasSet() : bool 82 | { 83 | return isset($this->sql['set']); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Numeric/NumericDataType.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns\Numeric; 11 | 12 | use Framework\Database\Definition\Table\Columns\Column; 13 | 14 | /** 15 | * Class NumericDataType. 16 | * 17 | * @package database 18 | */ 19 | abstract class NumericDataType extends Column 20 | { 21 | protected bool $signed = false; 22 | protected bool $unsigned = false; 23 | protected bool $zerofill = false; 24 | protected bool $autoIncrement = false; 25 | 26 | /** 27 | * @see https://mariadb.com/kb/en/auto_increment/ 28 | * 29 | * @return static 30 | */ 31 | public function autoIncrement() : static 32 | { 33 | $this->autoIncrement = true; 34 | return $this; 35 | } 36 | 37 | protected function renderAutoIncrement() : ?string 38 | { 39 | if (!$this->autoIncrement) { 40 | return null; 41 | } 42 | return ' AUTO_INCREMENT'; 43 | } 44 | 45 | /** 46 | * @return static 47 | */ 48 | public function signed() : static 49 | { 50 | $this->signed = true; 51 | return $this; 52 | } 53 | 54 | protected function renderSigned() : ?string 55 | { 56 | if (!$this->signed) { 57 | return null; 58 | } 59 | return ' signed'; 60 | } 61 | 62 | /** 63 | * @return static 64 | */ 65 | public function unsigned() : static 66 | { 67 | $this->unsigned = true; 68 | return $this; 69 | } 70 | 71 | protected function renderUnsigned() : ?string 72 | { 73 | if (!$this->unsigned) { 74 | return null; 75 | } 76 | return ' unsigned'; 77 | } 78 | 79 | /** 80 | * @return static 81 | */ 82 | public function zerofill() : static 83 | { 84 | $this->zerofill = true; 85 | return $this; 86 | } 87 | 88 | protected function renderZerofill() : ?string 89 | { 90 | if (!$this->zerofill) { 91 | return null; 92 | } 93 | return ' zerofill'; 94 | } 95 | 96 | protected function renderTypeAttributes() : ?string 97 | { 98 | return $this->renderSigned() 99 | . $this->renderUnsigned() 100 | . $this->renderZerofill() 101 | . $this->renderAutoIncrement(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Definition/Table/Indexes/IndexDefinition.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Indexes; 11 | 12 | use Framework\Database\Database; 13 | use Framework\Database\Definition\Table\DefinitionPart; 14 | use Framework\Database\Definition\Table\Indexes\Keys\ForeignKey; 15 | use Framework\Database\Definition\Table\Indexes\Keys\FulltextKey; 16 | use Framework\Database\Definition\Table\Indexes\Keys\Key; 17 | use Framework\Database\Definition\Table\Indexes\Keys\PrimaryKey; 18 | use Framework\Database\Definition\Table\Indexes\Keys\SpatialKey; 19 | use Framework\Database\Definition\Table\Indexes\Keys\UniqueKey; 20 | use RuntimeException; 21 | 22 | /** 23 | * Class IndexDefinition. 24 | * 25 | * @see https://mariadb.com/kb/en/create-table/#index-definitions 26 | * @see https://mariadb.com/kb/en/optimization-and-indexes/ 27 | * 28 | * @package database 29 | */ 30 | class IndexDefinition extends DefinitionPart 31 | { 32 | protected Database $database; 33 | protected ?string $name; 34 | protected ?Index $index = null; 35 | 36 | public function __construct(Database $database, ?string $name = null) 37 | { 38 | $this->database = $database; 39 | $this->name = $name; 40 | } 41 | 42 | public function key(string $column, string ...$columns) : Key 43 | { 44 | return $this->index = new Key($this->database, $this->name, $column, ...$columns); 45 | } 46 | 47 | public function primaryKey(string $column, string ...$columns) : PrimaryKey 48 | { 49 | return $this->index = new PrimaryKey($this->database, $this->name, $column, ...$columns); 50 | } 51 | 52 | public function uniqueKey(string $column, string ...$columns) : UniqueKey 53 | { 54 | return $this->index = new UniqueKey($this->database, $this->name, $column, ...$columns); 55 | } 56 | 57 | public function fulltextKey(string $column, string ...$columns) : FulltextKey 58 | { 59 | return $this->index = new FulltextKey($this->database, $this->name, $column, ...$columns); 60 | } 61 | 62 | public function foreignKey(string $column, string ...$columns) : ForeignKey 63 | { 64 | return $this->index = new ForeignKey($this->database, $this->name, $column, ...$columns); 65 | } 66 | 67 | public function spatialKey(string $column, string ...$columns) : SpatialKey 68 | { 69 | return $this->index = new SpatialKey($this->database, $this->name, $column, ...$columns); 70 | } 71 | 72 | protected function sql() : string 73 | { 74 | if (!$this->index) { 75 | throw new RuntimeException("Key type not set in index {$this->name}"); 76 | } 77 | return $this->index->sql(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Result/Explain.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Result; 11 | 12 | /** 13 | * Class Explain. 14 | * 15 | * @see https://mariadb.com/kb/en/explain/ 16 | * 17 | * @package database 18 | */ 19 | readonly class Explain 20 | { 21 | /** 22 | * Sequence number that shows in which order tables are joined. 23 | */ 24 | public int $id; 25 | /** 26 | * What kind of SELECT the table comes from. 27 | * 28 | * @see https://mariadb.com/kb/en/explain/#select_type-column 29 | */ 30 | public string $selectType; 31 | /** 32 | * Alias name of table. Materialized temporary tables for sub queries are 33 | * named . 34 | */ 35 | public string $table; 36 | /** 37 | * How rows are found from the table (join type). 38 | * 39 | * @see https://mariadb.com/kb/en/explain/#type-column 40 | */ 41 | public string $type; 42 | /** 43 | * keys in table that could be used to find rows in the table. 44 | */ 45 | public ?string $possibleKeys; 46 | /** 47 | * The name of the key that is used to retrieve rows. NULL is no key was used. 48 | */ 49 | public ?string $key; 50 | /** 51 | * How many bytes of the key that was used (shows if we are using only parts 52 | * of the multi-column key). 53 | */ 54 | public ?string $keyLen; 55 | /** 56 | * The reference that is used as the key value. 57 | */ 58 | public ?string $ref; 59 | /** 60 | * An estimate of how many rows we will find in the table for each key lookup. 61 | */ 62 | public string $rows; 63 | /** 64 | * Extra information about this join. 65 | * 66 | * @see https://mariadb.com/kb/en/explain/#extra-column 67 | */ 68 | public string $extra; 69 | /** 70 | * The EXTENDED keyword adds another column, filtered, to the output. This 71 | * is a percentage estimate of the table rows that will be filtered by the 72 | * condition. 73 | * 74 | * @see https://mariadb.com/kb/en/explain/#explain-extended 75 | */ 76 | public float $filtered; 77 | /** 78 | * EXPLAIN FORMAT=JSON is a variant of EXPLAIN command that produces output 79 | * in JSON form. 80 | * 81 | * @see https://mariadb.com/kb/en/explain-format-json/ 82 | */ 83 | public string $explain; 84 | 85 | public function __construct(\stdClass $result) 86 | { 87 | foreach ((array) $result as $key => $value) { 88 | $key = \strtolower($key); 89 | $key = \ucwords($key, '_'); 90 | $key = \strtr($key, ['_' => '']); 91 | $key[0] = \strtolower($key[0]); 92 | $this->{$key} = $value; 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Manipulation/Traits/OrderBy.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Manipulation\Traits; 11 | 12 | use Closure; 13 | 14 | /** 15 | * Trait OrderBy. 16 | * 17 | * @see https://mariadb.com/kb/en/order-by/ 18 | * 19 | * @package database 20 | */ 21 | trait OrderBy 22 | { 23 | /** 24 | * Appends columns to the ORDER BY clause. 25 | * 26 | * @param Closure|string $column The column name or a subquery 27 | * @param Closure|string ...$columns Extra column names and/or subqueries 28 | * 29 | * @return static 30 | */ 31 | public function orderBy(Closure | string $column, Closure | string ...$columns) : static 32 | { 33 | return $this->addOrderBy($column, $columns, null); 34 | } 35 | 36 | /** 37 | * Appends columns with the ASC direction to the ORDER BY clause. 38 | * 39 | * @param Closure|string $column The column name or a subquery 40 | * @param Closure|string ...$columns Extra column names and/or subqueries 41 | * 42 | * @return static 43 | */ 44 | public function orderByAsc(Closure | string $column, Closure | string ...$columns) : static 45 | { 46 | return $this->addOrderBy($column, $columns, 'ASC'); 47 | } 48 | 49 | /** 50 | * Appends columns with the DESC direction to the ORDER BY clause. 51 | * 52 | * @param Closure|string $column The column name or a subquery 53 | * @param Closure|string ...$columns Extra column names and/or subqueries 54 | * 55 | * @return static 56 | */ 57 | public function orderByDesc(Closure | string $column, Closure | string ...$columns) : static 58 | { 59 | return $this->addOrderBy($column, $columns, 'DESC'); 60 | } 61 | 62 | /** 63 | * Adds a ORDER BY expression. 64 | * 65 | * @param Closure|string $column The column name or a subquery 66 | * @param array $columns Extra column names and/or subqueries 67 | * @param string|null $direction `ASC`, `DESC` or null for none 68 | * 69 | * @return static 70 | */ 71 | private function addOrderBy(Closure | string $column, array $columns, ?string $direction) : static 72 | { 73 | foreach ([$column, ...$columns] as $column) { 74 | $this->sql['order_by'][] = [ 75 | 'column' => $column, 76 | 'direction' => $direction, 77 | ]; 78 | } 79 | return $this; 80 | } 81 | 82 | /** 83 | * Renders the ORDER BY clause. 84 | * 85 | * @return string|null The ORDER BY clause or null if it was not set 86 | */ 87 | protected function renderOrderBy() : ?string 88 | { 89 | if (!isset($this->sql['order_by'])) { 90 | return null; 91 | } 92 | $expressions = []; 93 | foreach ($this->sql['order_by'] as $part) { 94 | $expression = $this->renderIdentifier($part['column']); 95 | if ($part['direction']) { 96 | $expression .= " {$part['direction']}"; 97 | } 98 | $expressions[] = $expression; 99 | } 100 | $expressions = \implode(', ', $expressions); 101 | return " ORDER BY {$expressions}"; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/Manipulation/Traits/GroupBy.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Manipulation\Traits; 11 | 12 | use Closure; 13 | 14 | /** 15 | * Trait GroupBy. 16 | * 17 | * @see https://mariadb.com/kb/en/group-by/ 18 | * 19 | * @package database 20 | * 21 | * @since 3.4 22 | */ 23 | trait GroupBy 24 | { 25 | /** 26 | * Appends columns to the GROUP BY clause. 27 | * 28 | * @param Closure|string $column The column name or a subquery 29 | * @param Closure|string ...$columns Extra column names and/or subqueries 30 | * 31 | * @return static 32 | */ 33 | public function groupBy(Closure | string $column, Closure | string ...$columns) : static 34 | { 35 | return $this->addGroupBy($column, $columns, null); 36 | } 37 | 38 | /** 39 | * Appends columns with the ASC direction to the GROUP BY clause. 40 | * 41 | * @param Closure|string $column The column name or a subquery 42 | * @param Closure|string ...$columns Extra column names and/or subqueries 43 | * 44 | * @return static 45 | */ 46 | public function groupByAsc(Closure | string $column, Closure | string ...$columns) : static 47 | { 48 | return $this->addGroupBy($column, $columns, 'ASC'); 49 | } 50 | 51 | /** 52 | * Appends columns with the DESC direction to the GROUP BY clause. 53 | * 54 | * @param Closure|string $column The column name or a subquery 55 | * @param Closure|string ...$columns Extra column names and/or subqueries 56 | * 57 | * @return static 58 | */ 59 | public function groupByDesc(Closure | string $column, Closure | string ...$columns) : static 60 | { 61 | return $this->addGroupBy($column, $columns, 'DESC'); 62 | } 63 | 64 | /** 65 | * Adds a GROUP BY expression. 66 | * 67 | * @param Closure|string $column The column name or a subquery 68 | * @param array $columns Extra column names and/or subqueries 69 | * @param string|null $direction `ASC`, `DESC` or null for none 70 | * 71 | * @return static 72 | */ 73 | private function addGroupBy(Closure | string $column, array $columns, ?string $direction) : static 74 | { 75 | foreach ([$column, ...$columns] as $column) { 76 | $this->sql['group_by'][] = [ 77 | 'column' => $column, 78 | 'direction' => $direction, 79 | ]; 80 | } 81 | return $this; 82 | } 83 | 84 | /** 85 | * Renders the GROUP BY clause. 86 | * 87 | * @return string|null The GROUP BY clause or null if it was not set 88 | */ 89 | protected function renderGroupBy() : ?string 90 | { 91 | if (!isset($this->sql['group_by'])) { 92 | return null; 93 | } 94 | $expressions = []; 95 | foreach ($this->sql['group_by'] as $part) { 96 | $expression = $this->renderIdentifier($part['column']); 97 | if ($part['direction']) { 98 | $expression .= " {$part['direction']}"; 99 | } 100 | $expressions[] = $expression; 101 | } 102 | $expressions = \implode(', ', $expressions); 103 | return " GROUP BY {$expressions}"; 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/PreparedStatement.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database; 11 | 12 | use InvalidArgumentException; 13 | use RuntimeException; 14 | 15 | /** 16 | * Class PreparedStatement. 17 | * 18 | * @package database 19 | */ 20 | class PreparedStatement 21 | { 22 | protected \mysqli_stmt $statement; 23 | protected bool $sendingBlob = false; 24 | 25 | public function __construct(\mysqli_stmt $statement) 26 | { 27 | $this->statement = $statement; 28 | } 29 | 30 | /** 31 | * Executes the prepared statement, returning a result set as a Result object. 32 | * 33 | * @param bool|float|int|string|null ...$params Parameters sent to the prepared statement 34 | * 35 | * @throws RuntimeException if it cannot obtain a result set from the prepared statement 36 | * 37 | * @return Result 38 | */ 39 | public function query(bool | float | int | string | null ...$params) : Result 40 | { 41 | $this->bindParams($params); 42 | $this->statement->execute(); 43 | $result = $this->statement->get_result(); 44 | if ($result === false) { 45 | throw new RuntimeException('Failed while trying to obtain a result set from the prepared statement'); 46 | } 47 | return new Result($result, true); 48 | } 49 | 50 | /** 51 | * Executes the prepared statement and return the number of affected rows. 52 | * 53 | * @param bool|float|int|string|null ...$params Parameters sent to the prepared statement 54 | * 55 | * @return int|string 56 | */ 57 | public function exec(bool | float | int | string | null ...$params) : int | string 58 | { 59 | $this->bindParams($params); 60 | $this->statement->execute(); 61 | if ($this->statement->field_count) { 62 | $this->statement->free_result(); 63 | } 64 | return $this->statement->affected_rows; 65 | } 66 | 67 | /** 68 | * @param array|mixed[] $params Values types: bool, float, int, string or null 69 | */ 70 | protected function bindParams(array $params) : void 71 | { 72 | $this->sendingBlob = false; 73 | if (empty($params)) { 74 | return; 75 | } 76 | $types = ''; 77 | foreach ($params as &$param) { 78 | $type = \gettype($param); 79 | switch ($type) { 80 | case 'boolean': 81 | $types .= 'i'; 82 | $param = (int) $param; 83 | break; 84 | case 'double': 85 | $types .= 'd'; 86 | break; 87 | case 'integer': 88 | $types .= 'i'; 89 | break; 90 | case 'NULL': 91 | case 'string': 92 | $types .= 's'; 93 | break; 94 | default: 95 | throw new InvalidArgumentException( 96 | "Invalid param data type: {$type}" 97 | ); 98 | } 99 | } 100 | unset($param); 101 | $this->statement->bind_param($types, ...$params); 102 | } 103 | 104 | public function sendBlob(string $chunk) : bool 105 | { 106 | if (!$this->sendingBlob) { 107 | $this->sendingBlob = true; 108 | $null = null; 109 | $this->statement->bind_param('b', $null); 110 | } 111 | return $this->statement->send_long_data(0, $chunk); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Manipulation/With.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Manipulation; 11 | 12 | use Closure; 13 | use Framework\Database\Result; 14 | use InvalidArgumentException; 15 | use LogicException; 16 | 17 | /** 18 | * Class With. 19 | * 20 | * @see https://mariadb.com/kb/en/with/ 21 | * 22 | * @package database 23 | */ 24 | class With extends Statement 25 | { 26 | /** 27 | * @see https://mariadb.com/kb/en/recursive-common-table-expressions-overview/ 28 | */ 29 | public const string OPT_RECURSIVE = 'RECURSIVE'; 30 | 31 | protected function renderOptions() : ?string 32 | { 33 | if (!$this->hasOptions()) { 34 | return null; 35 | } 36 | $options = $this->sql['options']; 37 | foreach ($options as &$option) { 38 | $input = $option; 39 | $option = \strtoupper($option); 40 | if ($option !== static::OPT_RECURSIVE) { 41 | throw new InvalidArgumentException("Invalid option: {$input}"); 42 | } 43 | } 44 | return \implode(' ', $options); 45 | } 46 | 47 | /** 48 | * Adds a table reference. 49 | * 50 | * @param Closure|string $table 51 | * @param Closure $alias 52 | * 53 | * @see https://mariadb.com/kb/en/non-recursive-common-table-expressions-overview/ 54 | * @see https://mariadb.com/kb/en/recursive-common-table-expressions-overview/ 55 | * 56 | * @return static 57 | */ 58 | public function reference(Closure | string $table, Closure $alias) : static 59 | { 60 | $this->sql['references'][] = [ 61 | 'table' => $table, 62 | 'alias' => $alias, 63 | ]; 64 | return $this; 65 | } 66 | 67 | protected function renderReference() : string 68 | { 69 | if (!isset($this->sql['references'])) { 70 | throw new LogicException('References must be set'); 71 | } 72 | $references = []; 73 | foreach ($this->sql['references'] as $reference) { 74 | $references[] = $this->renderIdentifier($reference['table']) 75 | . ' AS ' . $this->renderAsSelect($reference['alias']); 76 | } 77 | return \implode(', ', $references); 78 | } 79 | 80 | private function renderAsSelect(Closure $subquery) : string 81 | { 82 | return '(' . $subquery(new Select($this->database)) . ')'; 83 | } 84 | 85 | /** 86 | * Sets the SELECT statement part. 87 | * 88 | * @param Closure $select 89 | * 90 | * @return static 91 | */ 92 | public function select(Closure $select) : static 93 | { 94 | $this->sql['select'] = $select(new Select($this->database)); 95 | return $this; 96 | } 97 | 98 | protected function renderSelect() : string 99 | { 100 | if (!isset($this->sql['select'])) { 101 | throw new LogicException('SELECT must be set'); 102 | } 103 | return $this->sql['select']; 104 | } 105 | 106 | /** 107 | * Renders the WITH statement. 108 | * 109 | * @return string 110 | */ 111 | public function sql() : string 112 | { 113 | $sql = 'WITH' . \PHP_EOL; 114 | $part = $this->renderOptions(); 115 | if ($part) { 116 | $sql .= $part . \PHP_EOL; 117 | } 118 | $sql .= $this->renderReference() . \PHP_EOL; 119 | $sql .= $this->renderSelect(); 120 | return $sql; 121 | } 122 | 123 | /** 124 | * Runs the WITH statement. 125 | * 126 | * @return Result 127 | */ 128 | public function run() : Result 129 | { 130 | return $this->database->query($this->sql()); 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/Definition/CreateSchema.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition; 11 | 12 | use Framework\Database\Statement; 13 | use LogicException; 14 | 15 | /** 16 | * Class CreateSchema. 17 | * 18 | * @see https://mariadb.com/kb/en/create-database/ 19 | * 20 | * @package database 21 | */ 22 | class CreateSchema extends Statement 23 | { 24 | /** 25 | * @return static 26 | */ 27 | public function orReplace() : static 28 | { 29 | $this->sql['or_replace'] = true; 30 | return $this; 31 | } 32 | 33 | protected function renderOrReplace() : ?string 34 | { 35 | if (!isset($this->sql['or_replace'])) { 36 | return null; 37 | } 38 | return ' OR REPLACE'; 39 | } 40 | 41 | /** 42 | * @return static 43 | */ 44 | public function ifNotExists() : static 45 | { 46 | $this->sql['if_not_exists'] = true; 47 | return $this; 48 | } 49 | 50 | protected function renderIfNotExists() : ?string 51 | { 52 | if (!isset($this->sql['if_not_exists'])) { 53 | return null; 54 | } 55 | if (isset($this->sql['or_replace'])) { 56 | throw new LogicException( 57 | 'Clauses OR REPLACE and IF NOT EXISTS can not be used together' 58 | ); 59 | } 60 | return ' IF NOT EXISTS'; 61 | } 62 | 63 | /** 64 | * @param string $schemaName 65 | * 66 | * @return static 67 | */ 68 | public function schema(string $schemaName) : static 69 | { 70 | $this->sql['schema'] = $schemaName; 71 | return $this; 72 | } 73 | 74 | protected function renderSchema() : string 75 | { 76 | if (isset($this->sql['schema'])) { 77 | return ' ' . $this->database->protectIdentifier($this->sql['schema']); 78 | } 79 | throw new LogicException('SCHEMA name must be set'); 80 | } 81 | 82 | /** 83 | * @param string $charset 84 | * 85 | * @return static 86 | */ 87 | public function charset(string $charset) : static 88 | { 89 | $this->sql['charset'] = $charset; 90 | return $this; 91 | } 92 | 93 | protected function renderCharset() : ?string 94 | { 95 | if (!isset($this->sql['charset'])) { 96 | return null; 97 | } 98 | $charset = $this->database->quote($this->sql['charset']); 99 | return " CHARACTER SET = {$charset}"; 100 | } 101 | 102 | /** 103 | * @param string $collation 104 | * 105 | * @return static 106 | */ 107 | public function collate(string $collation) : static 108 | { 109 | $this->sql['collation'] = $collation; 110 | return $this; 111 | } 112 | 113 | protected function renderCollate() : ?string 114 | { 115 | if (!isset($this->sql['collation'])) { 116 | return null; 117 | } 118 | $collation = $this->database->quote($this->sql['collation']); 119 | return " COLLATE = {$collation}"; 120 | } 121 | 122 | public function sql() : string 123 | { 124 | $sql = 'CREATE' . $this->renderOrReplace(); 125 | $sql .= ' SCHEMA' . $this->renderIfNotExists(); 126 | $sql .= $this->renderSchema() . \PHP_EOL; 127 | $part = $this->renderCharset(); 128 | if ($part) { 129 | $sql .= $part . \PHP_EOL; 130 | } 131 | $part = $this->renderCollate(); 132 | if ($part) { 133 | $sql .= $part . \PHP_EOL; 134 | } 135 | return $sql; 136 | } 137 | 138 | /** 139 | * Runs the CREATE SCHEMA statement. 140 | * 141 | * @return int|string The number of affected rows 142 | */ 143 | public function run() : int | string 144 | { 145 | return $this->database->exec($this->sql()); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Definition/CreateTable.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition; 11 | 12 | use Framework\Database\Definition\Table\TableDefinition; 13 | use Framework\Database\Definition\Table\TableStatement; 14 | use LogicException; 15 | 16 | /** 17 | * Class CreateTable. 18 | * 19 | * @see https://mariadb.com/kb/en/create-table/ 20 | * 21 | * @package database 22 | */ 23 | class CreateTable extends TableStatement 24 | { 25 | /** 26 | * Adds a OR REPLACE part. 27 | * 28 | * WARNING: This feature is MariaDB only. It is not compatible with MySQL. 29 | * 30 | * @return static 31 | */ 32 | public function orReplace() : static 33 | { 34 | $this->sql['or_replace'] = true; 35 | return $this; 36 | } 37 | 38 | protected function renderOrReplace() : ?string 39 | { 40 | if (!isset($this->sql['or_replace'])) { 41 | return null; 42 | } 43 | return ' OR REPLACE'; 44 | } 45 | 46 | /** 47 | * @return static 48 | */ 49 | public function temporary() : static 50 | { 51 | $this->sql['temporary'] = true; 52 | return $this; 53 | } 54 | 55 | protected function renderTemporary() : ?string 56 | { 57 | if (!isset($this->sql['temporary'])) { 58 | return null; 59 | } 60 | return ' TEMPORARY'; 61 | } 62 | 63 | /** 64 | * @return static 65 | */ 66 | public function ifNotExists() : static 67 | { 68 | $this->sql['if_not_exists'] = true; 69 | return $this; 70 | } 71 | 72 | protected function renderIfNotExists() : ?string 73 | { 74 | if (!isset($this->sql['if_not_exists'])) { 75 | return null; 76 | } 77 | if (isset($this->sql['or_replace'])) { 78 | throw new LogicException( 79 | 'Clauses OR REPLACE and IF NOT EXISTS can not be used together' 80 | ); 81 | } 82 | return ' IF NOT EXISTS'; 83 | } 84 | 85 | /** 86 | * @param string $tableName 87 | * 88 | * @return static 89 | */ 90 | public function table(string $tableName) : static 91 | { 92 | $this->sql['table'] = $tableName; 93 | return $this; 94 | } 95 | 96 | protected function renderTable() : string 97 | { 98 | if (isset($this->sql['table'])) { 99 | return ' ' . $this->database->protectIdentifier($this->sql['table']); 100 | } 101 | throw new LogicException('TABLE name must be set'); 102 | } 103 | 104 | /** 105 | * @param callable $definition 106 | * 107 | * @return static 108 | */ 109 | public function definition(callable $definition) : static 110 | { 111 | $this->sql['definition'] = $definition; 112 | return $this; 113 | } 114 | 115 | protected function renderDefinition() : string 116 | { 117 | if (!isset($this->sql['definition'])) { 118 | throw new LogicException('Table definition must be set'); 119 | } 120 | $definition = new TableDefinition($this->database); 121 | $this->sql['definition']($definition); 122 | return $definition->sql(); 123 | } 124 | 125 | public function sql() : string 126 | { 127 | $sql = 'CREATE' . $this->renderOrReplace() . $this->renderTemporary(); 128 | $sql .= ' TABLE' . $this->renderIfNotExists(); 129 | $sql .= $this->renderTable() . ' (' . \PHP_EOL; 130 | $sql .= $this->renderDefinition() . \PHP_EOL; 131 | $sql .= ')' . $this->renderOptions(); 132 | return $sql; 133 | } 134 | 135 | /** 136 | * Runs the CREATE TABLE statement. 137 | * 138 | * @return int|string The number of affected rows 139 | */ 140 | public function run() : int | string 141 | { 142 | return $this->database->exec($this->sql()); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/Definition/DropTable.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition; 11 | 12 | use Framework\Database\Statement; 13 | use InvalidArgumentException; 14 | use LogicException; 15 | 16 | /** 17 | * Class DropTable. 18 | * 19 | * @see https://mariadb.com/kb/en/drop-table/ 20 | * 21 | * @package database 22 | */ 23 | class DropTable extends Statement 24 | { 25 | /** 26 | * @return static 27 | */ 28 | public function temporary() : static 29 | { 30 | $this->sql['temporary'] = true; 31 | return $this; 32 | } 33 | 34 | protected function renderTemporary() : ?string 35 | { 36 | if (!isset($this->sql['temporary'])) { 37 | return null; 38 | } 39 | return ' TEMPORARY'; 40 | } 41 | 42 | /** 43 | * @return static 44 | */ 45 | public function ifExists() : static 46 | { 47 | $this->sql['if_exists'] = true; 48 | return $this; 49 | } 50 | 51 | protected function renderIfExists() : ?string 52 | { 53 | if (!isset($this->sql['if_exists'])) { 54 | return null; 55 | } 56 | return ' IF EXISTS'; 57 | } 58 | 59 | /** 60 | * @param string $comment 61 | * 62 | * @return static 63 | */ 64 | public function commentToSave(string $comment) : static 65 | { 66 | $this->sql['comment'] = $comment; 67 | return $this; 68 | } 69 | 70 | protected function renderCommentToSave() : ?string 71 | { 72 | if (!isset($this->sql['comment'])) { 73 | return null; 74 | } 75 | $comment = \strtr($this->sql['comment'], ['*/' => '* /']); 76 | return " /* {$comment} */"; 77 | } 78 | 79 | /** 80 | * @param string $table 81 | * @param string ...$tables 82 | * 83 | * @return static 84 | */ 85 | public function table(string $table, string ...$tables) : static 86 | { 87 | $this->sql['tables'] = $tables ? \array_merge([$table], $tables) : [$table]; 88 | return $this; 89 | } 90 | 91 | protected function renderTables() : string 92 | { 93 | if (!isset($this->sql['tables'])) { 94 | throw new LogicException('Table names can not be empty'); 95 | } 96 | $tables = $this->sql['tables']; 97 | foreach ($tables as &$table) { 98 | $table = $this->database->protectIdentifier($table); 99 | } 100 | unset($table); 101 | $tables = \implode(', ', $tables); 102 | return " {$tables}"; 103 | } 104 | 105 | /** 106 | * @param int $seconds 107 | * 108 | * @return static 109 | */ 110 | public function wait(int $seconds) : static 111 | { 112 | $this->sql['wait'] = $seconds; 113 | return $this; 114 | } 115 | 116 | public function renderWait() : ?string 117 | { 118 | if (!isset($this->sql['wait'])) { 119 | return null; 120 | } 121 | if ($this->sql['wait'] < 0) { 122 | throw new InvalidArgumentException( 123 | "Invalid WAIT value: {$this->sql['wait']}" 124 | ); 125 | } 126 | return " WAIT {$this->sql['wait']}"; 127 | } 128 | 129 | public function sql() : string 130 | { 131 | $sql = 'DROP' . $this->renderTemporary(); 132 | $sql .= ' TABLE' . $this->renderIfExists(); 133 | $sql .= $this->renderCommentToSave(); 134 | $sql .= $this->renderTables() . \PHP_EOL; 135 | $part = $this->renderWait(); 136 | if ($part) { 137 | $sql .= $part . \PHP_EOL; 138 | } 139 | return $sql; 140 | } 141 | 142 | /** 143 | * Runs the DROP TABLE statement. 144 | * 145 | * @return int|string The number of affected rows 146 | */ 147 | public function run() : int | string 148 | { 149 | return $this->database->exec($this->sql()); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Definition/AlterSchema.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition; 11 | 12 | use Framework\Database\Statement; 13 | use LogicException; 14 | 15 | /** 16 | * Class AlterSchema. 17 | * 18 | * @see https://mariadb.com/kb/en/alter-database/ 19 | * 20 | * @package database 21 | */ 22 | class AlterSchema extends Statement 23 | { 24 | /** 25 | * @param string $schemaName 26 | * 27 | * @return static 28 | */ 29 | public function schema(string $schemaName) : static 30 | { 31 | $this->sql['schema'] = $schemaName; 32 | return $this; 33 | } 34 | 35 | protected function renderSchema() : ?string 36 | { 37 | if (!isset($this->sql['schema'])) { 38 | return null; 39 | } 40 | $schema = $this->sql['schema']; 41 | if (isset($this->sql['upgrade'])) { 42 | $schema = "#mysql50#{$schema}"; 43 | } 44 | return ' ' . $this->database->protectIdentifier($schema); 45 | } 46 | 47 | /** 48 | * @param string $charset 49 | * 50 | * @return static 51 | */ 52 | public function charset(string $charset) : static 53 | { 54 | $this->sql['charset'] = $charset; 55 | return $this; 56 | } 57 | 58 | protected function renderCharset() : ?string 59 | { 60 | if (!isset($this->sql['charset'])) { 61 | return null; 62 | } 63 | $charset = $this->database->quote($this->sql['charset']); 64 | return " CHARACTER SET = {$charset}"; 65 | } 66 | 67 | /** 68 | * @param string $collation 69 | * 70 | * @return static 71 | */ 72 | public function collate(string $collation) : static 73 | { 74 | $this->sql['collation'] = $collation; 75 | return $this; 76 | } 77 | 78 | protected function renderCollate() : ?string 79 | { 80 | if (!isset($this->sql['collation'])) { 81 | return null; 82 | } 83 | $collation = $this->database->quote($this->sql['collation']); 84 | return " COLLATE = {$collation}"; 85 | } 86 | 87 | /** 88 | * @return static 89 | */ 90 | public function upgrade() : static 91 | { 92 | $this->sql['upgrade'] = true; 93 | return $this; 94 | } 95 | 96 | protected function renderUpgrade() : ?string 97 | { 98 | if (!isset($this->sql['upgrade'])) { 99 | return null; 100 | } 101 | if (isset($this->sql['charset']) || isset($this->sql['collation'])) { 102 | throw new LogicException( 103 | 'UPGRADE DATA DIRECTORY NAME can not be used with CHARACTER SET or COLLATE' 104 | ); 105 | } 106 | return ' UPGRADE DATA DIRECTORY NAME'; 107 | } 108 | 109 | protected function checkSpecifications() : void 110 | { 111 | if (!isset($this->sql['charset']) 112 | && !isset($this->sql['collation']) 113 | && !isset($this->sql['upgrade']) 114 | ) { 115 | throw new LogicException( 116 | 'ALTER SCHEMA must have a specification' 117 | ); 118 | } 119 | } 120 | 121 | public function sql() : string 122 | { 123 | $sql = 'ALTER SCHEMA'; 124 | $sql .= $this->renderSchema() . \PHP_EOL; 125 | $part = $this->renderCharset(); 126 | if ($part) { 127 | $sql .= $part . \PHP_EOL; 128 | } 129 | $part = $this->renderCollate(); 130 | if ($part) { 131 | $sql .= $part . \PHP_EOL; 132 | } 133 | $part = $this->renderUpgrade(); 134 | if ($part) { 135 | $sql .= $part . \PHP_EOL; 136 | } 137 | $this->checkSpecifications(); 138 | return $sql; 139 | } 140 | 141 | /** 142 | * Runs the ALTER SCHEMA statement. 143 | * 144 | * @return int|string The number of affected rows 145 | */ 146 | public function run() : int | string 147 | { 148 | return $this->database->exec($this->sql()); 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/Definition/Table/Indexes/Keys/ForeignKey.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Indexes\Keys; 11 | 12 | use InvalidArgumentException; 13 | use LogicException; 14 | 15 | /** 16 | * Class ForeignKey. 17 | * 18 | * @see https://mariadb.com/kb/en/foreign-keys/ 19 | * 20 | * @package database 21 | */ 22 | final class ForeignKey extends ConstraintKey 23 | { 24 | /** 25 | * The change is allowed and propagates on the child table. 26 | * For example, if a parent row is deleted, the child row is also deleted; 27 | * if a parent row's ID changes, the child row's ID will also change. 28 | */ 29 | public const string OPT_CASCADE = 'CASCADE'; 30 | /** 31 | * Synonym for RESTRICT. 32 | * 33 | * @see ForeignKey::OPT_RESTRICT 34 | */ 35 | public const string OPT_NO_ACTION = 'NO ACTION'; 36 | /** 37 | * The change on the parent table is prevented. 38 | * The statement terminates with a 1451 error (SQLSTATE '2300'). 39 | * This is the default behavior for both ON DELETE and ON UPDATE. 40 | */ 41 | public const string OPT_RESTRICT = 'RESTRICT'; 42 | /** 43 | * The change is allowed, and the child row's foreign key columns are set 44 | * to NULL. 45 | */ 46 | public const string OPT_SET_NULL = 'SET NULL'; 47 | protected string $type = 'FOREIGN KEY'; 48 | protected ?string $referenceTable = null; 49 | /** 50 | * @var array 51 | */ 52 | protected array $referenceColumns = []; 53 | protected ?string $onDelete = null; 54 | protected ?string $onUpdate = null; 55 | 56 | /** 57 | * @param string $table 58 | * @param string $column 59 | * @param string ...$columns 60 | * 61 | * @return static 62 | */ 63 | public function references(string $table, string $column, string ...$columns) : static 64 | { 65 | $this->referenceTable = $table; 66 | $this->referenceColumns = $columns ? \array_merge([$column], $columns) : [$column]; 67 | return $this; 68 | } 69 | 70 | protected function renderReferences() : string 71 | { 72 | if ($this->referenceTable === null) { 73 | throw new LogicException('REFERENCES clause was not set'); 74 | } 75 | $table = $this->database->protectIdentifier($this->referenceTable); 76 | $columns = []; 77 | foreach ($this->referenceColumns as $column) { 78 | $columns[] = $this->database->protectIdentifier($column); 79 | } 80 | $columns = \implode(', ', $columns); 81 | return " REFERENCES {$table} ({$columns})"; 82 | } 83 | 84 | /** 85 | * @param string $option 86 | * 87 | * @return static 88 | */ 89 | public function onDelete(string $option) : static 90 | { 91 | $this->onDelete = $option; 92 | return $this; 93 | } 94 | 95 | protected function renderOnDelete() : ?string 96 | { 97 | if ($this->onDelete === null) { 98 | return null; 99 | } 100 | $reference = $this->makeReferenceOption($this->onDelete); 101 | return " ON DELETE {$reference}"; 102 | } 103 | 104 | /** 105 | * @param string $option 106 | * 107 | * @return static 108 | */ 109 | public function onUpdate(string $option) : static 110 | { 111 | $this->onUpdate = $option; 112 | return $this; 113 | } 114 | 115 | protected function renderOnUpdate() : ?string 116 | { 117 | if ($this->onUpdate === null) { 118 | return null; 119 | } 120 | $reference = $this->makeReferenceOption($this->onUpdate); 121 | return " ON UPDATE {$reference}"; 122 | } 123 | 124 | private function makeReferenceOption(string $option) : string 125 | { 126 | $result = \strtoupper($option); 127 | if (\in_array($result, ['RESTRICT', 'CASCADE', 'SET NULL', 'NO ACTION'], true)) { 128 | return $result; 129 | } 130 | throw new InvalidArgumentException("Invalid reference option: {$option}"); 131 | } 132 | 133 | protected function renderTypeAttributes() : ?string 134 | { 135 | return $this->renderReferences() . $this->renderOnDelete() . $this->renderOnUpdate(); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/Manipulation/Delete.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Manipulation; 11 | 12 | use Closure; 13 | use InvalidArgumentException; 14 | 15 | /** 16 | * Class Delete. 17 | * 18 | * @see https://mariadb.com/kb/en/delete/ 19 | * 20 | * @package database 21 | */ 22 | class Delete extends Statement 23 | { 24 | use Traits\Explain; 25 | use Traits\Join; 26 | use Traits\OrderBy; 27 | use Traits\Where; 28 | 29 | public const string OPT_LOW_PRIORITY = 'LOW_PRIORITY'; 30 | public const string OPT_QUICK = 'QUICK'; 31 | public const string OPT_IGNORE = 'IGNORE'; 32 | 33 | protected function renderOptions() : ?string 34 | { 35 | if (!$this->hasOptions()) { 36 | return null; 37 | } 38 | $options = $this->sql['options']; 39 | foreach ($options as &$option) { 40 | $input = $option; 41 | $option = \strtoupper($option); 42 | if (!\in_array($option, [ 43 | static::OPT_LOW_PRIORITY, 44 | static::OPT_QUICK, 45 | static::OPT_IGNORE, 46 | ], true)) { 47 | throw new InvalidArgumentException("Invalid option: {$input}"); 48 | } 49 | } 50 | unset($option); 51 | $options = \implode(' ', $options); 52 | return " {$options}"; 53 | } 54 | 55 | /** 56 | * Sets the table references. 57 | * 58 | * @param Closure|array|string $reference The table 59 | * name as string, a subquery as Closure or an array for aliased table where 60 | * the key is the alias name and the value is the table name or a subquery 61 | * @param Closure|array|string ...$references Extra 62 | * references. Same values as $reference 63 | * 64 | * @return static 65 | */ 66 | public function table( 67 | Closure | array | string $reference, 68 | Closure | array | string ...$references 69 | ) : static { 70 | $this->sql['table'] = []; 71 | foreach ([$reference, ...$references] as $reference) { 72 | $this->sql['table'][] = $reference; 73 | } 74 | return $this; 75 | } 76 | 77 | /** 78 | * Renders the table references. 79 | * 80 | * @return string|null The table references or null if none was set 81 | */ 82 | protected function renderTable() : ?string 83 | { 84 | if (!isset($this->sql['table'])) { 85 | return null; 86 | } 87 | $tables = []; 88 | foreach ($this->sql['table'] as $table) { 89 | $tables[] = $this->renderAliasedIdentifier($table); 90 | } 91 | return ' ' . \implode(', ', $tables); 92 | } 93 | 94 | /** 95 | * Sets the LIMIT clause. 96 | * 97 | * @param int $limit 98 | * 99 | * @see https://mariadb.com/kb/en/limit/ 100 | * 101 | * @return static 102 | */ 103 | public function limit(int $limit) : static 104 | { 105 | return $this->setLimit($limit); 106 | } 107 | 108 | /** 109 | * Renders de DELETE statement. 110 | * 111 | * @return string 112 | */ 113 | public function sql() : string 114 | { 115 | $sql = 'DELETE' . \PHP_EOL; 116 | $part = $this->renderOptions(); 117 | if ($part) { 118 | $sql .= $part . \PHP_EOL; 119 | } 120 | $part = $this->renderTable(); 121 | if ($part) { 122 | $sql .= $part . \PHP_EOL; 123 | } 124 | $part = $this->renderFrom(); 125 | if ($part) { 126 | $sql .= $part . \PHP_EOL; 127 | } 128 | $part = $this->renderJoin(); 129 | if ($part) { 130 | $sql .= $part . \PHP_EOL; 131 | } 132 | $part = $this->renderWhere(); 133 | if ($part) { 134 | $sql .= $part . \PHP_EOL; 135 | } 136 | $part = $this->renderOrderBy(); 137 | if ($part) { 138 | $sql .= $part . \PHP_EOL; 139 | } 140 | $part = $this->renderLimit(); 141 | if ($part) { 142 | $sql .= $part . \PHP_EOL; 143 | } 144 | return $sql; 145 | } 146 | 147 | /** 148 | * Runs the DELETE statement. 149 | * 150 | * @return int|string The number of affected rows 151 | */ 152 | public function run() : int | string 153 | { 154 | return $this->database->exec($this->sql()); 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Manipulation/Update.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Manipulation; 11 | 12 | use Closure; 13 | use InvalidArgumentException; 14 | use LogicException; 15 | 16 | /** 17 | * Class Update. 18 | * 19 | * @see https://mariadb.com/kb/en/update/ 20 | * 21 | * @package database 22 | */ 23 | class Update extends Statement 24 | { 25 | use Traits\Explain; 26 | use Traits\Join; 27 | use Traits\Set; 28 | use Traits\Where; 29 | use Traits\OrderBy; 30 | 31 | /** 32 | * Convert errors to warnings, which will not stop inserts of additional rows. 33 | * 34 | * @see https://mariadb.com/kb/en/insert-ignore/ 35 | */ 36 | public const string OPT_IGNORE = 'IGNORE'; 37 | /** 38 | * @see https://mariadb.com/kb/en/high_priority-and-low_priority/ 39 | */ 40 | public const string OPT_LOW_PRIORITY = 'LOW_PRIORITY'; 41 | 42 | protected function renderOptions() : ?string 43 | { 44 | if (!$this->hasOptions()) { 45 | return null; 46 | } 47 | $options = $this->sql['options']; 48 | foreach ($options as &$option) { 49 | $input = $option; 50 | $option = \strtoupper($option); 51 | if (!\in_array($option, [ 52 | static::OPT_IGNORE, 53 | static::OPT_LOW_PRIORITY, 54 | ], true)) { 55 | throw new InvalidArgumentException("Invalid option: {$input}"); 56 | } 57 | } 58 | unset($option); 59 | $options = \implode(' ', $options); 60 | return " {$options}"; 61 | } 62 | 63 | /** 64 | * Sets the table references. 65 | * 66 | * @param Closure|array|string $reference 67 | * @param Closure|array|string ...$references 68 | * 69 | * @return static 70 | */ 71 | public function table( 72 | Closure | array | string $reference, 73 | Closure | array | string ...$references 74 | ) : static { 75 | $this->sql['table'] = []; 76 | foreach ([$reference, ...$references] as $reference) { 77 | $this->sql['table'][] = $reference; 78 | } 79 | return $this; 80 | } 81 | 82 | protected function renderTable() : string 83 | { 84 | if (!isset($this->sql['table'])) { 85 | throw new LogicException('Table references must be set'); 86 | } 87 | $tables = []; 88 | foreach ($this->sql['table'] as $table) { 89 | $tables[] = $this->renderAliasedIdentifier($table); 90 | } 91 | return ' ' . \implode(', ', $tables); 92 | } 93 | 94 | /** 95 | * Sets the LIMIT clause. 96 | * 97 | * @param int $limit 98 | * 99 | * @see https://mariadb.com/kb/en/limit/ 100 | * 101 | * @return static 102 | */ 103 | public function limit(int $limit) : static 104 | { 105 | return $this->setLimit($limit); 106 | } 107 | 108 | protected function renderSetPart() : string 109 | { 110 | if (!$this->hasSet()) { 111 | throw new LogicException('SET statement must be set'); 112 | } 113 | return $this->renderSet(); 114 | } 115 | 116 | /** 117 | * Renders the UPDATE statement. 118 | * 119 | * @return string 120 | */ 121 | public function sql() : string 122 | { 123 | $sql = 'UPDATE' . \PHP_EOL; 124 | $part = $this->renderOptions(); 125 | if ($part) { 126 | $sql .= $part . \PHP_EOL; 127 | } 128 | $sql .= $this->renderTable() . \PHP_EOL; 129 | $part = $this->renderJoin(); 130 | if ($part) { 131 | $sql .= $part . \PHP_EOL; 132 | } 133 | $sql .= $this->renderSetPart() . \PHP_EOL; 134 | $part = $this->renderWhere(); 135 | if ($part) { 136 | $sql .= $part . \PHP_EOL; 137 | } 138 | $part = $this->renderOrderBy(); 139 | if ($part) { 140 | $sql .= $part . \PHP_EOL; 141 | } 142 | $part = $this->renderLimit(); 143 | if ($part) { 144 | $sql .= $part . \PHP_EOL; 145 | } 146 | return $sql; 147 | } 148 | 149 | /** 150 | * Runs the UPDATE statement. 151 | * 152 | * @return int|string The number of affected rows 153 | */ 154 | public function run() : int | string 155 | { 156 | return $this->database->exec($this->sql()); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/Debug/DatabaseCollector.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Debug; 11 | 12 | use Framework\Database\Database; 13 | use Framework\Debug\Collector; 14 | use Framework\Debug\Debugger; 15 | 16 | /** 17 | * Class DatabaseCollector. 18 | * 19 | * @package database 20 | */ 21 | class DatabaseCollector extends Collector 22 | { 23 | protected Database $database; 24 | 25 | public function setDatabase(Database $database) : static 26 | { 27 | $this->database = $database; 28 | return $this; 29 | } 30 | 31 | public function getServerInfo() : string 32 | { 33 | return $this->database->getConnection()->server_info; 34 | } 35 | 36 | public function getActivities() : array 37 | { 38 | $activities = []; 39 | foreach ($this->getData() as $index => $data) { 40 | $activities[] = [ 41 | 'collector' => $this->getName(), 42 | 'class' => static::class, 43 | 'description' => 'Run statement ' . ($index + 1), 44 | 'start' => $data['start'], 45 | 'end' => $data['end'], 46 | ]; 47 | } 48 | return $activities; 49 | } 50 | 51 | public function getContents() : string 52 | { 53 | \ob_start(); 54 | if (!isset($this->database)) { 55 | echo '

This collector has not been added to a Database instance.

'; 56 | return \ob_get_clean(); // @phpstan-ignore-line 57 | } 58 | echo $this->showHeader(); 59 | if (!$this->hasData()) { 60 | echo '

Did not run statements.

'; 61 | return \ob_get_clean(); // @phpstan-ignore-line 62 | } 63 | $count = \count($this->getData()); ?> 64 |

Ran statement 65 | in getStatementsTime() ?> ms: 66 |

67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | getData() as $index => $item): ?> 78 | 79 | 80 | 81 | 86 | > 89 | 90 | 91 | 92 |
#TimeStatementRows
82 |
85 |
93 | getData() as $data) { 101 | $total = $data['end'] - $data['start']; 102 | $time += $total; 103 | } 104 | return Debugger::roundSecondsToMilliseconds($time); 105 | } 106 | 107 | protected function showHeader() : string 108 | { 109 | $config = $this->database->getConfig(); 110 | \ob_start(); 111 | ?> 112 |

113 | Host: 114 |

115 | getHostInfo(), 'TCP/IP')) { 117 | if (isset($config['port'])) { 118 | ?> 119 |

Port:

120 | 123 |

Socket:

124 | 127 |

Server Info: getServerInfo()) ?>

128 | database->getConnection()->host_info; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/Manipulation/Replace.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Manipulation; 11 | 12 | use InvalidArgumentException; 13 | use LogicException; 14 | 15 | /** 16 | * Class Replace. 17 | * 18 | * @see https://mariadb.com/kb/en/replace/ 19 | * 20 | * @package database 21 | */ 22 | class Replace extends Statement 23 | { 24 | use Traits\Select; 25 | use Traits\Set; 26 | use Traits\Values; 27 | 28 | /** 29 | * @see https://mariadb.com/kb/en/insert-delayed/ 30 | */ 31 | public const string OPT_DELAYED = 'DELAYED'; 32 | /** 33 | * @see https://mariadb.com/kb/en/high_priority-and-low_priority/ 34 | */ 35 | public const string OPT_LOW_PRIORITY = 'LOW_PRIORITY'; 36 | 37 | /** 38 | * @param string $table 39 | * 40 | * @return static 41 | */ 42 | public function into(string $table) : static 43 | { 44 | $this->sql['into'] = $table; 45 | return $this; 46 | } 47 | 48 | protected function renderInto() : string 49 | { 50 | if (!isset($this->sql['into'])) { 51 | throw new LogicException('INTO table must be set'); 52 | } 53 | return ' INTO ' . $this->renderIdentifier($this->sql['into']); 54 | } 55 | 56 | /** 57 | * @param string $column 58 | * @param string ...$columns 59 | * 60 | * @return static 61 | */ 62 | public function columns(string $column, string ...$columns) : static 63 | { 64 | $this->sql['columns'] = [$column, ...$columns]; 65 | return $this; 66 | } 67 | 68 | protected function renderColumns() : ?string 69 | { 70 | if (!isset($this->sql['columns'])) { 71 | return null; 72 | } 73 | $columns = []; 74 | foreach ($this->sql['columns'] as $column) { 75 | $columns[] = $this->renderIdentifier($column); 76 | } 77 | $columns = \implode(', ', $columns); 78 | return " ({$columns})"; 79 | } 80 | 81 | protected function renderOptions() : ?string 82 | { 83 | if (!$this->hasOptions()) { 84 | return null; 85 | } 86 | $options = $this->sql['options']; 87 | foreach ($options as &$option) { 88 | $input = $option; 89 | $option = \strtoupper($option); 90 | if (!\in_array($option, [ 91 | static::OPT_DELAYED, 92 | static::OPT_LOW_PRIORITY, 93 | ], true)) { 94 | throw new InvalidArgumentException("Invalid option: {$input}"); 95 | } 96 | } 97 | unset($option); 98 | $intersection = \array_intersect( 99 | $options, 100 | [static::OPT_DELAYED, static::OPT_LOW_PRIORITY] 101 | ); 102 | if (\count($intersection) > 1) { 103 | throw new LogicException( 104 | 'Options LOW_PRIORITY and DELAYED can not be used together' 105 | ); 106 | } 107 | $options = \implode(' ', $options); 108 | return " {$options}"; 109 | } 110 | 111 | protected function checkRowStatementsConflict() : void 112 | { 113 | if (!isset($this->sql['values']) 114 | && !isset($this->sql['select']) 115 | && !$this->hasSet() 116 | ) { 117 | throw new LogicException( 118 | 'The REPLACE INTO must be followed by VALUES, SET or SELECT statement' 119 | ); 120 | } 121 | } 122 | 123 | /** 124 | * Renders the REPLACE statement. 125 | * 126 | * @return string 127 | */ 128 | public function sql() : string 129 | { 130 | $sql = 'REPLACE' . \PHP_EOL; 131 | $part = $this->renderOptions(); 132 | if ($part) { 133 | $sql .= $part . \PHP_EOL; 134 | } 135 | $sql .= $this->renderInto() . \PHP_EOL; 136 | $part = $this->renderColumns(); 137 | if ($part) { 138 | $sql .= $part . \PHP_EOL; 139 | } 140 | $this->checkRowStatementsConflict(); 141 | $part = $this->renderValues(); 142 | if ($part) { 143 | $sql .= $part . \PHP_EOL; 144 | } 145 | $part = $this->renderSetCheckingConflicts(); 146 | if ($part) { 147 | $sql .= $part . \PHP_EOL; 148 | } 149 | $part = $this->renderSelect(); 150 | if ($part) { 151 | $sql .= $part . \PHP_EOL; 152 | } 153 | return $sql; 154 | } 155 | 156 | /** 157 | * Runs the REPLACE statement. 158 | * 159 | * @return int|string The number of affected rows 160 | */ 161 | public function run() : int | string 162 | { 163 | return $this->database->exec($this->sql()); 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/Definition/Table/TableDefinition.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table; 11 | 12 | use Closure; 13 | use Framework\Database\Database; 14 | use Framework\Database\Definition\Table\Columns\ColumnDefinition; 15 | use Framework\Database\Definition\Table\Indexes\IndexDefinition; 16 | 17 | /** 18 | * Class TableDefinition. 19 | * 20 | * @package database 21 | */ 22 | class TableDefinition extends DefinitionPart 23 | { 24 | protected Database $database; 25 | /** 26 | * @var array> 27 | */ 28 | protected array $columns = []; 29 | /** 30 | * @var array> 31 | */ 32 | protected array $indexes = []; 33 | /** 34 | * @var array 35 | */ 36 | protected array $checks = []; 37 | protected ?string $condition = null; 38 | 39 | /** 40 | * TableDefinition constructor. 41 | * 42 | * @param Database $database 43 | * @param string|null $condition 44 | */ 45 | public function __construct(Database $database, ?string $condition = null) 46 | { 47 | $this->database = $database; 48 | $this->condition = $condition; 49 | } 50 | 51 | /** 52 | * Adds a column to the Table Definition list. 53 | * 54 | * @param string $name Column name 55 | * @param string|null $changeName New column name. Used on ALTER TABLE CHANGE 56 | * 57 | * @return ColumnDefinition 58 | */ 59 | public function column(string $name, ?string $changeName = null) : ColumnDefinition 60 | { 61 | $definition = new ColumnDefinition($this->database); 62 | $this->columns[] = [ 63 | 'name' => $name, 64 | 'change_name' => $changeName, 65 | 'definition' => $definition, 66 | ]; 67 | return $definition; 68 | } 69 | 70 | /** 71 | * Adds an index to the Table Definition list. 72 | * 73 | * @param string|null $name Index name 74 | * 75 | * @return IndexDefinition 76 | */ 77 | public function index(?string $name = null) : IndexDefinition 78 | { 79 | $definition = new IndexDefinition($this->database, $name); 80 | $this->indexes[] = [ 81 | 'name' => $name, 82 | 'definition' => $definition, 83 | ]; 84 | return $definition; 85 | } 86 | 87 | /** 88 | * Adds a check constraint to the Table Definition list. 89 | * 90 | * @param Closure $expression Must return a string with the check expression. 91 | * The function receives a Database instance in the first parameter. 92 | * 93 | * @return Check 94 | */ 95 | public function check(Closure $expression) : Check 96 | { 97 | return $this->checks[] = new Check($this->database, $expression); 98 | } 99 | 100 | protected function renderColumns(?string $prefix = null) : string 101 | { 102 | if ($prefix) { 103 | $prefix .= ' COLUMN'; 104 | } 105 | if ($this->condition) { 106 | $prefix .= ' ' . $this->condition; 107 | } 108 | $sql = []; 109 | foreach ($this->columns as $column) { 110 | $name = $this->database->protectIdentifier($column['name']); 111 | $changeName = $column['change_name'] 112 | ? ' ' . $this->database->protectIdentifier($column['change_name']) 113 | : null; 114 | $definition = $column['definition']->sql(); 115 | $sql[] = " {$prefix} {$name}{$changeName}{$definition}"; 116 | } 117 | return \implode(',' . \PHP_EOL, $sql); 118 | } 119 | 120 | protected function renderIndexes(?string $prefix = null) : string 121 | { 122 | $sql = []; 123 | foreach ($this->indexes as $index) { 124 | $definition = $index['definition']->sql(); 125 | if ($this->condition) { 126 | $definition = \explode('(', $definition, 2); 127 | $definition = $definition[0] . $this->condition . ' (' . $definition[1]; 128 | } 129 | $sql[] = " {$prefix}{$definition}"; 130 | } 131 | return \implode(',' . \PHP_EOL, $sql); 132 | } 133 | 134 | protected function renderChecks() : string 135 | { 136 | $sql = []; 137 | foreach ($this->checks as $check) { 138 | $sql[] = ' ' . $check->sql(); 139 | } 140 | return \implode(',' . \PHP_EOL, $sql); 141 | } 142 | 143 | protected function sql(?string $prefix = null) : string 144 | { 145 | $sql = $this->renderColumns($prefix); 146 | $part = $this->renderIndexes($prefix); 147 | if ($part) { 148 | $sql .= ',' . \PHP_EOL . $part; 149 | } 150 | $part = $this->renderChecks(); 151 | if ($part) { 152 | $sql .= ',' . \PHP_EOL . $part; 153 | } 154 | return $sql; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/Manipulation/Statement.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Manipulation; 11 | 12 | use Closure; 13 | use InvalidArgumentException; 14 | 15 | /** 16 | * Class Statement. 17 | * 18 | * @see https://mariadb.com/kb/en/data-manipulation/ 19 | * 20 | * @package database 21 | */ 22 | abstract class Statement extends \Framework\Database\Statement 23 | { 24 | /** 25 | * Sets the statement options. 26 | * 27 | * @param string $option One of the OPT_* constants 28 | * @param string ...$options Each option value must be one of the OPT_* constants 29 | * 30 | * @return static 31 | */ 32 | public function options(string $option, string ...$options) : static 33 | { 34 | $this->sql['options'] = []; 35 | foreach ([$option, ...$options] as $option) { 36 | $this->sql['options'][] = $option; 37 | } 38 | return $this; 39 | } 40 | 41 | /** 42 | * Tells if the statement has options set. 43 | * 44 | * @return bool 45 | */ 46 | protected function hasOptions() : bool 47 | { 48 | return isset($this->sql['options']); 49 | } 50 | 51 | abstract protected function renderOptions() : ?string; 52 | 53 | /** 54 | * Returns an SQL part between parentheses. 55 | * 56 | * @param Closure $subquery A {@see Closure} having the current Manipulation 57 | * instance as first argument. The returned value must be scalar 58 | * 59 | * @see https://mariadb.com/kb/en/subqueries/ 60 | * @see https://mariadb.com/kb/en/built-in-functions/ 61 | * 62 | * @return string 63 | */ 64 | protected function subquery(Closure $subquery) : string 65 | { 66 | return '(' . $subquery($this->database) . ')'; 67 | } 68 | 69 | /** 70 | * Sets the LIMIT clause. 71 | * 72 | * @param int $limit 73 | * @param int|null $offset 74 | * 75 | * @see https://mariadb.com/kb/en/limit/ 76 | * 77 | * @return static 78 | */ 79 | protected function setLimit(int $limit, ?int $offset = null) : static 80 | { 81 | $this->sql['limit'] = [ 82 | 'limit' => $limit, 83 | 'offset' => $offset, 84 | ]; 85 | return $this; 86 | } 87 | 88 | /** 89 | * Renders the LIMIT clause. 90 | * 91 | * @return string|null 92 | */ 93 | protected function renderLimit() : ?string 94 | { 95 | if (!isset($this->sql['limit'])) { 96 | return null; 97 | } 98 | if ($this->sql['limit']['limit'] < 1) { 99 | throw new InvalidArgumentException('LIMIT must be greater than 0'); 100 | } 101 | $offset = $this->sql['limit']['offset']; 102 | if ($offset !== null) { 103 | if ($offset < 1) { 104 | throw new InvalidArgumentException('LIMIT OFFSET must be greater than 0'); 105 | } 106 | $offset = " OFFSET {$this->sql['limit']['offset']}"; 107 | } 108 | return " LIMIT {$this->sql['limit']['limit']}{$offset}"; 109 | } 110 | 111 | /** 112 | * Renders a column part. 113 | * 114 | * @param Closure|string $column The column name or a subquery 115 | * 116 | * @return string 117 | */ 118 | protected function renderIdentifier(Closure | string $column) : string 119 | { 120 | return $column instanceof Closure 121 | ? $this->subquery($column) 122 | : $this->database->protectIdentifier($column); 123 | } 124 | 125 | /** 126 | * Renders a column part with an optional alias name, AS clause. 127 | * 128 | * @param Closure|array|string $column The column name, 129 | * a subquery or an array where the index is the alias and the value is the column/subquery 130 | * 131 | * @return string 132 | */ 133 | protected function renderAliasedIdentifier(Closure | array | string $column) : string 134 | { 135 | if (\is_array($column)) { 136 | if (\count($column) !== 1) { 137 | throw new InvalidArgumentException('Aliased column must have only 1 key'); 138 | } 139 | $alias = (string) \array_key_first($column); 140 | return $this->renderIdentifier($column[$alias]) . ' AS ' 141 | . $this->database->protectIdentifier($alias); 142 | } 143 | return $this->renderIdentifier($column); 144 | } 145 | 146 | /** 147 | * Renders a subquery or quote a value. 148 | * 149 | * @param Closure|float|int|string|null $value A {@see Closure} for 150 | * subquery, other types to quote 151 | * 152 | * @return float|int|string 153 | */ 154 | protected function renderValue(Closure | float | int | string | null $value) : float | int | string 155 | { 156 | return $value instanceof Closure 157 | ? $this->subquery($value) 158 | : $this->database->quote($value); 159 | } 160 | 161 | /** 162 | * Renders an assignment part. 163 | * 164 | * @param string $identifier Identifier/column name 165 | * @param Closure|float|int|string|null $expression Expression/value 166 | * 167 | * @see Statement::renderValue() 168 | * @see https://mariadb.com/kb/en/assignment-operators-assignment-operator/ 169 | * 170 | * @return string 171 | */ 172 | protected function renderAssignment( 173 | string $identifier, 174 | Closure | float | int | string | null $expression 175 | ) : string { 176 | return $this->database->protectIdentifier($identifier) 177 | . ' = ' . $this->renderValue($expression); 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/Column.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns; 11 | 12 | use Closure; 13 | use Framework\Database\Database; 14 | use Framework\Database\Definition\Table\DefinitionPart; 15 | use LogicException; 16 | 17 | /** 18 | * Class Column. 19 | * 20 | * @package database 21 | */ 22 | abstract class Column extends DefinitionPart 23 | { 24 | protected Database $database; 25 | protected string $type; 26 | /** 27 | * @var array 28 | */ 29 | protected array $length; 30 | protected bool $null = false; 31 | protected bool $uniqueKey = false; 32 | protected bool $primaryKey = false; 33 | protected Closure | bool | float | int | string | null $default; 34 | protected Closure $check; 35 | protected ?string $comment; 36 | protected bool $first = false; 37 | protected ?string $after; 38 | 39 | /** 40 | * Column constructor. 41 | * 42 | * @param Database $database 43 | * @param bool|float|int|string|null ...$length 44 | */ 45 | public function __construct(Database $database, bool | float | int | string | null ...$length) 46 | { 47 | $this->database = $database; 48 | $this->length = $length; 49 | } 50 | 51 | protected function renderType() : string 52 | { 53 | if (empty($this->type)) { 54 | throw new LogicException('Column type is empty'); 55 | } 56 | return ' ' . $this->type; 57 | } 58 | 59 | protected function renderLength() : ?string 60 | { 61 | if (!isset($this->length[0])) { 62 | return null; 63 | } 64 | $length = $this->database->quote($this->length[0]); 65 | return "({$length})"; 66 | } 67 | 68 | /** 69 | * @param Closure $expression 70 | * 71 | * @return static 72 | */ 73 | public function check(Closure $expression) : static 74 | { 75 | $this->check = $expression; 76 | return $this; 77 | } 78 | 79 | protected function renderCheck() : ?string 80 | { 81 | if (!isset($this->check)) { 82 | return null; 83 | } 84 | return ' CHECK (' . ($this->check)($this->database) . ')'; 85 | } 86 | 87 | /** 88 | * @return static 89 | */ 90 | public function null() : static 91 | { 92 | $this->null = true; 93 | return $this; 94 | } 95 | 96 | /** 97 | * @return static 98 | */ 99 | public function notNull() : static 100 | { 101 | $this->null = false; 102 | return $this; 103 | } 104 | 105 | protected function renderNull() : ?string 106 | { 107 | return $this->null ? ' NULL' : ' NOT NULL'; 108 | } 109 | 110 | /** 111 | * @param Closure|bool|float|int|string|null $default 112 | * 113 | * @return static 114 | */ 115 | public function default(Closure | bool | float | int | string | null $default) : static 116 | { 117 | $this->default = $default; 118 | return $this; 119 | } 120 | 121 | protected function renderDefault() : ?string 122 | { 123 | if (!isset($this->default)) { 124 | return null; 125 | } 126 | $default = $this->default instanceof Closure 127 | ? '(' . ($this->default)($this->database) . ')' 128 | : $this->database->quote($this->default); 129 | return ' DEFAULT ' . $default; 130 | } 131 | 132 | /** 133 | * @param string $comment 134 | * 135 | * @return static 136 | */ 137 | public function comment(string $comment) : static 138 | { 139 | $this->comment = $comment; 140 | return $this; 141 | } 142 | 143 | protected function renderComment() : ?string 144 | { 145 | if (!isset($this->comment)) { 146 | return null; 147 | } 148 | return ' COMMENT ' . $this->database->quote($this->comment); 149 | } 150 | 151 | /** 152 | * @return static 153 | */ 154 | public function primaryKey() : static 155 | { 156 | $this->primaryKey = true; 157 | return $this; 158 | } 159 | 160 | protected function renderPrimaryKey() : ?string 161 | { 162 | if (!$this->primaryKey) { 163 | return null; 164 | } 165 | return ' PRIMARY KEY'; 166 | } 167 | 168 | /** 169 | * @return static 170 | */ 171 | public function uniqueKey() : static 172 | { 173 | $this->uniqueKey = true; 174 | return $this; 175 | } 176 | 177 | protected function renderUniqueKey() : ?string 178 | { 179 | if (!$this->uniqueKey) { 180 | return null; 181 | } 182 | return ' UNIQUE KEY'; 183 | } 184 | 185 | /** 186 | * @return static 187 | */ 188 | public function first() : static 189 | { 190 | $this->first = true; 191 | return $this; 192 | } 193 | 194 | protected function renderFirst() : ?string 195 | { 196 | if (!$this->first) { 197 | return null; 198 | } 199 | return ' FIRST'; 200 | } 201 | 202 | /** 203 | * @param string $column 204 | * 205 | * @return static 206 | */ 207 | public function after(string $column) : static 208 | { 209 | $this->after = $column; 210 | return $this; 211 | } 212 | 213 | protected function renderAfter() : ?string 214 | { 215 | if (!isset($this->after)) { 216 | return null; 217 | } 218 | if ($this->first) { 219 | throw new LogicException('Clauses FIRST and AFTER can not be used together'); 220 | } 221 | return ' AFTER ' . $this->database->protectIdentifier($this->after); 222 | } 223 | 224 | protected function renderTypeAttributes() : ?string 225 | { 226 | return null; 227 | } 228 | 229 | protected function sql() : string 230 | { 231 | $sql = $this->renderType(); 232 | $sql .= $this->renderLength(); 233 | $sql .= $this->renderTypeAttributes(); 234 | $sql .= $this->renderNull(); 235 | $sql .= $this->renderDefault(); 236 | $sql .= $this->renderUniqueKey(); 237 | $sql .= $this->renderPrimaryKey(); 238 | $sql .= $this->renderComment(); 239 | $sql .= $this->renderFirst(); 240 | $sql .= $this->renderAfter(); 241 | $sql .= $this->renderCheck(); 242 | return $sql; 243 | } 244 | } 245 | -------------------------------------------------------------------------------- /src/Result.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database; 11 | 12 | use Framework\Database\Result\Field; 13 | use LogicException; 14 | use mysqli_result; 15 | use OutOfBoundsException; 16 | use OutOfRangeException; 17 | 18 | /** 19 | * Class Result. 20 | * 21 | * @package database 22 | */ 23 | class Result 24 | { 25 | /** 26 | * @var mysqli_result 27 | */ 28 | protected mysqli_result $result; 29 | protected bool $buffered; 30 | protected bool $free = false; 31 | protected string $fetchClass = \stdClass::class; 32 | /** 33 | * @var array 34 | */ 35 | protected array $fetchConstructor = []; 36 | 37 | /** 38 | * Result constructor. 39 | * 40 | * @param mysqli_result $result 41 | * @param bool $buffered 42 | */ 43 | public function __construct(mysqli_result $result, bool $buffered) 44 | { 45 | $this->result = $result; 46 | $this->buffered = $buffered; 47 | } 48 | 49 | public function __destruct() 50 | { 51 | if (!$this->isFree()) { 52 | $this->free(); 53 | } 54 | } 55 | 56 | /** 57 | * Frees the memory associated with a result. 58 | */ 59 | public function free() : void 60 | { 61 | $this->checkIsFree(); 62 | $this->free = true; 63 | $this->result->free(); 64 | } 65 | 66 | public function isFree() : bool 67 | { 68 | return $this->free; 69 | } 70 | 71 | protected function checkIsFree() : void 72 | { 73 | if ($this->isFree()) { 74 | throw new LogicException('Result is already free'); 75 | } 76 | } 77 | 78 | public function isBuffered() : bool 79 | { 80 | return $this->buffered; 81 | } 82 | 83 | /** 84 | * Adjusts the result pointer to an arbitrary row in the result. 85 | * 86 | * @param int $offset The field offset. Must be between zero and the total 87 | * number of rows minus one 88 | * 89 | * @throws LogicException if is an unbuffered result 90 | * @throws OutOfBoundsException for invalid cursor offset 91 | * 92 | * @return bool 93 | */ 94 | public function moveCursor(int $offset) : bool 95 | { 96 | $this->checkIsFree(); 97 | if (!$this->isBuffered()) { 98 | throw new LogicException('Cursor cannot be moved on unbuffered results'); 99 | } 100 | if ($offset < 0 || ($offset !== 0 && $offset >= $this->result->num_rows)) { 101 | throw new OutOfRangeException( 102 | "Invalid cursor offset: {$offset}" 103 | ); 104 | } 105 | return $this->result->data_seek($offset); 106 | } 107 | 108 | /** 109 | * @param string $class 110 | * @param mixed ...$constructor 111 | * 112 | * @return static 113 | */ 114 | public function setFetchClass(string $class, mixed ...$constructor) : static 115 | { 116 | $this->fetchClass = $class; 117 | $this->fetchConstructor = $constructor; 118 | return $this; 119 | } 120 | 121 | /** 122 | * Fetches the current row as object and move the cursor to the next. 123 | * 124 | * @param string|null $class 125 | * @param mixed ...$constructor 126 | * 127 | * @return object|null 128 | */ 129 | public function fetch(?string $class = null, mixed ...$constructor) : ?object 130 | { 131 | $this->checkIsFree(); 132 | $class ??= $this->fetchClass; 133 | $constructor = $constructor ?: $this->fetchConstructor; 134 | if ($constructor) { 135 | // @phpstan-ignore-next-line 136 | return $this->result->fetch_object($class, $constructor); 137 | } 138 | // @phpstan-ignore-next-line 139 | return $this->result->fetch_object($class); 140 | } 141 | 142 | /** 143 | * Fetches all rows as objects. 144 | * 145 | * @param string|null $class 146 | * @param mixed ...$constructor 147 | * 148 | * @return array 149 | */ 150 | public function fetchAll(?string $class = null, mixed ...$constructor) : array 151 | { 152 | $this->checkIsFree(); 153 | $all = []; 154 | while ($row = $this->fetch($class, ...$constructor)) { 155 | $all[] = $row; 156 | } 157 | return $all; 158 | } 159 | 160 | /** 161 | * Fetches a specific row as object and move the cursor to the next. 162 | * 163 | * @param int $offset 164 | * @param string|null $class 165 | * @param mixed ...$constructor 166 | * 167 | * @return object 168 | */ 169 | public function fetchRow(int $offset, ?string $class = null, mixed ...$constructor) : object 170 | { 171 | $this->checkIsFree(); 172 | $this->moveCursor($offset); 173 | return $this->fetch($class, ...$constructor); 174 | } 175 | 176 | /** 177 | * Fetches the current row as array and move the cursor to the next. 178 | * 179 | * @return array|null 180 | */ 181 | public function fetchArray() : ?array 182 | { 183 | $this->checkIsFree(); 184 | return $this->result->fetch_assoc(); // @phpstan-ignore-line 185 | } 186 | 187 | /** 188 | * Fetches all rows as arrays. 189 | * 190 | * @return array> 191 | */ 192 | public function fetchArrayAll() : array 193 | { 194 | $this->checkIsFree(); 195 | return $this->result->fetch_all(\MYSQLI_ASSOC); 196 | } 197 | 198 | /** 199 | * Fetches a specific row as array and move the cursor to the next. 200 | * 201 | * @param int $offset 202 | * 203 | * @return array 204 | */ 205 | public function fetchArrayRow(int $offset) : array 206 | { 207 | $this->checkIsFree(); 208 | $this->moveCursor($offset); 209 | return $this->result->fetch_assoc(); // @phpstan-ignore-line 210 | } 211 | 212 | /** 213 | * Gets the number of rows in the result set. 214 | * 215 | * @return int|string 216 | */ 217 | public function numRows() : int | string 218 | { 219 | $this->checkIsFree(); 220 | return $this->result->num_rows; 221 | } 222 | 223 | /** 224 | * Returns an array of objects representing the fields in a result set. 225 | * 226 | * @return array an array of objects which contains field 227 | * definition information 228 | */ 229 | public function fetchFields() : array 230 | { 231 | $this->checkIsFree(); 232 | $fields = []; 233 | foreach ($this->result->fetch_fields() as $field) { 234 | $fields[] = new Field($field); 235 | } 236 | return $fields; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /src/Manipulation/Insert.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Manipulation; 11 | 12 | use Closure; 13 | use InvalidArgumentException; 14 | use LogicException; 15 | 16 | /** 17 | * Class Insert. 18 | * 19 | * @see https://mariadb.com/kb/en/insert/ 20 | * 21 | * @package database 22 | */ 23 | class Insert extends Statement 24 | { 25 | use Traits\Select; 26 | use Traits\Set; 27 | use Traits\Values; 28 | 29 | /** 30 | * @see https://mariadb.com/kb/en/insert-delayed/ 31 | */ 32 | public const string OPT_DELAYED = 'DELAYED'; 33 | /** 34 | * Convert errors to warnings, which will not stop inserts of additional rows. 35 | * 36 | * @see https://mariadb.com/kb/en/insert-ignore/ 37 | */ 38 | public const string OPT_IGNORE = 'IGNORE'; 39 | /** 40 | * @see https://mariadb.com/kb/en/high_priority-and-low_priority/ 41 | */ 42 | public const string OPT_HIGH_PRIORITY = 'HIGH_PRIORITY'; 43 | /** 44 | * @see https://mariadb.com/kb/en/high_priority-and-low_priority/ 45 | */ 46 | public const string OPT_LOW_PRIORITY = 'LOW_PRIORITY'; 47 | 48 | protected function renderOptions() : ?string 49 | { 50 | if (!$this->hasOptions()) { 51 | return null; 52 | } 53 | $options = $this->sql['options']; 54 | foreach ($options as &$option) { 55 | $input = $option; 56 | $option = \strtoupper($option); 57 | if (!\in_array($option, [ 58 | static::OPT_DELAYED, 59 | static::OPT_IGNORE, 60 | static::OPT_LOW_PRIORITY, 61 | static::OPT_HIGH_PRIORITY, 62 | ], true)) { 63 | throw new InvalidArgumentException("Invalid option: {$input}"); 64 | } 65 | } 66 | unset($option); 67 | $intersection = \array_intersect( 68 | $options, 69 | [static::OPT_DELAYED, static::OPT_HIGH_PRIORITY, static::OPT_LOW_PRIORITY] 70 | ); 71 | if (\count($intersection) > 1) { 72 | throw new LogicException( 73 | 'Options LOW_PRIORITY, DELAYED or HIGH_PRIORITY can not be used together' 74 | ); 75 | } 76 | $options = \implode(' ', $options); 77 | return " {$options}"; 78 | } 79 | 80 | /** 81 | * Sets the INTO table. 82 | * 83 | * @param string $table Table name 84 | * 85 | * @return static 86 | */ 87 | public function into(string $table) : static 88 | { 89 | $this->sql['into'] = $table; 90 | return $this; 91 | } 92 | 93 | /** 94 | * Renders the "INTO $table" clause. 95 | * 96 | * @throws LogicException if INTO was not set 97 | * 98 | * @return string 99 | */ 100 | protected function renderInto() : string 101 | { 102 | if (!isset($this->sql['into'])) { 103 | throw new LogicException('INTO table must be set'); 104 | } 105 | return ' INTO ' . $this->renderIdentifier($this->sql['into']); 106 | } 107 | 108 | /** 109 | * Sets the INTO columns. 110 | * 111 | * @param string $column Column name 112 | * @param string ...$columns Extra column names 113 | * 114 | * @return static 115 | */ 116 | public function columns(string $column, string ...$columns) : static 117 | { 118 | $this->sql['columns'] = [$column, ...$columns]; 119 | return $this; 120 | } 121 | 122 | /** 123 | * Renders the INTO $table "(...$columns)" part. 124 | * 125 | * @return string|null The imploded columns or null if none was set 126 | */ 127 | protected function renderColumns() : ?string 128 | { 129 | if (!isset($this->sql['columns'])) { 130 | return null; 131 | } 132 | $columns = []; 133 | foreach ($this->sql['columns'] as $column) { 134 | $columns[] = $this->renderIdentifier($column); 135 | } 136 | $columns = \implode(', ', $columns); 137 | return " ({$columns})"; 138 | } 139 | 140 | /** 141 | * Sets the ON DUPLICATE KEY UPDATE part. 142 | * 143 | * @param array|object $columns Column name 144 | * as key/property, column value/expression as value 145 | * 146 | * @see https://mariadb.com/kb/en/insert-on-duplicate-key-update/ 147 | * 148 | * @return static 149 | */ 150 | public function onDuplicateKeyUpdate(array | object $columns) : static 151 | { 152 | $this->sql['on_duplicate'] = (array) $columns; 153 | return $this; 154 | } 155 | 156 | /** 157 | * Renders the ON DUPLICATE KEY UPDATE part. 158 | * 159 | * @return string|null The part or null if it was not set 160 | */ 161 | protected function renderOnDuplicateKeyUpdate() : ?string 162 | { 163 | if (!isset($this->sql['on_duplicate'])) { 164 | return null; 165 | } 166 | $onDuplicate = []; 167 | foreach ($this->sql['on_duplicate'] as $column => $value) { 168 | $onDuplicate[] = $this->renderAssignment($column, $value); 169 | } 170 | $onDuplicate = \implode(', ', $onDuplicate); 171 | return " ON DUPLICATE KEY UPDATE {$onDuplicate}"; 172 | } 173 | 174 | /** 175 | * Check for conflicts in the INSERT statement. 176 | * 177 | * @throws LogicException if it has conflicts 178 | */ 179 | protected function checkRowStatementsConflict() : void 180 | { 181 | if (!isset($this->sql['values']) 182 | && !isset($this->sql['select']) 183 | && !$this->hasSet() 184 | ) { 185 | throw new LogicException( 186 | 'The INSERT INTO must be followed by VALUES, SET or SELECT statement' 187 | ); 188 | } 189 | } 190 | 191 | /** 192 | * Renders the INSERT statement. 193 | * 194 | * @return string 195 | */ 196 | public function sql() : string 197 | { 198 | $sql = 'INSERT' . \PHP_EOL; 199 | $part = $this->renderOptions(); 200 | if ($part) { 201 | $sql .= $part . \PHP_EOL; 202 | } 203 | $sql .= $this->renderInto() . \PHP_EOL; 204 | $part = $this->renderColumns(); 205 | if ($part) { 206 | $sql .= $part . \PHP_EOL; 207 | } 208 | $this->checkRowStatementsConflict(); 209 | $part = $this->renderValues(); 210 | if ($part) { 211 | $sql .= $part . \PHP_EOL; 212 | } 213 | $part = $this->renderSetCheckingConflicts(); 214 | if ($part) { 215 | $sql .= $part . \PHP_EOL; 216 | } 217 | $part = $this->renderSelect(); 218 | if ($part) { 219 | $sql .= $part . \PHP_EOL; 220 | } 221 | $part = $this->renderOnDuplicateKeyUpdate(); 222 | if ($part) { 223 | $sql .= $part . \PHP_EOL; 224 | } 225 | return $sql; 226 | } 227 | 228 | /** 229 | * Runs the INSERT statement. 230 | * 231 | * @return int|string The number of affected rows 232 | */ 233 | public function run() : int | string 234 | { 235 | return $this->database->exec($this->sql()); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /src/Result/Field.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Result; 11 | 12 | /** 13 | * Class Field. 14 | * 15 | * @package database 16 | */ 17 | readonly class Field 18 | { 19 | /** 20 | * The name of the column. 21 | */ 22 | public string $name; 23 | /** 24 | * Original column name if an alias was specified. 25 | */ 26 | public string $orgname; 27 | /** 28 | * The name of the table this field belongs to (if not calculated). 29 | */ 30 | public string $table; 31 | /** 32 | * Original table name if an alias was specified. 33 | */ 34 | public string $orgtable; 35 | /** 36 | * Reserved for default value, currently always "". 37 | */ 38 | public string $def; 39 | /** 40 | * Database (since PHP 5.3.6). 41 | */ 42 | public string $db; 43 | /** 44 | * The catalog name, always "def" (since PHP 5.3.6). 45 | */ 46 | public string $catalog; 47 | /** 48 | * The maximum width of the field for the result set. As of PHP 8.1, this 49 | * value is always 0. 50 | */ 51 | public int $maxLength; 52 | /** 53 | * The width of the field, in bytes, as specified in the table definition. 54 | * Note that this number (bytes) might differ from your table definition 55 | * value (characters), depending on the character set you use. For example, 56 | * the character set utf8 has 3 bytes per character, so varchar(10) will 57 | * return a length of 30 for utf8 (10*3), but return 10 for latin1 (10*1). 58 | */ 59 | public int $length; 60 | /** 61 | * The character set number (id) for the field. 62 | */ 63 | public int $charsetnr; 64 | /** 65 | * An integer representing the bit-flags for the field. 66 | */ 67 | public int $flags; 68 | /** 69 | * The data type used for this field. 70 | */ 71 | public int $type; 72 | /** 73 | * The number of decimals used (for integer fields). 74 | */ 75 | public int $decimals; 76 | /** 77 | * The name of the data type used for this field. 78 | */ 79 | public ?string $typeName; 80 | /** 81 | * Tells if field is defined as BINARY. 82 | */ 83 | public bool $isBinary; 84 | /** 85 | * Tells if field is defined as BLOB. 86 | */ 87 | public bool $isBlob; 88 | /** 89 | * Tells if field is defined as ENUM. 90 | */ 91 | public bool $isEnum; 92 | /** 93 | * Tells if field is part of GROUP BY. 94 | */ 95 | public bool $isGroup; 96 | /** 97 | * Tells if field is defined as NUMERIC. 98 | */ 99 | public bool $isNum; 100 | /** 101 | * Tells if field is defined as SET. 102 | */ 103 | public bool $isSet; 104 | /** 105 | * Tells if field is defined as TIMESTAMP. 106 | */ 107 | public bool $isTimestamp; 108 | /** 109 | * Tells if field is defined as UNSIGNED. 110 | */ 111 | public bool $isUnsigned; 112 | /** 113 | * Tells if field is defined as ZEROFILL. 114 | */ 115 | public bool $isZerofill; 116 | /** 117 | * Tells if field is defined as AUTO_INCREMENT. 118 | */ 119 | public bool $isAutoIncrement; 120 | /** 121 | * Tells if field is part of an index. 122 | */ 123 | public bool $isMultipleKey; 124 | /** 125 | * Tells if field is defined as NOT NULL. 126 | */ 127 | public bool $isNotNull; 128 | /** 129 | * Tells if field is part of a multi-index. 130 | */ 131 | public bool $isPartKey; 132 | /** 133 | * Tells if field is part of a primary index. 134 | */ 135 | public bool $isPriKey; 136 | /** 137 | * Tells if field is part of a unique index. 138 | */ 139 | public bool $isUniqueKey; 140 | public bool $isNoDefaultValue; 141 | public bool $isOnUpdateNow; 142 | 143 | public function __construct(\stdClass $field) 144 | { 145 | foreach ((array) $field as $key => $value) { 146 | $key = \ucwords($key, '_'); 147 | $key = \strtr($key, ['_' => '']); 148 | $key[0] = \strtolower($key[0]); 149 | $this->{$key} = $value; 150 | } 151 | $this->setTypeName(); 152 | $this->setFlags(); 153 | } 154 | 155 | protected function setTypeName() : void 156 | { 157 | $this->typeName = match ($this->type) { 158 | \MYSQLI_TYPE_BIT => 'bit', 159 | \MYSQLI_TYPE_BLOB => 'blob', 160 | \MYSQLI_TYPE_CHAR => 'char', 161 | \MYSQLI_TYPE_DATE => 'date', 162 | \MYSQLI_TYPE_DATETIME => 'datetime', 163 | \MYSQLI_TYPE_DECIMAL => 'decimal', 164 | \MYSQLI_TYPE_DOUBLE => 'double', 165 | \MYSQLI_TYPE_ENUM => 'enum', 166 | \MYSQLI_TYPE_FLOAT => 'float', 167 | \MYSQLI_TYPE_GEOMETRY => 'geometry', 168 | \MYSQLI_TYPE_INT24 => 'int24', 169 | //\MYSQLI_TYPE_INTERVAL => 'interval', 170 | \MYSQLI_TYPE_JSON => 'json', 171 | \MYSQLI_TYPE_LONG => 'long', 172 | \MYSQLI_TYPE_LONG_BLOB => 'long_blob', 173 | \MYSQLI_TYPE_LONGLONG => 'longlong', 174 | \MYSQLI_TYPE_MEDIUM_BLOB => 'medium_blob', 175 | \MYSQLI_TYPE_NEWDATE => 'newdate', 176 | \MYSQLI_TYPE_NEWDECIMAL => 'newdecimal', 177 | \MYSQLI_TYPE_NULL => 'null', 178 | \MYSQLI_TYPE_SET => 'set', 179 | \MYSQLI_TYPE_SHORT => 'short', 180 | \MYSQLI_TYPE_STRING => 'string', 181 | \MYSQLI_TYPE_TIME => 'time', 182 | \MYSQLI_TYPE_TIMESTAMP => 'timestamp', 183 | //\MYSQLI_TYPE_TINY => 'tiny', 184 | \MYSQLI_TYPE_TINY_BLOB => 'tiny_blob', 185 | \MYSQLI_TYPE_VAR_STRING => 'var_string', 186 | \MYSQLI_TYPE_YEAR => 'year', 187 | default => null 188 | }; 189 | } 190 | 191 | protected function setFlags() : void 192 | { 193 | $this->isBinary = (bool) ($this->flags & \MYSQLI_BINARY_FLAG); 194 | $this->isBlob = (bool) ($this->flags & \MYSQLI_BLOB_FLAG); 195 | $this->isEnum = (bool) ($this->flags & \MYSQLI_ENUM_FLAG); 196 | $this->isGroup = (bool) ($this->flags & \MYSQLI_GROUP_FLAG); 197 | $this->isNum = (bool) ($this->flags & \MYSQLI_NUM_FLAG); 198 | $this->isSet = (bool) ($this->flags & \MYSQLI_SET_FLAG); 199 | $this->isTimestamp = (bool) ($this->flags & \MYSQLI_TIMESTAMP_FLAG); 200 | $this->isUnsigned = (bool) ($this->flags & \MYSQLI_UNSIGNED_FLAG); 201 | $this->isZerofill = (bool) ($this->flags & \MYSQLI_ZEROFILL_FLAG); 202 | $this->isAutoIncrement = (bool) ($this->flags & \MYSQLI_AUTO_INCREMENT_FLAG); 203 | $this->isMultipleKey = (bool) ($this->flags & \MYSQLI_MULTIPLE_KEY_FLAG); 204 | $this->isNotNull = (bool) ($this->flags & \MYSQLI_NOT_NULL_FLAG); 205 | $this->isPartKey = (bool) ($this->flags & \MYSQLI_PART_KEY_FLAG); 206 | $this->isPriKey = (bool) ($this->flags & \MYSQLI_PRI_KEY_FLAG); 207 | $this->isUniqueKey = (bool) ($this->flags & \MYSQLI_UNIQUE_KEY_FLAG); 208 | $this->isNoDefaultValue = (bool) ($this->flags & \MYSQLI_NO_DEFAULT_VALUE_FLAG); 209 | $this->isOnUpdateNow = (bool) ($this->flags & \MYSQLI_ON_UPDATE_NOW_FLAG); 210 | } 211 | } 212 | -------------------------------------------------------------------------------- /src/Manipulation/LoadData.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Manipulation; 11 | 12 | use InvalidArgumentException; 13 | use LogicException; 14 | 15 | /** 16 | * Class LoadData. 17 | * 18 | * @see https://mariadb.com/kb/en/load-data-infile/ 19 | * 20 | * @package database 21 | */ 22 | class LoadData extends Statement 23 | { 24 | use Traits\Set; 25 | 26 | /** 27 | * @see https://mariadb.com/kb/en/high_priority-and-low_priority/ 28 | */ 29 | public const string OPT_LOW_PRIORITY = 'LOW_PRIORITY'; 30 | /** 31 | * @see https://mariadb.com/kb/en/load-data-infile/#priority-and-concurrency 32 | */ 33 | public const string OPT_CONCURRENT = 'CONCURRENT'; 34 | /** 35 | * @see https://mariadb.com/kb/en/load-data-infile/#load-data-local-infile 36 | */ 37 | public const string OPT_LOCAL = 'LOCAL'; 38 | 39 | protected function renderOptions() : ?string 40 | { 41 | if (!$this->hasOptions()) { 42 | return null; 43 | } 44 | $options = $this->sql['options']; 45 | foreach ($options as &$option) { 46 | $input = $option; 47 | $option = \strtoupper($option); 48 | if (!\in_array($option, [ 49 | static::OPT_LOW_PRIORITY, 50 | static::OPT_CONCURRENT, 51 | static::OPT_LOCAL, 52 | ], true)) { 53 | throw new InvalidArgumentException("Invalid option: {$input}"); 54 | } 55 | } 56 | unset($option); 57 | $intersection = \array_intersect( 58 | $options, 59 | [static::OPT_LOW_PRIORITY, static::OPT_CONCURRENT] 60 | ); 61 | if (\count($intersection) > 1) { 62 | throw new LogicException('Options LOW_PRIORITY and CONCURRENT can not be used together'); 63 | } 64 | return \implode(' ', $options); 65 | } 66 | 67 | /** 68 | * @param string $filename 69 | * 70 | * @return static 71 | */ 72 | public function infile(string $filename) : static 73 | { 74 | $this->sql['infile'] = $filename; 75 | return $this; 76 | } 77 | 78 | protected function renderInfile() : string 79 | { 80 | if (empty($this->sql['infile'])) { 81 | throw new LogicException('INFILE statement is required'); 82 | } 83 | $filename = $this->database->quote($this->sql['infile']); 84 | return " INFILE {$filename}"; 85 | } 86 | 87 | /** 88 | * @param string $table 89 | * 90 | * @return static 91 | */ 92 | public function intoTable(string $table) : static 93 | { 94 | $this->sql['table'] = $table; 95 | return $this; 96 | } 97 | 98 | protected function renderIntoTable() : string 99 | { 100 | if (empty($this->sql['table'])) { 101 | throw new LogicException('Table is required'); 102 | } 103 | return ' INTO TABLE ' . $this->database->protectIdentifier($this->sql['table']); 104 | } 105 | 106 | /** 107 | * @param string $charset 108 | * 109 | * @see https://mariadb.com/kb/en/supported-character-sets-and-collations/ 110 | * 111 | * @return static 112 | */ 113 | public function charset(string $charset) : static 114 | { 115 | $this->sql['charset'] = $charset; 116 | return $this; 117 | } 118 | 119 | protected function renderCharset() : ?string 120 | { 121 | if (!isset($this->sql['charset'])) { 122 | return null; 123 | } 124 | return " CHARACTER SET {$this->sql['charset']}"; 125 | } 126 | 127 | /** 128 | * @param string $str 129 | * 130 | * @return static 131 | */ 132 | public function columnsTerminatedBy(string $str) : static 133 | { 134 | $this->sql['columns_terminated_by'] = $this->database->quote($str); 135 | return $this; 136 | } 137 | 138 | /** 139 | * @param string $char 140 | * @param bool $optionally 141 | * 142 | * @return static 143 | */ 144 | public function columnsEnclosedBy(string $char, bool $optionally = false) : static 145 | { 146 | $this->sql['columns_enclosed_by'] = $this->database->quote($char); 147 | $this->sql['columns_enclosed_by_opt'] = $optionally; 148 | return $this; 149 | } 150 | 151 | /** 152 | * @param string $char 153 | * 154 | * @return static 155 | */ 156 | public function columnsEscapedBy(string $char) : static 157 | { 158 | $this->sql['columns_escaped_by'] = $this->database->quote($char); 159 | return $this; 160 | } 161 | 162 | protected function renderColumns() : ?string 163 | { 164 | if (!isset($this->sql['columns_terminated_by']) 165 | && !isset($this->sql['columns_enclosed_by']) 166 | && !isset($this->sql['columns_escaped_by'])) { 167 | return null; 168 | } 169 | $part = ' COLUMNS' . \PHP_EOL; 170 | if (isset($this->sql['columns_terminated_by'])) { 171 | $part .= ' TERMINATED BY ' . $this->sql['columns_terminated_by'] . \PHP_EOL; 172 | } 173 | if (isset($this->sql['columns_enclosed_by'])) { 174 | if (isset($this->sql['columns_enclosed_by_opt'])) { 175 | $part .= ' OPTIONALLY'; 176 | } 177 | $part .= ' ENCLOSED BY ' . $this->sql['columns_enclosed_by'] . \PHP_EOL; 178 | } 179 | if (isset($this->sql['columns_escaped_by'])) { 180 | $part .= ' ESCAPED BY ' . $this->sql['columns_escaped_by'] . \PHP_EOL; 181 | } 182 | return $part; 183 | } 184 | 185 | /** 186 | * @param string $str 187 | * 188 | * @return static 189 | */ 190 | public function linesStartingBy(string $str) : static 191 | { 192 | $this->sql['lines_starting_by'] = $this->database->quote($str); 193 | return $this; 194 | } 195 | 196 | /** 197 | * @param string $str 198 | * 199 | * @return static 200 | */ 201 | public function linesTerminatedBy(string $str) : static 202 | { 203 | $this->sql['lines_terminated_by'] = $this->database->quote($str); 204 | return $this; 205 | } 206 | 207 | protected function renderLines() : ?string 208 | { 209 | if (!isset($this->sql['lines_starting_by']) 210 | && !isset($this->sql['lines_terminated_by'])) { 211 | return null; 212 | } 213 | $part = ' LINES' . \PHP_EOL; 214 | if (isset($this->sql['lines_starting_by'])) { 215 | $part .= ' STARTING BY ' . $this->sql['lines_starting_by'] . \PHP_EOL; 216 | } 217 | if (isset($this->sql['lines_terminated_by'])) { 218 | $part .= ' TERMINATED BY ' . $this->sql['lines_terminated_by'] . \PHP_EOL; 219 | } 220 | return $part; 221 | } 222 | 223 | /** 224 | * @param int $number 225 | * 226 | * @return static 227 | */ 228 | public function ignoreLines(int $number) : static 229 | { 230 | $this->sql['ignore_lines'] = $number; 231 | return $this; 232 | } 233 | 234 | protected function renderIgnoreLines() : ?string 235 | { 236 | if (!isset($this->sql['ignore_lines'])) { 237 | return null; 238 | } 239 | return " IGNORE {$this->sql['ignore_lines']} LINES"; 240 | } 241 | 242 | /** 243 | * @return string 244 | */ 245 | public function sql() : string 246 | { 247 | $sql = 'LOAD DATA' . \PHP_EOL; 248 | $part = $this->renderOptions(); 249 | if ($part) { 250 | $sql .= $part . \PHP_EOL; 251 | } 252 | $sql .= $this->renderInfile() . \PHP_EOL; 253 | $sql .= $this->renderIntoTable() . \PHP_EOL; 254 | $part = $this->renderCharset(); 255 | if ($part) { 256 | $sql .= $part . \PHP_EOL; 257 | } 258 | $sql .= $this->renderColumns(); 259 | $sql .= $this->renderLines(); 260 | $part = $this->renderIgnoreLines(); 261 | if ($part) { 262 | $sql .= $part . \PHP_EOL; 263 | } 264 | return $sql; 265 | } 266 | 267 | public function run() : int | string 268 | { 269 | return $this->database->exec($this->sql()); 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/Definition/Table/Columns/ColumnDefinition.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table\Columns; 11 | 12 | use Framework\Database\Database; 13 | use Framework\Database\Definition\Table\Columns\DateTime\DateColumn; 14 | use Framework\Database\Definition\Table\Columns\DateTime\DatetimeColumn; 15 | use Framework\Database\Definition\Table\Columns\DateTime\TimeColumn; 16 | use Framework\Database\Definition\Table\Columns\DateTime\TimestampColumn; 17 | use Framework\Database\Definition\Table\Columns\DateTime\YearColumn; 18 | use Framework\Database\Definition\Table\Columns\Geometry\GeometryCollectionColumn; 19 | use Framework\Database\Definition\Table\Columns\Geometry\GeometryColumn; 20 | use Framework\Database\Definition\Table\Columns\Geometry\LinestringColumn; 21 | use Framework\Database\Definition\Table\Columns\Geometry\MultilinestringColumn; 22 | use Framework\Database\Definition\Table\Columns\Geometry\MultipointColumn; 23 | use Framework\Database\Definition\Table\Columns\Geometry\MultipolygonColumn; 24 | use Framework\Database\Definition\Table\Columns\Geometry\PointColumn; 25 | use Framework\Database\Definition\Table\Columns\Geometry\PolygonColumn; 26 | use Framework\Database\Definition\Table\Columns\Numeric\BigintColumn; 27 | use Framework\Database\Definition\Table\Columns\Numeric\BitColumn; 28 | use Framework\Database\Definition\Table\Columns\Numeric\BooleanColumn; 29 | use Framework\Database\Definition\Table\Columns\Numeric\DecimalColumn; 30 | use Framework\Database\Definition\Table\Columns\Numeric\FloatColumn; 31 | use Framework\Database\Definition\Table\Columns\Numeric\IntColumn; 32 | use Framework\Database\Definition\Table\Columns\Numeric\MediumintColumn; 33 | use Framework\Database\Definition\Table\Columns\Numeric\SmallintColumn; 34 | use Framework\Database\Definition\Table\Columns\Numeric\TinyintColumn; 35 | use Framework\Database\Definition\Table\Columns\String\BinaryColumn; 36 | use Framework\Database\Definition\Table\Columns\String\BlobColumn; 37 | use Framework\Database\Definition\Table\Columns\String\CharColumn; 38 | use Framework\Database\Definition\Table\Columns\String\EnumColumn; 39 | use Framework\Database\Definition\Table\Columns\String\JsonColumn; 40 | use Framework\Database\Definition\Table\Columns\String\LongblobColumn; 41 | use Framework\Database\Definition\Table\Columns\String\LongtextColumn; 42 | use Framework\Database\Definition\Table\Columns\String\MediumblobColumn; 43 | use Framework\Database\Definition\Table\Columns\String\MediumtextColumn; 44 | use Framework\Database\Definition\Table\Columns\String\SetColumn; 45 | use Framework\Database\Definition\Table\Columns\String\TextColumn; 46 | use Framework\Database\Definition\Table\Columns\String\TinyblobColumn; 47 | use Framework\Database\Definition\Table\Columns\String\TinytextColumn; 48 | use Framework\Database\Definition\Table\Columns\String\VarbinaryColumn; 49 | use Framework\Database\Definition\Table\Columns\String\VarcharColumn; 50 | use Framework\Database\Definition\Table\DefinitionPart; 51 | 52 | /** 53 | * Class ColumnDefinition. 54 | * 55 | * @see https://mariadb.com/kb/en/create-table/#index-definitions 56 | * 57 | * @package database 58 | */ 59 | class ColumnDefinition extends DefinitionPart 60 | { 61 | protected Database $database; 62 | protected Column $column; 63 | 64 | public function __construct(Database $database) 65 | { 66 | $this->database = $database; 67 | } 68 | 69 | public function int(?int $maximum = null) : IntColumn 70 | { 71 | return $this->column = new IntColumn($this->database, $maximum); 72 | } 73 | 74 | public function bigint(?int $maximum = null) : BigintColumn 75 | { 76 | return $this->column = new BigintColumn($this->database, $maximum); 77 | } 78 | 79 | public function tinyint(?int $maximum = null) : TinyintColumn 80 | { 81 | return $this->column = new TinyintColumn($this->database, $maximum); 82 | } 83 | 84 | public function decimal(?int $maximum = null, ?int $decimals = null) : DecimalColumn 85 | { 86 | return $this->column = new DecimalColumn($this->database, $maximum, $decimals); 87 | } 88 | 89 | public function float(?int $maximum = null, ?int $decimals = null) : FloatColumn 90 | { 91 | return $this->column = new FloatColumn($this->database, $maximum, $decimals); 92 | } 93 | 94 | public function mediumint(?int $maximum = null) : MediumintColumn 95 | { 96 | return $this->column = new MediumintColumn($this->database, $maximum); 97 | } 98 | 99 | public function smallint(?int $maximum = null) : SmallintColumn 100 | { 101 | return $this->column = new SmallintColumn($this->database, $maximum); 102 | } 103 | 104 | public function boolean(?int $maximum = null) : BooleanColumn 105 | { 106 | return $this->column = new BooleanColumn($this->database, $maximum); 107 | } 108 | 109 | public function varchar(?int $maximum = null) : VarcharColumn 110 | { 111 | return $this->column = new VarcharColumn($this->database, $maximum); 112 | } 113 | 114 | public function char(?int $maximum = null) : CharColumn 115 | { 116 | return $this->column = new CharColumn($this->database, $maximum); 117 | } 118 | 119 | public function enum(string $value, string ...$values) : EnumColumn 120 | { 121 | return $this->column = new EnumColumn($this->database, $value, ...$values); 122 | } 123 | 124 | public function set(string $value, string ...$values) : SetColumn 125 | { 126 | return $this->column = new SetColumn($this->database, $value, ...$values); 127 | } 128 | 129 | public function text(?int $maximum = null) : TextColumn 130 | { 131 | return $this->column = new TextColumn($this->database, $maximum); 132 | } 133 | 134 | public function longtext() : LongtextColumn 135 | { 136 | return $this->column = new LongtextColumn($this->database); 137 | } 138 | 139 | public function mediumtext() : MediumtextColumn 140 | { 141 | return $this->column = new MediumtextColumn($this->database); 142 | } 143 | 144 | public function tinytext() : TinytextColumn 145 | { 146 | return $this->column = new TinytextColumn($this->database); 147 | } 148 | 149 | public function json() : JsonColumn 150 | { 151 | return $this->column = new JsonColumn($this->database); 152 | } 153 | 154 | public function blob() : BlobColumn 155 | { 156 | return $this->column = new BlobColumn($this->database); 157 | } 158 | 159 | public function tinyblob() : TinyblobColumn 160 | { 161 | return $this->column = new TinyblobColumn($this->database); 162 | } 163 | 164 | public function mediumblob() : MediumblobColumn 165 | { 166 | return $this->column = new MediumblobColumn($this->database); 167 | } 168 | 169 | public function longblob() : LongblobColumn 170 | { 171 | return $this->column = new LongblobColumn($this->database); 172 | } 173 | 174 | public function bit() : BitColumn 175 | { 176 | return $this->column = new BitColumn($this->database); 177 | } 178 | 179 | public function binary() : BinaryColumn 180 | { 181 | return $this->column = new BinaryColumn($this->database); 182 | } 183 | 184 | public function varbinary() : VarbinaryColumn 185 | { 186 | return $this->column = new VarbinaryColumn($this->database); 187 | } 188 | 189 | public function date() : DateColumn 190 | { 191 | return $this->column = new DateColumn($this->database); 192 | } 193 | 194 | public function time() : TimeColumn 195 | { 196 | return $this->column = new TimeColumn($this->database); 197 | } 198 | 199 | public function datetime() : DatetimeColumn 200 | { 201 | return $this->column = new DatetimeColumn($this->database); 202 | } 203 | 204 | public function timestamp() : TimestampColumn 205 | { 206 | return $this->column = new TimestampColumn($this->database); 207 | } 208 | 209 | public function year() : YearColumn 210 | { 211 | return $this->column = new YearColumn($this->database); 212 | } 213 | 214 | public function geometrycollection() : GeometryCollectionColumn 215 | { 216 | return $this->column = new GeometryCollectionColumn($this->database); 217 | } 218 | 219 | public function geometry() : GeometryColumn 220 | { 221 | return $this->column = new GeometryColumn($this->database); 222 | } 223 | 224 | public function linestring() : LinestringColumn 225 | { 226 | return $this->column = new LinestringColumn($this->database); 227 | } 228 | 229 | public function multilinestring() : MultilinestringColumn 230 | { 231 | return $this->column = new MultilinestringColumn($this->database); 232 | } 233 | 234 | public function multipoint() : MultipointColumn 235 | { 236 | return $this->column = new MultipointColumn($this->database); 237 | } 238 | 239 | public function multipolygon() : MultipolygonColumn 240 | { 241 | return $this->column = new MultipolygonColumn($this->database); 242 | } 243 | 244 | public function point() : PointColumn 245 | { 246 | return $this->column = new PointColumn($this->database); 247 | } 248 | 249 | public function polygon() : PolygonColumn 250 | { 251 | return $this->column = new PolygonColumn($this->database); 252 | } 253 | 254 | protected function sql() : string 255 | { 256 | return $this->column->sql(); 257 | } 258 | } 259 | -------------------------------------------------------------------------------- /.phpstorm.meta.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace PHPSTORM_META; 11 | 12 | registerArgumentsSet( 13 | 'charsets', 14 | 'armscii8', 15 | 'ascii', 16 | 'big5', 17 | 'binary', 18 | 'cp1250', 19 | 'cp1251', 20 | 'cp1256', 21 | 'cp1257', 22 | 'cp850', 23 | 'cp852', 24 | 'cp866', 25 | 'cp932', 26 | 'dec8', 27 | 'eucjpms', 28 | 'euckr', 29 | 'gb2312', 30 | 'gbk', 31 | 'geostd8', 32 | 'greek', 33 | 'hebrew', 34 | 'hp8', 35 | 'keybcs2', 36 | 'koi8r', 37 | 'koi8u', 38 | 'latin1', 39 | 'latin2', 40 | 'latin5', 41 | 'latin7', 42 | 'macce', 43 | 'macroman', 44 | 'sjis', 45 | 'swe7', 46 | 'tis620', 47 | 'ucs2', 48 | 'ujis', 49 | 'utf16', 50 | 'utf16le', 51 | 'utf32', 52 | 'utf8', 53 | 'utf8mb3', 54 | 'utf8mb4', 55 | ); 56 | expectedArguments( 57 | \Framework\Database\Database::setCollations(), 58 | 0, 59 | argumentsSet('charsets') 60 | ); 61 | expectedArguments( 62 | \Framework\Database\Definition\AlterSchema::charset(), 63 | 0, 64 | argumentsSet('charsets') 65 | ); 66 | expectedArguments( 67 | \Framework\Database\Definition\AlterTable::charset(), 68 | 0, 69 | argumentsSet('charsets') 70 | ); 71 | expectedArguments( 72 | \Framework\Database\Definition\AlterTable::convertToCharset(), 73 | 0, 74 | argumentsSet('charsets') 75 | ); 76 | expectedArguments( 77 | \Framework\Database\Definition\CreateSchema::charset(), 78 | 0, 79 | argumentsSet('charsets') 80 | ); 81 | expectedArguments( 82 | \Framework\Database\Definition\Table\Columns\String\StringDataType::charset(), 83 | 0, 84 | argumentsSet('charsets') 85 | ); 86 | expectedArguments( 87 | \Framework\Database\Manipulation\LoadData::charset(), 88 | 0, 89 | argumentsSet('charsets') 90 | ); 91 | expectedArguments( 92 | \Framework\Database\Manipulation\Select::intoOutfile(), 93 | 1, 94 | argumentsSet('charsets') 95 | ); 96 | registerArgumentsSet( 97 | 'definition_alter_table_algo', 98 | \Framework\Database\Definition\AlterTable::ALGO_COPY, 99 | \Framework\Database\Definition\AlterTable::ALGO_DEFAULT, 100 | \Framework\Database\Definition\AlterTable::ALGO_INPLACE, 101 | \Framework\Database\Definition\AlterTable::ALGO_INSTANT, 102 | \Framework\Database\Definition\AlterTable::ALGO_NOCOPY, 103 | 'COPY', 104 | 'DEFAULT', 105 | 'INPLACE', 106 | 'INSTANT', 107 | 'NOCOPY', 108 | ); 109 | expectedArguments( 110 | \Framework\Database\Definition\AlterTable::algorithm(), 111 | 0, 112 | argumentsSet('definition_alter_table_algo') 113 | ); 114 | registerArgumentsSet( 115 | 'definition_alter_table_lock', 116 | \Framework\Database\Definition\AlterTable::LOCK_DEFAULT, 117 | \Framework\Database\Definition\AlterTable::LOCK_EXCLUSIVE, 118 | \Framework\Database\Definition\AlterTable::LOCK_NONE, 119 | \Framework\Database\Definition\AlterTable::LOCK_SHARED, 120 | 'DEFAULT', 121 | 'EXCLUSIVE', 122 | 'NONE', 123 | 'SHARED', 124 | ); 125 | expectedArguments( 126 | \Framework\Database\Definition\AlterTable::lock(), 127 | 0, 128 | argumentsSet('definition_alter_table_lock') 129 | ); 130 | registerArgumentsSet( 131 | 'manipulation_delete_opt', 132 | \Framework\Database\Manipulation\Delete::OPT_IGNORE, 133 | \Framework\Database\Manipulation\Delete::OPT_LOW_PRIORITY, 134 | \Framework\Database\Manipulation\Delete::OPT_QUICK, 135 | 'IGNORE', 136 | 'LOW_PRIORITY', 137 | 'QUICK', 138 | ); 139 | expectedArguments( 140 | \Framework\Database\Manipulation\Delete::options(), 141 | 0, 142 | argumentsSet('manipulation_delete_opt') 143 | ); 144 | expectedArguments( 145 | \Framework\Database\Manipulation\Delete::options(), 146 | 1, 147 | argumentsSet('manipulation_delete_opt') 148 | ); 149 | expectedArguments( 150 | \Framework\Database\Manipulation\Delete::options(), 151 | 2, 152 | argumentsSet('manipulation_delete_opt') 153 | ); 154 | registerArgumentsSet( 155 | 'manipulation_insert_opt', 156 | \Framework\Database\Manipulation\Insert::OPT_DELAYED, 157 | \Framework\Database\Manipulation\Insert::OPT_HIGH_PRIORITY, 158 | \Framework\Database\Manipulation\Insert::OPT_IGNORE, 159 | \Framework\Database\Manipulation\Insert::OPT_LOW_PRIORITY, 160 | 'DELAYED', 161 | 'HIGH_PRIORITY', 162 | 'IGNORE', 163 | 'LOW_PRIORITY', 164 | ); 165 | expectedArguments( 166 | \Framework\Database\Manipulation\Insert::options(), 167 | 0, 168 | argumentsSet('manipulation_insert_opt') 169 | ); 170 | expectedArguments( 171 | \Framework\Database\Manipulation\Insert::options(), 172 | 1, 173 | argumentsSet('manipulation_insert_opt') 174 | ); 175 | expectedArguments( 176 | \Framework\Database\Manipulation\Insert::options(), 177 | 2, 178 | argumentsSet('manipulation_insert_opt') 179 | ); 180 | registerArgumentsSet( 181 | 'manipulation_load_data_opt', 182 | \Framework\Database\Manipulation\LoadData::OPT_CONCURRENT, 183 | \Framework\Database\Manipulation\LoadData::OPT_LOCAL, 184 | \Framework\Database\Manipulation\LoadData::OPT_LOW_PRIORITY, 185 | 'CONCURRENT', 186 | 'LOCAL', 187 | 'LOW_PRIORITY', 188 | ); 189 | expectedArguments( 190 | \Framework\Database\Manipulation\LoadData::options(), 191 | 0, 192 | argumentsSet('manipulation_load_data_opt') 193 | ); 194 | expectedArguments( 195 | \Framework\Database\Manipulation\LoadData::options(), 196 | 1, 197 | argumentsSet('manipulation_load_data_opt') 198 | ); 199 | expectedArguments( 200 | \Framework\Database\Manipulation\LoadData::options(), 201 | 2, 202 | argumentsSet('manipulation_load_data_opt') 203 | ); 204 | registerArgumentsSet( 205 | 'manipulation_replace_opt', 206 | \Framework\Database\Manipulation\Replace::OPT_DELAYED, 207 | \Framework\Database\Manipulation\Replace::OPT_LOW_PRIORITY, 208 | 'DELAYED', 209 | 'LOW_PRIORITY', 210 | ); 211 | expectedArguments( 212 | \Framework\Database\Manipulation\Replace::options(), 213 | 0, 214 | argumentsSet('manipulation_replace_opt') 215 | ); 216 | expectedArguments( 217 | \Framework\Database\Manipulation\Replace::options(), 218 | 1, 219 | argumentsSet('manipulation_replace_opt') 220 | ); 221 | registerArgumentsSet( 222 | 'manipulation_select_opt', 223 | \Framework\Database\Manipulation\Select::OPT_ALL, 224 | \Framework\Database\Manipulation\Select::OPT_DISTINCT, 225 | \Framework\Database\Manipulation\Select::OPT_DISTINCTROW, 226 | \Framework\Database\Manipulation\Select::OPT_HIGH_PRIORITY, 227 | \Framework\Database\Manipulation\Select::OPT_SQL_BIG_RESULT, 228 | \Framework\Database\Manipulation\Select::OPT_SQL_BUFFER_RESULT, 229 | \Framework\Database\Manipulation\Select::OPT_SQL_CACHE, 230 | \Framework\Database\Manipulation\Select::OPT_SQL_CALC_FOUND_ROWS, 231 | \Framework\Database\Manipulation\Select::OPT_SQL_NO_CACHE, 232 | \Framework\Database\Manipulation\Select::OPT_SQL_SMALL_RESULT, 233 | \Framework\Database\Manipulation\Select::OPT_STRAIGHT_JOIN, 234 | 'ALL', 235 | 'DISTINCT', 236 | 'DISTINCTROW', 237 | 'HIGH_PRIORITY', 238 | 'SQL_BIG_RESULT', 239 | 'SQL_BUFFER_RESULT', 240 | 'SQL_CACHE', 241 | 'SQL_CALC_FOUND_ROWS', 242 | 'SQL_NO_CACHE', 243 | 'SQL_SMALL_RESULT', 244 | 'STRAIGHT_JOIN', 245 | ); 246 | expectedArguments( 247 | \Framework\Database\Manipulation\Select::options(), 248 | 0, 249 | argumentsSet('manipulation_select_opt') 250 | ); 251 | expectedArguments( 252 | \Framework\Database\Manipulation\Select::options(), 253 | 1, 254 | argumentsSet('manipulation_select_opt') 255 | ); 256 | expectedArguments( 257 | \Framework\Database\Manipulation\Select::options(), 258 | 2, 259 | argumentsSet('manipulation_select_opt') 260 | ); 261 | registerArgumentsSet( 262 | 'manipulation_update_opt', 263 | \Framework\Database\Manipulation\Update::OPT_IGNORE, 264 | \Framework\Database\Manipulation\Update::OPT_LOW_PRIORITY, 265 | 'IGNORE', 266 | 'LOW_PRIORITY', 267 | ); 268 | expectedArguments( 269 | \Framework\Database\Manipulation\Update::options(), 270 | 0, 271 | argumentsSet('manipulation_update_opt') 272 | ); 273 | expectedArguments( 274 | \Framework\Database\Manipulation\Update::options(), 275 | 1, 276 | argumentsSet('manipulation_update_opt') 277 | ); 278 | registerArgumentsSet( 279 | 'manipulation_with_opt', 280 | \Framework\Database\Manipulation\With::OPT_RECURSIVE, 281 | 'RECURSIVE', 282 | ); 283 | expectedArguments( 284 | \Framework\Database\Manipulation\With::options(), 285 | 0, 286 | argumentsSet('manipulation_with_opt') 287 | ); 288 | registerArgumentsSet( 289 | 'foreign_key_opt', 290 | \Framework\Database\Definition\Table\Indexes\Keys\ForeignKey::OPT_CASCADE, 291 | \Framework\Database\Definition\Table\Indexes\Keys\ForeignKey::OPT_NO_ACTION, 292 | \Framework\Database\Definition\Table\Indexes\Keys\ForeignKey::OPT_RESTRICT, 293 | \Framework\Database\Definition\Table\Indexes\Keys\ForeignKey::OPT_SET_NULL, 294 | 'CASCADE', 295 | 'NO ACTION', 296 | 'RESTRICT', 297 | 'SET NULL', 298 | ); 299 | expectedArguments( 300 | \Framework\Database\Definition\Table\Indexes\Keys\ForeignKey::onDelete(), 301 | 0, 302 | argumentsSet('foreign_key_opt') 303 | ); 304 | expectedArguments( 305 | \Framework\Database\Definition\Table\Indexes\Keys\ForeignKey::onUpdate(), 306 | 0, 307 | argumentsSet('foreign_key_opt') 308 | ); 309 | registerArgumentsSet( 310 | 'table_options', 311 | \Framework\Database\Definition\Table\TableStatement::OPT_AUTO_INCREMENT, 312 | \Framework\Database\Definition\Table\TableStatement::OPT_AVG_ROW_LENGTH, 313 | \Framework\Database\Definition\Table\TableStatement::OPT_CHARSET, 314 | \Framework\Database\Definition\Table\TableStatement::OPT_CHECKSUM, 315 | \Framework\Database\Definition\Table\TableStatement::OPT_COLLATE, 316 | \Framework\Database\Definition\Table\TableStatement::OPT_COMMENT, 317 | \Framework\Database\Definition\Table\TableStatement::OPT_CONNECTION, 318 | \Framework\Database\Definition\Table\TableStatement::OPT_DATA_DIRECTORY, 319 | \Framework\Database\Definition\Table\TableStatement::OPT_DELAY_KEY_WRITE, 320 | \Framework\Database\Definition\Table\TableStatement::OPT_ENCRYPTED, 321 | \Framework\Database\Definition\Table\TableStatement::OPT_ENCRYPTION_KEY_ID, 322 | \Framework\Database\Definition\Table\TableStatement::OPT_ENGINE, 323 | \Framework\Database\Definition\Table\TableStatement::OPT_IETF_QUOTES, 324 | \Framework\Database\Definition\Table\TableStatement::OPT_INDEX_DIRECTORY, 325 | \Framework\Database\Definition\Table\TableStatement::OPT_INSERT_METHOD, 326 | \Framework\Database\Definition\Table\TableStatement::OPT_KEY_BLOCK_SIZE, 327 | \Framework\Database\Definition\Table\TableStatement::OPT_MAX_ROWS, 328 | \Framework\Database\Definition\Table\TableStatement::OPT_MIN_ROWS, 329 | \Framework\Database\Definition\Table\TableStatement::OPT_PACK_KEYS, 330 | \Framework\Database\Definition\Table\TableStatement::OPT_PAGE_CHECKSUM, 331 | \Framework\Database\Definition\Table\TableStatement::OPT_PAGE_COMPRESSED, 332 | \Framework\Database\Definition\Table\TableStatement::OPT_PAGE_COMPRESSION_LEVEL, 333 | \Framework\Database\Definition\Table\TableStatement::OPT_PASSWORD, 334 | \Framework\Database\Definition\Table\TableStatement::OPT_ROW_FORMAT, 335 | \Framework\Database\Definition\Table\TableStatement::OPT_SEQUENCE, 336 | \Framework\Database\Definition\Table\TableStatement::OPT_STATS_AUTO_RECALC, 337 | \Framework\Database\Definition\Table\TableStatement::OPT_STATS_PERSISTENT, 338 | \Framework\Database\Definition\Table\TableStatement::OPT_STATS_SAMPLE_PAGES, 339 | \Framework\Database\Definition\Table\TableStatement::OPT_TABLESPACE, 340 | \Framework\Database\Definition\Table\TableStatement::OPT_TRANSACTIONAL, 341 | \Framework\Database\Definition\Table\TableStatement::OPT_UNION, 342 | \Framework\Database\Definition\Table\TableStatement::OPT_WITH_SYSTEM_VERSIONING, 343 | 'AUTO_INCREMENT', 344 | 'AVG_ROW_LENGTH', 345 | 'CHARSET', 346 | 'CHECKSUM', 347 | 'COLLATE', 348 | 'COMMENT', 349 | 'CONNECTION', 350 | 'DATA DIRECTORY', 351 | 'DELAY_KEY_WRITE', 352 | 'ENCRYPTED', 353 | 'ENCRYPTION_KEY_ID', 354 | 'ENGINE', 355 | 'IETF_QUOTES', 356 | 'INDEX DIRECTORY', 357 | 'INSERT_METHOD', 358 | 'KEY_BLOCK_SIZE', 359 | 'MAX_ROWS', 360 | 'MIN_ROWS', 361 | 'PACK_KEYS', 362 | 'PAGE_CHECKSUM', 363 | 'PAGE_COMPRESSED', 364 | 'PAGE_COMPRESSION_LEVEL', 365 | 'PASSWORD', 366 | 'ROW_FORMAT', 367 | 'SEQUENCE', 368 | 'STATS_AUTO_RECALC', 369 | 'STATS_PERSISTENT', 370 | 'STATS_SAMPLE_PAGES', 371 | 'TABLESPACE', 372 | 'TRANSACTIONAL', 373 | 'UNION', 374 | 'WITH SYSTEM VERSIONING', 375 | ); 376 | expectedArguments( 377 | \Framework\Database\Definition\Table\TableStatement::option(), 378 | 0, 379 | argumentsSet('table_options') 380 | ); 381 | registerArgumentsSet( 382 | 'operators', 383 | '=', 384 | '<=>', 385 | '!=', 386 | '<>', 387 | '>', 388 | '>=', 389 | '<', 390 | '<=', 391 | 'LIKE', 392 | 'NOT LIKE', 393 | 'IN', 394 | 'NOT IN', 395 | 'BETWEEN', 396 | 'NOT BETWEEN', 397 | 'IS NULL', 398 | 'IS NOT NULL', 399 | ); 400 | expectedArguments( 401 | \Framework\Database\Manipulation\Traits\Having::having(), 402 | 1, 403 | argumentsSet('operators') 404 | ); 405 | expectedArguments( 406 | \Framework\Database\Manipulation\Traits\Having::orHaving(), 407 | 1, 408 | argumentsSet('operators') 409 | ); 410 | expectedArguments( 411 | \Framework\Database\Manipulation\Traits\Where::where(), 412 | 1, 413 | argumentsSet('operators') 414 | ); 415 | expectedArguments( 416 | \Framework\Database\Manipulation\Traits\Where::orWhere(), 417 | 1, 418 | argumentsSet('operators') 419 | ); 420 | registerArgumentsSet( 421 | 'explain_delete', 422 | \Framework\Database\Manipulation\Delete::EXP_EXTENDED, 423 | \Framework\Database\Manipulation\Delete::EXP_FORMAT_JSON, 424 | \Framework\Database\Manipulation\Delete::EXP_PARTITIONS, 425 | 'EXTENDED', 426 | 'FORMAT=JSON', 427 | 'PARTITIONS', 428 | ); 429 | expectedArguments( 430 | \Framework\Database\Manipulation\Delete::explain(), 431 | 0, 432 | argumentsSet('explain_delete') 433 | ); 434 | registerArgumentsSet( 435 | 'explain_select', 436 | \Framework\Database\Manipulation\Select::EXP_EXTENDED, 437 | \Framework\Database\Manipulation\Select::EXP_FORMAT_JSON, 438 | \Framework\Database\Manipulation\Select::EXP_PARTITIONS, 439 | 'EXTENDED', 440 | 'FORMAT=JSON', 441 | 'PARTITIONS', 442 | ); 443 | expectedArguments( 444 | \Framework\Database\Manipulation\Select::explain(), 445 | 0, 446 | argumentsSet('explain_select') 447 | ); 448 | registerArgumentsSet( 449 | 'explain_update', 450 | \Framework\Database\Manipulation\Update::EXP_EXTENDED, 451 | \Framework\Database\Manipulation\Update::EXP_FORMAT_JSON, 452 | \Framework\Database\Manipulation\Update::EXP_PARTITIONS, 453 | 'EXTENDED', 454 | 'FORMAT=JSON', 455 | 'PARTITIONS', 456 | ); 457 | expectedArguments( 458 | \Framework\Database\Manipulation\Update::explain(), 459 | 0, 460 | argumentsSet('explain_update') 461 | ); 462 | -------------------------------------------------------------------------------- /src/Definition/Table/TableStatement.php: -------------------------------------------------------------------------------- 1 | 6 | * 7 | * For the full copyright and license information, please view the LICENSE 8 | * file that was distributed with this source code. 9 | */ 10 | namespace Framework\Database\Definition\Table; 11 | 12 | use Framework\Database\Statement; 13 | use InvalidArgumentException; 14 | 15 | /** 16 | * Class TableStatement. 17 | * 18 | * @see https://mariadb.com/kb/en/create-table/#table-options 19 | * 20 | * @package database 21 | */ 22 | abstract class TableStatement extends Statement 23 | { 24 | /** 25 | * @see https://mariadb.com/kb/en/create-table/#storage-engine 26 | */ 27 | public const string OPT_ENGINE = 'ENGINE'; 28 | /** 29 | * @see https://mariadb.com/kb/en/create-table/#auto_increment 30 | */ 31 | public const string OPT_AUTO_INCREMENT = 'AUTO_INCREMENT'; 32 | /** 33 | * @see https://mariadb.com/kb/en/create-table/#avg_row_length 34 | */ 35 | public const string OPT_AVG_ROW_LENGTH = 'AVG_ROW_LENGTH'; 36 | /** 37 | * @see https://mariadb.com/kb/en/create-table/#default-character-setcharset 38 | */ 39 | public const string OPT_CHARSET = 'CHARSET'; 40 | /** 41 | * @see https://mariadb.com/kb/en/create-table/#checksumtable_checksum 42 | */ 43 | public const string OPT_CHECKSUM = 'CHECKSUM'; 44 | /** 45 | * @see https://mariadb.com/kb/en/create-table/#default-collate 46 | */ 47 | public const string OPT_COLLATE = 'COLLATE'; 48 | /** 49 | * @see https://mariadb.com/kb/en/create-table/#comment 50 | */ 51 | public const string OPT_COMMENT = 'COMMENT'; 52 | /** 53 | * @see https://mariadb.com/kb/en/create-table/#connection 54 | */ 55 | public const string OPT_CONNECTION = 'CONNECTION'; 56 | /** 57 | * @see https://mariadb.com/kb/en/create-table/#data-directoryindex-directory 58 | */ 59 | public const string OPT_DATA_DIRECTORY = 'DATA DIRECTORY'; 60 | /** 61 | * @see https://mariadb.com/kb/en/create-table/#delay_key_write 62 | */ 63 | public const string OPT_DELAY_KEY_WRITE = 'DELAY_KEY_WRITE'; 64 | /** 65 | * @see https://mariadb.com/kb/en/create-table/#encrypted 66 | */ 67 | public const string OPT_ENCRYPTED = 'ENCRYPTED'; 68 | /** 69 | * @see https://mariadb.com/kb/en/create-table/#encryption_key_id 70 | */ 71 | public const string OPT_ENCRYPTION_KEY_ID = 'ENCRYPTION_KEY_ID'; 72 | /** 73 | * @see https://mariadb.com/kb/en/create-table/#ietf_quotes 74 | */ 75 | public const string OPT_IETF_QUOTES = 'IETF_QUOTES'; 76 | /** 77 | * @see https://mariadb.com/kb/en/create-table/#data-directoryindex-directory 78 | */ 79 | public const string OPT_INDEX_DIRECTORY = 'INDEX DIRECTORY'; 80 | /** 81 | * @see https://mariadb.com/kb/en/create-table/#insert_method 82 | */ 83 | public const string OPT_INSERT_METHOD = 'INSERT_METHOD'; 84 | /** 85 | * @see https://mariadb.com/kb/en/create-table/#key_block_size 86 | */ 87 | public const string OPT_KEY_BLOCK_SIZE = 'KEY_BLOCK_SIZE'; 88 | /** 89 | * @see https://mariadb.com/kb/en/create-table/#min_rowsmax_rows 90 | */ 91 | public const string OPT_MAX_ROWS = 'MAX_ROWS'; 92 | /** 93 | * @see https://mariadb.com/kb/en/create-table/#min_rowsmax_rows 94 | */ 95 | public const string OPT_MIN_ROWS = 'MIN_ROWS'; 96 | /** 97 | * @see https://mariadb.com/kb/en/create-table/#pack_keys 98 | */ 99 | public const string OPT_PACK_KEYS = 'PACK_KEYS'; 100 | /** 101 | * @see https://mariadb.com/kb/en/create-table/#page_checksum 102 | */ 103 | public const string OPT_PAGE_CHECKSUM = 'PAGE_CHECKSUM'; 104 | /** 105 | * @see https://mariadb.com/kb/en/create-table/#page_compressed 106 | */ 107 | public const string OPT_PAGE_COMPRESSED = 'PAGE_COMPRESSED'; 108 | /** 109 | * @see https://mariadb.com/kb/en/create-table/#page_compression_level 110 | */ 111 | public const string OPT_PAGE_COMPRESSION_LEVEL = 'PAGE_COMPRESSION_LEVEL'; 112 | /** 113 | * @see https://mariadb.com/kb/en/create-table/#password 114 | */ 115 | public const string OPT_PASSWORD = 'PASSWORD'; 116 | /** 117 | * @see https://mariadb.com/kb/en/create-table/#row_format 118 | */ 119 | public const string OPT_ROW_FORMAT = 'ROW_FORMAT'; 120 | /** 121 | * @see https://mariadb.com/kb/en/create-table/#sequence 122 | */ 123 | public const string OPT_SEQUENCE = 'SEQUENCE'; 124 | /** 125 | * @see https://mariadb.com/kb/en/create-table/#stats_auto_recalc 126 | */ 127 | public const string OPT_STATS_AUTO_RECALC = 'STATS_AUTO_RECALC'; 128 | /** 129 | * @see https://mariadb.com/kb/en/create-table/#stats_persistent 130 | */ 131 | public const string OPT_STATS_PERSISTENT = 'STATS_PERSISTENT'; 132 | /** 133 | * @see https://mariadb.com/kb/en/create-table/#stats_sample_pages 134 | */ 135 | public const string OPT_STATS_SAMPLE_PAGES = 'STATS_SAMPLE_PAGES'; 136 | /** 137 | * @see https://mariadb.com/kb/en/create-tablespace/ 138 | */ 139 | public const string OPT_TABLESPACE = 'TABLESPACE'; 140 | /** 141 | * @see https://mariadb.com/kb/en/create-table/#transactional 142 | */ 143 | public const string OPT_TRANSACTIONAL = 'TRANSACTIONAL'; 144 | /** 145 | * @see https://mariadb.com/kb/en/create-table/#union 146 | */ 147 | public const string OPT_UNION = 'UNION'; 148 | /** 149 | * @see https://mariadb.com/kb/en/create-table/#with-system-versioning 150 | */ 151 | public const string OPT_WITH_SYSTEM_VERSIONING = 'WITH SYSTEM VERSIONING'; 152 | 153 | /** 154 | * Adds a table option. 155 | * 156 | * @param string $name 157 | * @param int|string|null $value 158 | * 159 | * @return static 160 | */ 161 | public function option(string $name, int | string | null $value = null) : static 162 | { 163 | $this->sql['options'][$name] = $value; 164 | return $this; 165 | } 166 | 167 | /** 168 | * Adds table options. 169 | * 170 | * @param array $options 171 | * 172 | * @return static 173 | */ 174 | public function options(array $options) : static 175 | { 176 | foreach ($options as $name => $value) { 177 | $this->option($name, $value); 178 | } 179 | return $this; 180 | } 181 | 182 | protected function renderOptions() : ?string 183 | { 184 | if (!isset($this->sql['options'])) { 185 | return null; 186 | } 187 | $options = []; 188 | foreach ($this->sql['options'] as $name => $value) { 189 | $nameUpper = \strtoupper($name); 190 | $value = (string) $value; 191 | $value = match ($nameUpper) { 192 | static::OPT_ENGINE => $this->makeEngine($value), 193 | static::OPT_AUTO_INCREMENT => $this->makeAutoIncrement($value), 194 | static::OPT_AVG_ROW_LENGTH => $this->makeAvgRowLength($value), 195 | static::OPT_CHARSET => $this->makeCharset($value), 196 | static::OPT_CHECKSUM => $this->makeChecksum($value), 197 | static::OPT_COLLATE => $this->makeCollate($value), 198 | static::OPT_COMMENT => $this->makeComment($value), 199 | static::OPT_CONNECTION => $this->makeConnection($value), 200 | static::OPT_DATA_DIRECTORY => $this->makeDataDirectory($value), 201 | static::OPT_DELAY_KEY_WRITE => $this->makeDelayKeyWrite($value), 202 | static::OPT_ENCRYPTED => $this->makeEncrypted($value), 203 | static::OPT_ENCRYPTION_KEY_ID => $this->makeEncryptionKeyId($value), 204 | static::OPT_IETF_QUOTES => $this->makeIetfQuotes($value), 205 | static::OPT_INDEX_DIRECTORY => $this->makeIndexDirectory($value), 206 | static::OPT_INSERT_METHOD => $this->makeInsertMethod($value), 207 | static::OPT_KEY_BLOCK_SIZE => $this->makeKeyBlockSize($value), 208 | static::OPT_MAX_ROWS => $this->makeMaxRows($value), 209 | static::OPT_MIN_ROWS => $this->makeMinRows($value), 210 | static::OPT_PACK_KEYS => $this->makePackKeys($value), 211 | static::OPT_PAGE_CHECKSUM => $this->makePageChecksum($value), 212 | static::OPT_PAGE_COMPRESSED => $this->makePageCompressed($value), 213 | static::OPT_PAGE_COMPRESSION_LEVEL => $this->makePageCompressionLevel($value), 214 | static::OPT_PASSWORD => $this->makePassword($value), 215 | static::OPT_ROW_FORMAT => $this->makeRowFormat($value), 216 | static::OPT_SEQUENCE => $this->makeSequence($value), 217 | static::OPT_STATS_AUTO_RECALC => $this->makeStatsAutoRecalc($value), 218 | static::OPT_STATS_PERSISTENT => $this->makeStatsPersistent($value), 219 | static::OPT_STATS_SAMPLE_PAGES => $this->makeStatsSamplePages($value), 220 | static::OPT_TABLESPACE => $this->makeTablespace($value), 221 | static::OPT_TRANSACTIONAL => $this->makeTransactional($value), 222 | static::OPT_UNION => $this->makeUnion($value), 223 | static::OPT_WITH_SYSTEM_VERSIONING => '', 224 | default => throw new InvalidArgumentException('Invalid option: ' . $name) 225 | }; 226 | $option = $nameUpper; 227 | if ($value !== '') { 228 | $option .= ' = ' . $value; 229 | } 230 | $options[] = $option; 231 | } 232 | return ' ' . \implode(', ', $options); 233 | } 234 | 235 | private function makeEngine(string $value) : string 236 | { 237 | return $this->getValue(static::OPT_ENGINE, $value, [ 238 | 'aria' => 'Aria', 239 | 'csv' => 'CSV', 240 | 'innodb' => 'InnoDB', 241 | 'memory' => 'MEMORY', 242 | 'mrg_myisam' => 'MRG_MyISAM', 243 | 'myisam' => 'MyISAM', 244 | 'sequence' => 'SEQUENCE', 245 | ], \strtolower($value), true); 246 | } 247 | 248 | private function makeAutoIncrement(string $value) : string 249 | { 250 | return \is_numeric($value) 251 | ? $value 252 | : throw $this->invalidValue(static::OPT_AUTO_INCREMENT, $value); 253 | } 254 | 255 | private function makeAvgRowLength(string $value) : string 256 | { 257 | if (\is_numeric($value) && $value >= 0) { 258 | return $value; 259 | } 260 | throw $this->invalidValue(static::OPT_AVG_ROW_LENGTH, $value); 261 | } 262 | 263 | private function makeCharset(string $value) : string 264 | { 265 | return $this->getValue(static::OPT_CHARSET, $value, [ 266 | 'armscii8', 267 | 'ascii', 268 | 'big5', 269 | 'binary', 270 | 'cp1250', 271 | 'cp1251', 272 | 'cp1256', 273 | 'cp1257', 274 | 'cp850', 275 | 'cp852', 276 | 'cp866', 277 | 'cp932', 278 | 'dec8', 279 | 'eucjpms', 280 | 'euckr', 281 | 'gb2312', 282 | 'gbk', 283 | 'geostd8', 284 | 'greek', 285 | 'hebrew', 286 | 'hp8', 287 | 'keybcs2', 288 | 'koi8r', 289 | 'koi8u', 290 | 'latin1', 291 | 'latin2', 292 | 'latin5', 293 | 'latin7', 294 | 'macce', 295 | 'macroman', 296 | 'sjis', 297 | 'swe7', 298 | 'tis620', 299 | 'ucs2', 300 | 'ujis', 301 | 'utf16', 302 | 'utf16le', 303 | 'utf32', 304 | 'utf8', 305 | 'utf8mb3', 306 | 'utf8mb4', 307 | ], \strtolower($value)); 308 | } 309 | 310 | private function makeChecksum(string $value) : string 311 | { 312 | return $this->getValue(static::OPT_CHECKSUM, $value, [ 313 | '0', 314 | '1', 315 | ]); 316 | } 317 | 318 | private function makeCollate(string $value) : string 319 | { 320 | return $this->quote($value); 321 | } 322 | 323 | private function makeComment(string $value) : string 324 | { 325 | return $this->quote($value); 326 | } 327 | 328 | private function makeConnection(string $value) : string 329 | { 330 | return $this->quote($value); 331 | } 332 | 333 | private function makeDataDirectory(string $value) : string 334 | { 335 | return $this->quote($value); 336 | } 337 | 338 | private function makeDelayKeyWrite(string $value) : string 339 | { 340 | return $this->getValue(static::OPT_DELAY_KEY_WRITE, $value, [ 341 | '0', 342 | '1', 343 | ]); 344 | } 345 | 346 | private function makeIetfQuotes(string $value) : string 347 | { 348 | return $this->getValue(static::OPT_IETF_QUOTES, $value, [ 349 | 'NO', 350 | 'YES', 351 | ], \strtoupper($value)); 352 | } 353 | 354 | private function makeIndexDirectory(string $value) : string 355 | { 356 | return $this->quote($value); 357 | } 358 | 359 | private function makeInsertMethod(string $value) : string 360 | { 361 | return $this->getValue(static::OPT_INSERT_METHOD, $value, [ 362 | 'FIRST', 363 | 'LAST', 364 | 'NO', 365 | ], \strtoupper($value)); 366 | } 367 | 368 | private function makeKeyBlockSize(string $value) : string 369 | { 370 | if (\is_numeric($value) && $value >= 0) { 371 | return $value; 372 | } 373 | throw $this->invalidValue(static::OPT_KEY_BLOCK_SIZE, $value); 374 | } 375 | 376 | private function makeEncrypted(string $value) : string 377 | { 378 | return $this->getValue(static::OPT_ENCRYPTED, $value, [ 379 | 'NO', 380 | 'YES', 381 | ], \strtoupper($value)); 382 | } 383 | 384 | private function makeEncryptionKeyId(string $value) : string 385 | { 386 | if (\is_numeric($value) && $value >= 1) { 387 | return $value; 388 | } 389 | throw $this->invalidValue(static::OPT_ENCRYPTION_KEY_ID, $value); 390 | } 391 | 392 | private function makeMaxRows(string $value) : string 393 | { 394 | if (\is_numeric($value) && $value >= 0) { 395 | return $value; 396 | } 397 | throw $this->invalidValue(static::OPT_MAX_ROWS, $value); 398 | } 399 | 400 | private function makeMinRows(string $value) : string 401 | { 402 | if (\is_numeric($value) && $value >= 0) { 403 | return $value; 404 | } 405 | throw $this->invalidValue(static::OPT_MIN_ROWS, $value); 406 | } 407 | 408 | private function makePackKeys(string $value) : string 409 | { 410 | return $this->getValue(static::OPT_PACK_KEYS, $value, [ 411 | '0', 412 | '1', 413 | ]); 414 | } 415 | 416 | private function makePageChecksum(string $value) : string 417 | { 418 | return $this->getValue(static::OPT_PAGE_CHECKSUM, $value, [ 419 | '0', 420 | '1', 421 | ]); 422 | } 423 | 424 | private function makePageCompressed(string $value) : string 425 | { 426 | return $this->getValue( 427 | static::OPT_PAGE_COMPRESSED, 428 | $value, 429 | \array_map('strval', \range(0, 9)) 430 | ); 431 | } 432 | 433 | private function makePageCompressionLevel(string $value) : string 434 | { 435 | return $this->getValue(static::OPT_PAGE_COMPRESSION_LEVEL, $value, [ 436 | '0', 437 | '1', 438 | ]); 439 | } 440 | 441 | private function makePassword(string $value) : string 442 | { 443 | return $this->quote($value); 444 | } 445 | 446 | private function makeStatsAutoRecalc(string $value) : string 447 | { 448 | return $this->getValue(static::OPT_STATS_AUTO_RECALC, $value, [ 449 | '0', 450 | '1', 451 | 'DEFAULT', 452 | ], \strtoupper($value)); 453 | } 454 | 455 | private function makeStatsPersistent(string $value) : string 456 | { 457 | return $this->getValue(static::OPT_STATS_PERSISTENT, $value, [ 458 | '0', 459 | '1', 460 | 'DEFAULT', 461 | ], \strtoupper($value)); 462 | } 463 | 464 | private function makeStatsSamplePages(string $value) : string 465 | { 466 | if (\strtoupper($value) === 'DEFAULT') { 467 | return 'DEFAULT'; 468 | } 469 | if (\is_numeric($value) && $value > 0) { 470 | return $value; 471 | } 472 | throw $this->invalidValue(static::OPT_STATS_SAMPLE_PAGES, $value); 473 | } 474 | 475 | private function makeTablespace(string $value) : string 476 | { 477 | return $this->quote($value); 478 | } 479 | 480 | private function makeRowFormat(string $value) : string 481 | { 482 | return $this->getValue(static::OPT_ROW_FORMAT, $value, [ 483 | 'COMPACT', 484 | 'COMPRESSED', 485 | 'DEFAULT', 486 | 'DYNAMIC', 487 | 'FIXED', 488 | 'PAGE', 489 | 'REDUNDANT', 490 | ], \strtoupper($value)); 491 | } 492 | 493 | private function makeSequence(string $value) : string 494 | { 495 | if (\is_numeric($value) && $value >= 0) { 496 | return $value; 497 | } 498 | throw $this->invalidValue(static::OPT_SEQUENCE, $value); 499 | } 500 | 501 | private function makeTransactional(string $value) : string 502 | { 503 | return $this->getValue(static::OPT_TRANSACTIONAL, $value, [ 504 | '0', 505 | '1', 506 | ]); 507 | } 508 | 509 | private function makeUnion(string $value) : string 510 | { 511 | if ($value === '') { 512 | throw $this->invalidValue(static::OPT_UNION, $value); 513 | } 514 | $tables = \array_map('trim', \explode(',', $value)); 515 | foreach ($tables as &$table) { 516 | $table = $this->database->protectIdentifier($table); 517 | } 518 | unset($table); 519 | $tables = \implode(', ', $tables); 520 | return '(' . $tables . ')'; 521 | } 522 | 523 | /** 524 | * @param string $optionName 525 | * @param string $originalValue 526 | * @param array $options 527 | * @param string|null $value 528 | * @param bool $getByKey 529 | * 530 | * @return string 531 | */ 532 | private function getValue( 533 | string $optionName, 534 | string $originalValue, 535 | array $options, 536 | ?string $value = null, 537 | bool $getByKey = false 538 | ) : string { 539 | $value ??= $originalValue; 540 | if ($getByKey) { 541 | if (isset($options[$value])) { 542 | return $options[$value]; 543 | } 544 | } elseif (\in_array($value, $options, true)) { 545 | return $value; 546 | } 547 | throw $this->invalidValue($optionName, $originalValue); 548 | } 549 | 550 | private function invalidValue(string $option, string $value) : InvalidArgumentException 551 | { 552 | return new InvalidArgumentException("Invalid {$option} option value: {$value}"); 553 | } 554 | 555 | private function quote(string $value) : string 556 | { 557 | return (string) $this->database->quote($value); 558 | } 559 | } 560 | --------------------------------------------------------------------------------