19declare(strict_types=1);
43 self::T_TEXT => [
'length',
'notnull',
'default',
'fixed'],
44 self::T_INTEGER => [
'length',
'notnull',
'default',
'unsigned'],
45 self::T_FLOAT => [
'notnull',
'default'],
46 self::T_DATE => [
'notnull',
'default'],
47 self::T_TIME => [
'notnull',
'default'],
48 self::T_TIMESTAMP => [
'notnull',
'default'],
49 self::T_CLOB => [
'notnull',
'default'],
50 self::T_BLOB => [
'notnull',
'default'],
56 "text" => [
"length",
"notnull",
"default",
"fixed"],
57 "integer" => [
"length",
"notnull",
"default",
"unsigned"],
58 "float" => [
"notnull",
"default"],
59 "date" => [
"notnull",
"default"],
60 "time" => [
"notnull",
"default"],
61 "timestamp" => [
"notnull",
"default"],
62 "clob" => [
"length",
"notnull",
"default"],
63 "blob" => [
"length",
"notnull",
"default"],
66 self::T_INTEGER => [1, 2, 3, 4, 8],
166 "CONSTRAINT_CATALOG",
280 "GEOMETRYCOLLECTION",
369 "MASTER_AUTO_POSITION",
371 "MASTER_CONNECT_RETRY",
373 "MASTER_HEARTBEAT_PERIOD",
379 "MASTER_RETRY_COUNT",
387 "MASTER_SSL_CRLPATH",
389 "MASTER_SSL_VERIFY_SERVER_CERT",
390 "MASTER_TLS_VERSION",
394 "MAX_CONNECTIONS_PER_HOUR",
395 "MAX_QUERIES_PER_HOUR",
398 "MAX_STATEMENT_TIME",
399 "MAX_UPDATES_PER_HOUR",
400 "MAX_USER_CONNECTIONS",
412 "MINUTE_MICROSECOND",
441 "NO_WRITE_TO_BINLOG",
524 "REPLICATE_DO_TABLE",
525 "REPLICATE_IGNORE_DB",
526 "REPLICATE_IGNORE_TABLE",
527 "REPLICATE_REWRITE_DB",
528 "REPLICATE_WILD_DO_TABLE",
529 "REPLICATE_WILD_IGNORE_TABLE",
559 "SECOND_MICROSECOND",
591 "SQL_AFTER_MTS_GAPS",
596 "SQL_CALC_FOUND_ROWS",
615 "STATS_SAMPLE_PAGES",
825 'timestamp' =>
'1970-01-01 00:00:00',
826 'time' =>
'00:00:00',
827 'date' =>
'1970-01-01',
837 if (!preg_match(self::DEFINITION_TABLE_NAME, $table_name)) {
838 throw new ilDatabaseException(
'Table name must only contain _a-z0-9 and must start with a-z.');
842 throw new ilDatabaseException(
"Invalid table name '" . $table_name .
"' (Reserved Word).");
845 if (stripos($table_name,
"sys_") === 0) {
846 throw new ilDatabaseException(
"Invalid table name '" . $table_name .
"'. Name must not start with 'sys_'.");
849 if (strlen($table_name) > self::MAX_TABLE_IDENTIFIER_LENGTH) {
851 .
"'. Maximum table identifer length is " . self::MAX_TABLE_IDENTIFIER_LENGTH .
" bytes.");
892 if (!preg_match(
"/^[a-z]+[_a-z0-9]*$/", $column_name)) {
894 .
"'. Column name must only contain _a-z0-9 and must start with a-z.");
898 throw new ilDatabaseException(
"Invalid column name '" . $column_name .
"' (Reserved Word).");
901 if (stripos($column_name,
"sys_") === 0) {
902 throw new ilDatabaseException(
"Invalid column name '" . $column_name .
"'. Name must not start with 'sys_'.");
905 if (strlen($column_name) > 30) {
906 throw new ilDatabaseException(
"Invalid column name '" . $column_name .
"'. Maximum column identifer length is 30 bytes.");
917 if (!preg_match(
"/^[a-z]+[_a-z0-9]*$/", $a_name)) {
918 throw new ilDatabaseException(
"Invalid column name '" . $a_name .
"'. Column name must only contain _a-z0-9 and must start with a-z.");
925 if (strlen($a_name) > 3) {
926 throw new ilDatabaseException(
"Invalid index name '" . $a_name .
"'. Maximum index identifer length is 3 bytes.");
939 switch ($a_def[
"type"]) {
941 throw new ilDatabaseException(
"Invalid column type '" . $a_def[
"type"] .
"'. Use integer(1) instead.");
944 throw new ilDatabaseException(
"Invalid column type '" . $a_def[
"type"] .
"'. Use float or integer instead.");
947 throw new ilDatabaseException(
"Invalid column type '" . $a_def[
"type"] .
"'. Allowed types are: "
954 foreach (array_keys($a_def) as $k) {
956 throw new ilDatabaseException(
"Attribute '" . $k .
"' is not allowed for column type '" . $a_def[
"type"] .
"'.");
962 switch ($a_def[
"type"]) {
964 if ((!isset($a_def[
"length"]) || $a_def[
"length"] < 1 || $a_def[
"length"] >
$max_length[self::T_TEXT]) && isset($a_def[
"length"])) {
965 throw new ilDatabaseException(
"Invalid length '" . $a_def[
"length"] .
"' for type text." .
" Length must be >=1 and <= "
971 if (isset($a_def[
"length"]) && !in_array((
int) $a_def[
"length"],
$max_length[self::T_INTEGER],
true)) {
972 throw new ilDatabaseException(
"Invalid length '" . $a_def[
"length"] .
"' for type integer." .
" Length must be "
973 . implode(
', ',
$max_length[self::T_INTEGER]) .
" (bytes).");
975 if ($a_def[
"unsigned"] ??
null) {
986 return in_array($attribute, $this->allowed_attributes[$type],
true);
1033 return $this->db_instance;
1044 if (!empty($db->options[
'datatype_map'])) {
1045 foreach ($db->options[
'datatype_map'] as $type => $mapped_type) {
1046 if (array_key_exists($mapped_type, $types)) {
1047 $types[$type] = $types[$mapped_type];
1048 } elseif (!empty($db->options[
'datatype_map_callback'][$type])) {
1049 $parameter = [
'type' => $type,
'mapped_type' => $mapped_type];
1050 $default = call_user_func_array(
1051 $db->options[
'datatype_map_callback'][$type],
1052 [&$db, __FUNCTION__, $parameter]
1054 $types[$type] = $default;
1072 if (!empty($db->options[
'datatype_map'][$type])) {
1073 $type = $db->options[
'datatype_map'][$type];
1074 if (!empty($db->options[
'datatype_map_callback'][$type])) {
1075 $parameter = [
'type' => $type,
'name' => $name,
'field' => $field];
1077 return call_user_func_array(
1078 $db->options[
'datatype_map_callback'][$type],
1079 [&$db, __FUNCTION__, $parameter]
1082 $field[
'type'] = $type;
1089 $quoted_name = $db->quoteIdentifier($name,
true);
1092 if ($type ===
'clob' || $type ===
'blob') {
1093 $notnull = empty($field[
'notnull']) ?
'' :
' NOT NULL';
1094 return $quoted_name .
' ' . $type_declaration . $notnull;
1102 $charset = empty($field[
'charset']) ?
'' :
' ' . $field[
'charset'];
1105 if (array_key_exists(
'default', $field)) {
1106 if ($field[
'default'] ===
'') {
1109 if (empty($field[
'notnull'])) {
1110 $field[
'default'] =
null;
1115 if ($field[
'default'] ===
''
1116 && isset($db->options[
'portability'])
1117 && ($db->options[
'portability'] & 32)
1119 $field[
'default'] =
' ';
1122 $default =
' DEFAULT ' . $this->
quote($field[
'default'], $field[
'type']);
1123 } elseif (empty($field[
'notnull'])) {
1124 $default =
' DEFAULT NULL';
1127 $notnull = empty($field[
'notnull']) ?
'' :
' NOT NULL';
1128 if (isset($field[
'notnull']) && $field[
'notnull'] ===
false) {
1132 $collation = empty($field[
'collation']) ?
'' :
' ' . $field[
'collation'];
1134 return $charset . $default . $notnull . $collation;
1144 $type = empty($current[
'type']) ? null : $current[
'type'];
1146 if (!method_exists($this,
"compare{$type}Definition")) {
1149 if (!empty($db->options[
'datatype_map_callback'][$type])) {
1150 $parameter = [
'current' => $current,
'previous' => $previous];
1152 return call_user_func_array(
1153 $db->options[
'datatype_map_callback'][$type],
1154 [&$db, __FUNCTION__, $parameter]
1161 if (empty($previous[
'type']) || $previous[
'type'] != $type) {
1165 $change = $this->{
"compare{$type}Definition"}($current, $previous);
1167 if ($previous[
'type'] != $type) {
1168 $change[
'type'] =
true;
1171 $previous_notnull = empty($previous[
'notnull']) ? false : $previous[
'notnull'];
1172 $notnull = empty($current[
'notnull']) ? false : $current[
'notnull'];
1173 if ($previous_notnull !== $notnull) {
1174 $change[
'notnull'] =
true;
1177 $alt = $previous_notnull ?
'' :
null;
1178 $previous_default = array_key_exists(
1181 ) ? $previous[
'default'] : $alt;
1182 $alt = $notnull ?
'' :
null;
1183 $default = array_key_exists(
'default', $current) ? $current[
'default'] : $alt;
1184 if ($previous_default !== $default) {
1185 $change[
'default'] =
true;
1194 public function quote($value, ?
string $type =
null,
bool $quote =
true,
bool $escape_wildcards =
false): string
1208 if (preg_match(
'/^(\w+:\/\/)(.*)$/', $file, $match) && $match[1] ===
'file://') {
1212 $fp = @fopen($file,
'wb');
1213 while (!@feof($lob)) {
1214 $result = @fread($lob, $db->options[
'lob_buffer_length']);
1215 $read = strlen($result);
1216 if (@fwrite($fp, $result, $read) !== $read) {
1232 $lob_data = stream_get_meta_data($lob);
1233 $lob_index = $lob_data[
'wrapper_data']->lob_index;
1235 if (isset($this->lobs[$lob_index])) {
1236 unset($this->lobs[$lob_index]);
1246 public function matchPattern(array $pattern, $operator =
null, $field =
null): string
1251 if (!is_null($operator)) {
1252 $operator = strtoupper((
string) $operator);
1253 switch ($operator) {
1256 if (is_null($field)) {
1257 throw new ilDatabaseException(
'case insensitive LIKE matching requires passing the field name');
1259 $db->loadModule(
'Function');
1260 $match = $db->lower($field) .
' LIKE ';
1264 $match = is_null($field) ?
'LIKE ' : $field .
' LIKE ';
1271 foreach ($pattern as $key => $value) {
1272 if ($key % 2 !== 0) {
1275 if ($operator ===
'ILIKE') {
1276 $value = strtolower((
string) $value);
1279 $escaped = $db->escape($value);
1280 $match .= $db->escapePattern($escaped);
1300 $db_type = strtok($field[
'type'],
'(), ');
1301 if (!empty($db->options[
'nativetype_map_callback'][$db_type])) {
1302 return call_user_func_array($db->options[
'nativetype_map_callback'][$db_type], [$db, $field]);
1315 if (!empty($db->options[
'datatype_map'][$type])) {
1316 $type = $db->options[
'datatype_map'][$type];
1317 if (!empty($db->options[
'datatype_map_callback'][$type])) {
1318 $parameter = [
'type' => $type];
1320 return call_user_func_array(
1321 $db->options[
'datatype_map_callback'][$type],
1322 [&$db, __FUNCTION__, $parameter]
1335 switch ($field[
'type']) {
1337 if (empty($field[
'length']) && array_key_exists(
'default', $field)) {
1338 $field[
'length'] = $db->varchar_max_length ??
null;
1340 $length = empty($field[
'length']) ? false : $field[
'length'];
1341 $fixed = empty($field[
'fixed']) ? false : $field[
'fixed'];
1343 return $length ?
'CHAR(' . $length .
')' :
'CHAR(255)';
1345 return $length ?
'VARCHAR(' . $length .
')' :
'TEXT';
1348 if (!empty($field[
'length'])) {
1349 $length = $field[
'length'];
1350 if ($length <= 255) {
1354 if ($length <= 65532) {
1358 if ($length <= 16_777_215) {
1359 return 'MEDIUMTEXT';
1365 if (!empty($field[
'length'])) {
1366 $length = $field[
'length'];
1367 if ($length <= 255) {
1371 if ($length <= 65532) {
1375 if ($length <= 16_777_215) {
1376 return 'MEDIUMBLOB';
1382 if (!empty($field[
'length'])) {
1383 $length = $field[
'length'];
1388 if ($length === 2) {
1392 if ($length === 3) {
1396 if ($length === 4) {
1407 return 'TINYINT(1)';
1417 $length = empty($field[
'length']) ? 18 : $field[
'length'];
1419 $scale = empty($field[
'scale']) ? $db->options[
'decimal_places'] : $field[
'scale'];
1421 return 'DECIMAL(' . $length .
',' . $scale .
')';
1432 $db_type = strtolower((
string) $field[
'type']);
1433 $db_type = strtok($db_type,
'(), ');
1434 if ($db_type ===
'national') {
1435 $db_type = strtok(
'(), ');
1437 if (!empty($field[
'length'])) {
1438 $length = strtok($field[
'length'],
', ');
1439 $decimal = strtok(
', ');
1441 $length = strtok(
'(), ');
1442 $decimal = strtok(
'(), ');
1445 $unsigned = $fixed =
null;
1448 $type[] =
'integer';
1449 $type[] =
'boolean';
1450 if (preg_match(
'/^(is|has)/', (
string) $field[
'name'])) {
1451 $type = array_reverse($type);
1453 $unsigned = preg_match(
'/ unsigned/i', (
string) $field[
'type']);
1457 $type[] =
'integer';
1458 $unsigned = preg_match(
'/ unsigned/i', (
string) $field[
'type']);
1462 $type[] =
'integer';
1463 $unsigned = preg_match(
'/ unsigned/i', (
string) $field[
'type']);
1468 $type[] =
'integer';
1469 $unsigned = preg_match(
'/ unsigned/i', (
string) $field[
'type']);
1473 $type[] =
'integer';
1474 $unsigned = preg_match(
'/ unsigned/i', (
string) $field[
'type']);
1487 if ($length ==
'1') {
1488 $type[] =
'boolean';
1489 if (preg_match(
'/^(is|has)/', (
string) $field[
'name'])) {
1490 $type = array_reverse($type);
1492 } elseif (str_contains($db_type,
'text')) {
1494 if ($decimal ===
'binary') {
1498 if ($fixed !==
false) {
1504 preg_match_all(
'/\'.+\'/U', (
string) $field[
'type'], $matches);
1507 if (is_array($matches)) {
1508 foreach ($matches[0] as $value) {
1509 $length = max($length, strlen($value) - 2);
1511 if ($length ==
'1' && count($matches[0]) === 2) {
1512 $type[] =
'boolean';
1513 if (preg_match(
'/^(is|has)/', (
string) $field[
'name'])) {
1514 $type = array_reverse($type);
1518 $type[] =
'integer';
1523 $type[] =
'integer';
1531 $type[] =
'timestamp';
1542 $unsigned = preg_match(
'/ unsigned/i', (
string) $field[
'type']);
1547 $type[] =
'decimal';
1548 $unsigned = preg_match(
'/ unsigned/i', (
string) $field[
'type']);
1549 if ($decimal !==
false) {
1550 $length = $length .
',' . $decimal;
1565 $type[] =
'integer';
1573 if ((
int) $length <= 0) {
1577 return [ $type, $length, $unsigned, $fixed ];
Class ilDBPdoMySQLFieldDefinition.
array $valid_default_values
const DEFINITION_COLUMN_NAME
getTypeDeclaration(array $field)
matchPattern(array $pattern, $operator=null, $field=null)
checkColumnDefinition(array $a_def)
getDeclaration(string $type, string $name, array $field)
checkTableName(string $table_name)
const DEFAULT_TEXT_LENGTH
isAllowedAttribute(string $attribute, string $type)
const DEFINITION_TABLE_NAME
array $allowed_attributes
setAllowedAttributes(array $allowed_attributes)
checkIndexName(string $a_name)
const MAX_TABLE_IDENTIFIER_LENGTH
mapPrepareDatatype(string $type)
writeLOBToFile($lob, string $file)
setMaxLength(array $max_length)
mapNativeDatatype(array $field)
__construct(protected \ilDBInterface $db_instance)
quote($value, ?string $type=null, bool $quote=true, bool $escape_wildcards=false)
array $allowed_attributes_old
ilMySQLQueryUtils $query_utils
compareDefinition(array $current, array $previous)
setAvailableTypes(array $available_types)
mapNativeDatatypeInternal(array $field)
setReservedMysql(array $reserved_mysql)
checkColumnName(string $column_name)
isReserved(string $table_name)
const DEFAULT_DECIMAL_PLACES
getDeclarationOptions(array $field)
Class ilDatabaseException.