diff --git a/.gitignore b/.gitignore index 8b63406c72b1..84f313d8961c 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,4 @@ scalability_jobs_* .serena .merge_conflicts +build.dir/ diff --git a/include/my_base.h b/include/my_base.h index 66a4b1e556b7..df03e746f7c6 100644 --- a/include/my_base.h +++ b/include/my_base.h @@ -105,10 +105,11 @@ enum ha_key_alg { SEs default algorithm for keys in mysql_prepare_create_table(). */ HA_KEY_ALG_SE_SPECIFIC = 0, - HA_KEY_ALG_BTREE = 1, /* B-tree. */ - HA_KEY_ALG_RTREE = 2, /* R-tree, for spatial searches */ - HA_KEY_ALG_HASH = 3, /* HASH keys (HEAP, NDB). */ - HA_KEY_ALG_FULLTEXT = 4 /* FULLTEXT. */ + HA_KEY_ALG_BTREE = 1, /* B-tree. */ + HA_KEY_ALG_RTREE = 2, /* R-tree, for spatial searches */ + HA_KEY_ALG_HASH = 3, /* HASH keys (HEAP, NDB). */ + HA_KEY_ALG_FULLTEXT = 4, /* FULLTEXT. */ + HA_KEY_ALG_VECTOR = 5, /* VECTOR. */ }; /* Storage media types */ @@ -521,11 +522,14 @@ enum ha_base_keytype { #define HA_USES_COMMENT (1 << 12) /** Key was automatically created to support Foreign Key constraint. */ #define HA_GENERATED_KEY (1 << 13) +/** Vector key (Percona). */ +#define HA_VECTOR (1 << 30) /* The combination of the above can be used for key type comparison. */ #define HA_KEYFLAG_MASK \ (HA_NOSAME | HA_PACK_KEY | HA_AUTO_KEY | HA_BINARY_PACK_KEY | HA_FULLTEXT | \ - HA_UNIQUE_CHECK | HA_SPATIAL | HA_NULL_ARE_EQUAL | HA_GENERATED_KEY) + HA_UNIQUE_CHECK | HA_SPATIAL | HA_NULL_ARE_EQUAL | HA_GENERATED_KEY | \ + HA_VECTOR) /** Fulltext index uses [pre]parser */ #define HA_USES_PARSER (1 << 14) diff --git a/mysql-test/suite/percona/r/vector_index_syntax.result b/mysql-test/suite/percona/r/vector_index_syntax.result new file mode 100644 index 000000000000..67cd8009f75f --- /dev/null +++ b/mysql-test/suite/percona/r/vector_index_syntax.result @@ -0,0 +1,266 @@ +CREATE TABLE t1 ( +id BIGINT UNSIGNED PRIMARY KEY, +v1 VECTOR( 1234 ), +VECTOR KEY( v1 ) TYPE hnsw WITH ( M = 6 ) +); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint unsigned NOT NULL, + `v1` vector(1234) DEFAULT NULL, + PRIMARY KEY (`id`), + VECTOR KEY `v1` (`v1`) TYPE `hnsw` WITH (`M`=6) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +SHOW INDEXES FROM t1; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression +t1 0 PRIMARY 1 id A 0 NULL NULL BTREE YES NULL +t1 1 v1 1 v1 A 0 1 NULL YES VECTOR YES NULL +ALTER TABLE t1 DROP PRIMARY KEY; +ERROR HY000: Vector index can only be created in tables with a BIGINT UNSIGNED primary key. +ALTER TABLE t1 ADD INDEX v2 (v1); +ERROR 42000: Specified key was too long; max key length is 3072 bytes +ALTER TABLE t1 ADD INDEX v2 (v1(1)); +ALTER TABLE t1 ADD UNIQUE (v1); +ERROR 42000: Specified key was too long; max key length is 3072 bytes +ALTER TABLE t1 ADD UNIQUE (v1(1)); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint unsigned NOT NULL, + `v1` vector(1234) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `v1_2` (`v1`(1)), + VECTOR KEY `v1` (`v1`) TYPE `hnsw`, + KEY `v2` (`v1`(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +ALTER TABLE t1 DROP INDEX v1; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint unsigned NOT NULL, + `v1` vector(1234) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `v1_2` (`v1`(1)), + KEY `v2` (`v1`(1)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +SHOW INDEXES FROM t1; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression +t1 0 PRIMARY 1 id A 0 NULL NULL BTREE YES NULL +t1 0 v1_2 1 v1 A 0 1 NULL YES BTREE YES NULL +t1 1 v2 1 v1 A 0 1 NULL YES BTREE YES NULL +DROP TABLE t1; +CREATE TABLE t1 ( +id BIGINT UNSIGNED PRIMARY KEY, +v1 VECTOR( 8 ) +); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint unsigned NOT NULL, + `v1` vector(8) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +SHOW INDEXES FROM t1; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression +t1 0 PRIMARY 1 id A 0 NULL NULL BTREE YES NULL +ALTER TABLE t1 ADD INDEX ix (v1); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint unsigned NOT NULL, + `v1` vector(8) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `ix` (`v1`(32)) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +SHOW INDEXES FROM t1; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression +t1 0 PRIMARY 1 id A 0 NULL NULL BTREE YES NULL +t1 1 ix 1 v1 A 0 32 NULL YES BTREE YES NULL +ALTER TABLE t1 ADD VECTOR INDEX vx (v1) TYPE hnsw; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint unsigned NOT NULL, + `v1` vector(8) DEFAULT NULL, + PRIMARY KEY (`id`), + KEY `ix` (`v1`(32)), + VECTOR KEY `vx` (`v1`) TYPE `hnsw` +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +SHOW INDEXES FROM t1; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression +t1 0 PRIMARY 1 id A 0 NULL NULL BTREE YES NULL +t1 1 ix 1 v1 A 0 32 NULL YES BTREE YES NULL +t1 1 vx 1 v1 A 0 1 NULL YES VECTOR YES NULL +DROP TABLE t1; +CREATE TABLE t1 ( +id BIGINT UNSIGNED PRIMARY KEY, +v1 VECTOR( 1234 ), +VECTOR KEY( v1 ) TYPE hnsw COMMENT 'abc' WITH ( M = 6 ) +); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'WITH ( M = 6 ) +)' at line 4 +CREATE TABLE t1 ( +id BIGINT UNSIGNED PRIMARY KEY, +v1 VECTOR( 1234 ), +VECTOR KEY( v1 ) TYPE hnsw WITH ( M = 6 ) COMMENT 'abc' +); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint unsigned NOT NULL, + `v1` vector(1234) DEFAULT NULL, + PRIMARY KEY (`id`), + VECTOR KEY `v1` (`v1`) TYPE `hnsw` WITH (`M`=6) COMMENT 'abc' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +SHOW INDEXES FROM t1; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression +t1 0 PRIMARY 1 id A 0 NULL NULL BTREE YES NULL +t1 1 v1 1 v1 A 0 1 NULL YES VECTOR abc YES NULL +DROP TABLE t1; +CREATE TABLE t1 ( +id BIGINT UNSIGNED PRIMARY KEY, +v1 VECTOR( 1234 ) +); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint unsigned NOT NULL, + `v1` vector(1234) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +SHOW INDEXES FROM t1; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression +t1 0 PRIMARY 1 id A 0 NULL NULL BTREE YES NULL +CREATE VECTOR INDEX ix ON t1(v1) TYPE Hnsw WITH (); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ')' at line 1 +CREATE VECTOR INDEX ix ON t1(v1) TYPE Hnsw WITH ( M = 6.0 ); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '6.0 )' at line 1 +CREATE VECTOR INDEX ix ON t1(v1) TYPE Hnsw WITH ( M = "6" ); +ERROR 42000: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '"6" )' at line 1 +CREATE VECTOR INDEX ix ON t1(v1) TYPE Hnsw WITH ( bogus = 1 ); +ERROR HY000: Illegal index construction parameter: bogus. +CREATE VECTOR INDEX ix ON t1(v1) TYPE no_such_type; +ERROR HY000: The index type no_such_type is not supported for vector indexes. +CREATE VECTOR INDEX ix ON t1(v1); +ERROR HY000: Type is required for vector indexes. +CREATE VECTOR INDEX ix ON t1(v1) TYPE Hnsw WITH ( m = 6 ); +SHOW INDEXES FROM t1; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression +t1 0 PRIMARY 1 id A 0 NULL NULL BTREE YES NULL +t1 1 ix 1 v1 A 0 1 NULL YES VECTOR YES NULL +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint unsigned NOT NULL, + `v1` vector(1234) DEFAULT NULL, + PRIMARY KEY (`id`), + VECTOR KEY `ix` (`v1`) TYPE `Hnsw` WITH (`m`=6) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +ALTER TABLE t1 DROP PRIMARY KEY; +ERROR HY000: Vector index can only be created in tables with a BIGINT UNSIGNED primary key. +ALTER TABLE t1 DROP INDEX ix; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint unsigned NOT NULL, + `v1` vector(1234) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +SHOW INDEXES FROM t1; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression +t1 0 PRIMARY 1 id A 0 NULL NULL BTREE YES NULL +CREATE VECTOR INDEX ix ON t1(v1) TYPE Hnsw WITH ( m = 6, metric = Euclidean ); +SHOW INDEXES FROM t1; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression +t1 0 PRIMARY 1 id A 0 NULL NULL BTREE YES NULL +t1 1 ix 1 v1 A 0 1 NULL YES VECTOR YES NULL +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint unsigned NOT NULL, + `v1` vector(1234) DEFAULT NULL, + PRIMARY KEY (`id`), + VECTOR KEY `ix` (`v1`) TYPE `Hnsw` WITH (`m`=6, `metric`=Euclidean) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +ALTER TABLE t1 DROP INDEX ix; +CREATE VECTOR INDEX ix ON t1(v1) TYPE Hnsw WITH ( metric = Euclidean, m = 6 ); +SHOW INDEXES FROM t1; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression +t1 0 PRIMARY 1 id A 0 NULL NULL BTREE YES NULL +t1 1 ix 1 v1 A 0 1 NULL YES VECTOR YES NULL +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint unsigned NOT NULL, + `v1` vector(1234) DEFAULT NULL, + PRIMARY KEY (`id`), + VECTOR KEY `ix` (`v1`) TYPE `Hnsw` WITH (`metric`=Euclidean, `m`=6) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +ALTER TABLE t1 DROP INDEX ix; +ALTER TABLE t1 DROP PRIMARY KEY; +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint unsigned NOT NULL, + `v1` vector(1234) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +SHOW INDEXES FROM t1; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression +DROP TABLE t1; +CREATE TABLE t1 ( +id BIGINT UNSIGNED PRIMARY KEY, +v1 VECTOR( 1234 ), +VECTOR KEY( v1 ) +); +ERROR HY000: Type is required for vector indexes. +CREATE TABLE t1 ( +id BIGINT UNSIGNED PRIMARY KEY, +v1 VECTOR( 1234 ), +VECTOR KEY( v1 ) USING btree +); +ERROR HY000: The index type BTREE is not supported for vector indexes. +CREATE TABLE t1 ( +id BIGINT UNSIGNED PRIMARY KEY, +v1 VECTOR( 1234 ), +VECTOR KEY( v1 ) USING rtree +); +ERROR HY000: The index type RTREE is not supported for vector indexes. +CREATE TABLE t1 ( +id BIGINT UNSIGNED PRIMARY KEY, +v1 VECTOR( 1234 ), +VECTOR KEY( v1 ) USING hash +); +ERROR HY000: The index type HASH is not supported for vector indexes. +CREATE TABLE t1 ( +id BIGINT UNSIGNED PRIMARY KEY, +c INT, +VECTOR KEY (c) TYPE hnsw +); +ERROR HY000: A VECTOR index may only contain a vector type column. +CREATE TABLE t1 ( +id BIGINT UNSIGNED PRIMARY KEY, +v1 VECTOR( 1234 ), +v2 VECTOR( 1234 ) +); +CREATE VECTOR INDEX ix ON t1( v1, v2 ) TYPE hnsw; +ERROR 42000: Too many key parts specified; max 1 parts allowed +DROP TABLE t1; +CREATE TABLE t1 ( +id BIGINT UNSIGNED NOT NULL, +v1 VECTOR(8), +UNIQUE KEY u_id (id), +VECTOR KEY (v1) TYPE hnsw +); +SHOW CREATE TABLE t1; +Table Create Table +t1 CREATE TABLE `t1` ( + `id` bigint unsigned NOT NULL, + `v1` vector(8) DEFAULT NULL, + UNIQUE KEY `u_id` (`id`), + VECTOR KEY `v1` (`v1`) TYPE `hnsw` +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci +SHOW INDEXES FROM t1; +Table Non_unique Key_name Seq_in_index Column_name Collation Cardinality Sub_part Packed Null Index_type Comment Index_comment Visible Expression +t1 0 u_id 1 id A 0 NULL NULL BTREE YES NULL +t1 1 v1 1 v1 A 0 1 NULL YES VECTOR YES NULL +DROP TABLE t1; diff --git a/mysql-test/suite/percona/t/vector_index_syntax.test b/mysql-test/suite/percona/t/vector_index_syntax.test new file mode 100644 index 000000000000..4a8f78df439b --- /dev/null +++ b/mysql-test/suite/percona/t/vector_index_syntax.test @@ -0,0 +1,172 @@ +CREATE TABLE t1 ( + id BIGINT UNSIGNED PRIMARY KEY, + v1 VECTOR( 1234 ), + VECTOR KEY( v1 ) TYPE hnsw WITH ( M = 6 ) +); +SHOW CREATE TABLE t1; +SHOW INDEXES FROM t1; + +--error ER_VECTOR_INDEX_NEEDS_PK +ALTER TABLE t1 DROP PRIMARY KEY; + +--error ER_TOO_LONG_KEY +ALTER TABLE t1 ADD INDEX v2 (v1); +ALTER TABLE t1 ADD INDEX v2 (v1(1)); +--error ER_TOO_LONG_KEY +ALTER TABLE t1 ADD UNIQUE (v1); +ALTER TABLE t1 ADD UNIQUE (v1(1)); + +SHOW CREATE TABLE t1; + +ALTER TABLE t1 DROP INDEX v1; +SHOW CREATE TABLE t1; +SHOW INDEXES FROM t1; + +DROP TABLE t1; + + +CREATE TABLE t1 ( + id BIGINT UNSIGNED PRIMARY KEY, + v1 VECTOR( 8 ) +); +SHOW CREATE TABLE t1; +SHOW INDEXES FROM t1; + +ALTER TABLE t1 ADD INDEX ix (v1); + +SHOW CREATE TABLE t1; +SHOW INDEXES FROM t1; + +ALTER TABLE t1 ADD VECTOR INDEX vx (v1) TYPE hnsw; + +SHOW CREATE TABLE t1; +SHOW INDEXES FROM t1; + +DROP TABLE t1; + +--error ER_PARSE_ERROR +CREATE TABLE t1 ( + id BIGINT UNSIGNED PRIMARY KEY, + v1 VECTOR( 1234 ), + VECTOR KEY( v1 ) TYPE hnsw COMMENT 'abc' WITH ( M = 6 ) +); + + +CREATE TABLE t1 ( + id BIGINT UNSIGNED PRIMARY KEY, + v1 VECTOR( 1234 ), + VECTOR KEY( v1 ) TYPE hnsw WITH ( M = 6 ) COMMENT 'abc' +); +SHOW CREATE TABLE t1; +SHOW INDEXES FROM t1; + +DROP TABLE t1; + + +CREATE TABLE t1 ( + id BIGINT UNSIGNED PRIMARY KEY, + v1 VECTOR( 1234 ) +); +SHOW CREATE TABLE t1; +SHOW INDEXES FROM t1; + + +--error ER_PARSE_ERROR +CREATE VECTOR INDEX ix ON t1(v1) TYPE Hnsw WITH (); +--error ER_PARSE_ERROR +CREATE VECTOR INDEX ix ON t1(v1) TYPE Hnsw WITH ( M = 6.0 ); +--error ER_PARSE_ERROR +CREATE VECTOR INDEX ix ON t1(v1) TYPE Hnsw WITH ( M = "6" ); +--error ER_ILLEGAL_INDEX_CONSTRUCTION_PARAMETER +CREATE VECTOR INDEX ix ON t1(v1) TYPE Hnsw WITH ( bogus = 1 ); +--error ER_INDEX_TYPE_NOT_SUPPORTED +CREATE VECTOR INDEX ix ON t1(v1) TYPE no_such_type; +--error ER_NO_INDEX_TYPE +CREATE VECTOR INDEX ix ON t1(v1); + +CREATE VECTOR INDEX ix ON t1(v1) TYPE Hnsw WITH ( m = 6 ); +SHOW INDEXES FROM t1; +SHOW CREATE TABLE t1; + +--error ER_VECTOR_INDEX_NEEDS_PK +ALTER TABLE t1 DROP PRIMARY KEY; + +ALTER TABLE t1 DROP INDEX ix; +SHOW CREATE TABLE t1; +SHOW INDEXES FROM t1; + +CREATE VECTOR INDEX ix ON t1(v1) TYPE Hnsw WITH ( m = 6, metric = Euclidean ); +SHOW INDEXES FROM t1; +SHOW CREATE TABLE t1; +ALTER TABLE t1 DROP INDEX ix; + +CREATE VECTOR INDEX ix ON t1(v1) TYPE Hnsw WITH ( metric = Euclidean, m = 6 ); +SHOW INDEXES FROM t1; +SHOW CREATE TABLE t1; +ALTER TABLE t1 DROP INDEX ix; + + +ALTER TABLE t1 DROP PRIMARY KEY; +SHOW CREATE TABLE t1; +SHOW INDEXES FROM t1; + +DROP TABLE t1; + + +--error ER_NO_INDEX_TYPE +CREATE TABLE t1 ( + id BIGINT UNSIGNED PRIMARY KEY, + v1 VECTOR( 1234 ), + VECTOR KEY( v1 ) +); + +--error ER_INDEX_TYPE_NOT_SUPPORTED +CREATE TABLE t1 ( + id BIGINT UNSIGNED PRIMARY KEY, + v1 VECTOR( 1234 ), + VECTOR KEY( v1 ) USING btree +); + +--error ER_INDEX_TYPE_NOT_SUPPORTED +CREATE TABLE t1 ( + id BIGINT UNSIGNED PRIMARY KEY, + v1 VECTOR( 1234 ), + VECTOR KEY( v1 ) USING rtree +); + +--error ER_INDEX_TYPE_NOT_SUPPORTED +CREATE TABLE t1 ( + id BIGINT UNSIGNED PRIMARY KEY, + v1 VECTOR( 1234 ), + VECTOR KEY( v1 ) USING hash +); + +--error ER_INDEX_MUST_HAVE_COMPATIBLE_COLUMN +CREATE TABLE t1 ( + id BIGINT UNSIGNED PRIMARY KEY, + c INT, + VECTOR KEY (c) TYPE hnsw +); + +CREATE TABLE t1 ( + id BIGINT UNSIGNED PRIMARY KEY, + v1 VECTOR( 1234 ), + v2 VECTOR( 1234 ) +); + +--error ER_TOO_MANY_KEY_PARTS +CREATE VECTOR INDEX ix ON t1( v1, v2 ) TYPE hnsw; + +DROP TABLE t1; + + +CREATE TABLE t1 ( + id BIGINT UNSIGNED NOT NULL, + v1 VECTOR(8), + UNIQUE KEY u_id (id), + VECTOR KEY (v1) TYPE hnsw +); +SHOW CREATE TABLE t1; +SHOW INDEXES FROM t1; + +DROP TABLE t1; diff --git a/share/messages_to_clients.txt b/share/messages_to_clients.txt index e8a5df1a936d..bbb18ef8bcd7 100644 --- a/share/messages_to_clients.txt +++ b/share/messages_to_clients.txt @@ -11112,6 +11112,27 @@ ER_LOG_NAME_NOT_MATCHING_SEC_LOG_PATH_CLIENT # Start of Percona Server 8.4/9.7 error messages to be sent to client # +ER_INDEX_MUST_HAVE_COMPATIBLE_COLUMN + eng "A %.20s index may only contain a %.20s type column." + +ER_VECTOR_INDEX_NEEDS_PK + eng "Vector index can only be created in tables with a BIGINT UNSIGNED primary key." + +ER_ONLY_SINGLE_VECTOR_INDEX_ALLOWED + eng "A table can have at most one vector index." + +ER_INDEX_TYPE_NOT_SUPPORTED + eng "The index type %.20s is not supported for %.20s indexes." + +ER_NO_INDEX_TYPE + eng "Type is required for vector indexes." + +ER_ILLEGAL_INDEX_CONSTRUCTION_PARAMETER + eng "Illegal index construction parameter: %.20s." + +ER_ILLEGAL_INDEX_CONSTRUCTION_PARAMETER_VALUE + eng "Illegal index construction parameter value: %.20s." + start-error-number 7100 # diff --git a/sql/create_field.cc b/sql/create_field.cc index 168d5293cd94..ac61abbaeeca 100644 --- a/sql/create_field.cc +++ b/sql/create_field.cc @@ -23,6 +23,7 @@ #include "sql/create_field.h" +#include "field_types.h" #include "m_string.h" #include "mysql/strings/dtoa.h" #include "sql-common/my_decimal.h" @@ -200,9 +201,10 @@ bool Create_field::init( const LEX_CSTRING *fld_comment, const char *fld_change, List *fld_interval_list, const CHARSET_INFO *fld_charset, bool has_explicit_collation, uint fld_geom_type, - const LEX_CSTRING *fld_zip_dict_name, Value_generator *fld_gcol_info, Value_generator *fld_default_val_expr, - LEX_CSTRING fld_masking_policy, std::optional srid, - dd::Column::enum_hidden_type hidden, bool is_array_arg) { + const LEX_CSTRING *fld_zip_dict_name, Value_generator *fld_gcol_info, + Value_generator *fld_default_val_expr, LEX_CSTRING fld_masking_policy, + std::optional srid, dd::Column::enum_hidden_type hidden, + bool is_array_arg) { uint sign_len, allowed_type_modifier = 0; ulong max_field_charlength = MAX_FIELD_CHARLENGTH; @@ -780,7 +782,8 @@ size_t Create_field::key_length() const { case MYSQL_TYPE_JSON: case MYSQL_TYPE_VAR_STRING: case MYSQL_TYPE_STRING: - case MYSQL_TYPE_VARCHAR: { + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VECTOR: { return std::min(max_display_width_in_bytes(), static_cast(MAX_FIELD_BLOBLENGTH)); } @@ -794,10 +797,6 @@ size_t Create_field::key_length() const { } return pack_length() + (max_display_width_in_bytes() & 7 ? 1 : 0); } - /* LCOV_EXCL_START */ - case MYSQL_TYPE_VECTOR: - assert(false); // Key on VECTOR type column is not supported. - /* LCOV_EXCL_STOP */ default: { return pack_length(is_array); } diff --git a/sql/dd/dd_table.cc b/sql/dd/dd_table.cc index 8a14c56e8599..4f3f56c9e3c5 100644 --- a/sql/dd/dd_table.cc +++ b/sql/dd/dd_table.cc @@ -735,6 +735,10 @@ bool fill_dd_columns_from_create_fields(THD *thd, dd::Abstract_table *tab_obj, col_options->set("is_array", true); } + if (field.sql_type == MYSQL_TYPE_VECTOR) { + col_options->set("vector_index", true); + } + // // Write intervals // @@ -825,6 +829,9 @@ static dd::Index::enum_index_algorithm dd_get_new_index_algorithm_type( case HA_KEY_ALG_FULLTEXT: return dd::Index::IA_FULLTEXT; + + case HA_KEY_ALG_VECTOR: + return dd::Index::IA_SE_SPECIFIC; } /* purecov: begin deadcode */ @@ -836,6 +843,8 @@ static dd::Index::enum_index_algorithm dd_get_new_index_algorithm_type( } static dd::Index::enum_index_type dd_get_new_index_type(const KEY *key) { + if (key->flags & HA_VECTOR) return dd::Index::IT_MULTIPLE; + if (key->flags & HA_FULLTEXT) return dd::Index::IT_FULLTEXT; if (key->flags & HA_SPATIAL) return dd::Index::IT_SPATIAL; @@ -1132,6 +1141,18 @@ static void fill_dd_indexes_from_keyinfo( if (key->parser_name.str) idx_options->set("parser_name", key->parser_name.str); + if (key->vector_index_type.length > 0) { + idx_options->set("vector_index_type", + dd::String_type(key->vector_index_type.str, + key->vector_index_type.length)); + } + + if (key->vector_construction_params.length > 0) { + idx_options->set("vector_construction_params", + dd::String_type(key->vector_construction_params.str, + key->vector_construction_params.length)); + } + /* If we have no primary key, then we pick the first candidate primary key and promote it. When we promote, the field's of key_part needs to diff --git a/sql/dd/impl/types/column_impl.cc b/sql/dd/impl/types/column_impl.cc index 34cb323def7b..1b6669ed6bc4 100644 --- a/sql/dd/impl/types/column_impl.cc +++ b/sql/dd/impl/types/column_impl.cc @@ -64,11 +64,17 @@ class Sdi_rcontext; class Sdi_wcontext; static const std::set default_valid_option_keys = { - "column_format", "geom_type", - "interval_count", "not_secondary", - "storage", "treat_bit_as_char", "zip_dict_id", - "is_array", "gipk" /* generated implicit primary key column */, - "masking_policy"}; + "column_format", + "geom_type", + "interval_count", + "not_secondary", + "storage", + "treat_bit_as_char", + "zip_dict_id", + "is_array", + "gipk" /* generated implicit primary key column */, + "masking_policy", + "vector_index"}; /////////////////////////////////////////////////////////////////////////// // Column_impl implementation. diff --git a/sql/dd/impl/types/index_impl.cc b/sql/dd/impl/types/index_impl.cc index a59046aa3eac..a40215bb037f 100644 --- a/sql/dd/impl/types/index_impl.cc +++ b/sql/dd/impl/types/index_impl.cc @@ -65,8 +65,10 @@ class Sdi_wcontext; class Table; static const std::set default_valid_option_keys = { - "block_size", "flags", "parser_name", - "gipk" /* generated implicit primary key */}; + "block_size", "flags", + "parser_name", "gipk", /* generated implicit primary key */ + "vector_index_type", "vector_construction_params", +}; /////////////////////////////////////////////////////////////////////////// // Index_impl implementation. diff --git a/sql/dd/info_schema/show.cc b/sql/dd/info_schema/show.cc index f37078e4fce8..94cc320d3552 100644 --- a/sql/dd/info_schema/show.cc +++ b/sql/dd/info_schema/show.cc @@ -868,6 +868,50 @@ Query_block *build_show_keys_query(const POS &pos, THD *thd, Select_lex_builder top_query(&pos, thd); + Item *index_type_item = + new (thd->mem_root) Item_field(pos, NullS, NullS, alias_type.str); + if (index_type_item == nullptr) return nullptr; + + Item *se_specific_item = new (thd->mem_root) + Item_string(STRING_WITH_LEN("SE_SPECIFIC"), system_charset_info); + if (se_specific_item == nullptr) return nullptr; + + Item *is_se_specific = + new (thd->mem_root) Item_func_eq(pos, index_type_item, se_specific_item); + if (is_se_specific == nullptr) return nullptr; + + Item *sub_part_item = + new (thd->mem_root) Item_field(pos, NullS, NullS, alias_sub_part.str); + if (sub_part_item == nullptr) return nullptr; + + Item *one_item = new (thd->mem_root) + Item_string(STRING_WITH_LEN("1"), system_charset_info); + if (one_item == nullptr) return nullptr; + + Item *is_single_sub_part = + new (thd->mem_root) Item_func_eq(pos, sub_part_item, one_item); + if (is_single_sub_part == nullptr) return nullptr; + + Item *vector_item = new (thd->mem_root) + Item_string(STRING_WITH_LEN("VECTOR"), system_charset_info); + if (vector_item == nullptr) return nullptr; + + Item *index_type_else_item = + new (thd->mem_root) Item_field(pos, NullS, NullS, alias_type.str); + if (index_type_else_item == nullptr) return nullptr; + + Item *index_type_if = new (thd->mem_root) + Item_func_if(pos, is_single_sub_part, vector_item, index_type_else_item); + if (index_type_if == nullptr) return nullptr; + + Item *index_type_default_item = + new (thd->mem_root) Item_field(pos, NullS, NullS, alias_type.str); + if (index_type_default_item == nullptr) return nullptr; + + Item *index_type_expr = new (thd->mem_root) + Item_func_if(pos, is_se_specific, index_type_if, index_type_default_item); + if (index_type_expr == nullptr) return nullptr; + // SELECT * FROM ... if (top_query.add_select_item(alias_table, alias_table) || top_query.add_select_item(alias_non_unique, alias_non_unique) || @@ -879,7 +923,7 @@ Query_block *build_show_keys_query(const POS &pos, THD *thd, top_query.add_select_item(alias_sub_part, alias_sub_part) || top_query.add_select_item(alias_packed, alias_packed) || top_query.add_select_item(alias_null, alias_null) || - top_query.add_select_item(alias_type, alias_type) || + top_query.add_select_expr(index_type_expr, alias_type) || top_query.add_select_item(alias_comment, alias_comment) || top_query.add_select_item(alias_index_comment, alias_index_comment) || top_query.add_select_item(alias_visible, alias_visible) || diff --git a/sql/dd_table_share.cc b/sql/dd_table_share.cc index c5504726c0a9..38890d3d5e39 100644 --- a/sql/dd_table_share.cc +++ b/sql/dd_table_share.cc @@ -235,6 +235,29 @@ static enum ha_key_alg dd_get_old_index_algorithm_type( return HA_KEY_ALG_SE_SPECIFIC; } +/** + Check whether any visible index element is marked as vector. + + @param[in] idx_obj Index metadata object. + + @return Whether any visible element belongs to a vector column. +*/ +static bool dd_index_has_vector_column(const dd::Index &idx_obj) { + for (const dd::Index_element *idx_elem : idx_obj.elements()) { + if (idx_elem->is_hidden()) continue; + + const dd::Properties &col_options = idx_elem->column().options(); + bool is_vector_column = false; + if (col_options.exists("vector_index") && + !col_options.get("vector_index", &is_vector_column) && + is_vector_column) { + return true; + } + } + + return false; +} + /* Check if the given key_part is suitable to be promoted as part of primary key. @@ -347,6 +370,8 @@ static bool prepare_share(THD *thd, TABLE_SHARE *share, share->key_info[key].algorithm == HA_KEY_ALG_FULLTEXT); assert(!(share->key_info[key].flags & HA_SPATIAL) || share->key_info[key].algorithm == HA_KEY_ALG_RTREE); + assert(!(share->key_info[key].flags & HA_VECTOR) || + share->key_info[key].algorithm == HA_KEY_ALG_VECTOR); if (primary_key >= MAX_KEY && (keyinfo->flags & HA_NOSAME)) { /* @@ -1385,6 +1410,8 @@ static bool fill_index_from_dd(THD *thd, TABLE_SHARE *share, keyinfo->algorithm = dd_get_old_index_algorithm_type(idx_obj->algorithm()); keyinfo->is_algorithm_explicit = idx_obj->is_algorithm_explicit(); + const bool has_vector_column = dd_index_has_vector_column(*idx_obj); + // Visibility keyinfo->is_visible = idx_obj->is_visible(); @@ -1395,6 +1422,12 @@ static bool fill_index_from_dd(THD *thd, TABLE_SHARE *share, if (!idx_ele->is_hidden()) keyinfo->user_defined_key_parts++; } + if (has_vector_column && keyinfo->user_defined_key_parts == 1 && + idx_obj->options().exists("vector_index_type")) { + keyinfo->algorithm = HA_KEY_ALG_VECTOR; + keyinfo->is_algorithm_explicit = false; + } + // flags switch (idx_obj->type()) { case dd::Index::IT_MULTIPLE: @@ -1416,6 +1449,11 @@ static bool fill_index_from_dd(THD *thd, TABLE_SHARE *share, break; } + if (has_vector_column && keyinfo->user_defined_key_parts == 1 && + idx_obj->options().exists("vector_index_type")) { + keyinfo->flags |= HA_VECTOR; + } + if (idx_obj->is_generated()) keyinfo->flags |= HA_GENERATED_KEY; /* @@ -1512,6 +1550,19 @@ static bool fill_index_from_dd(THD *thd, TABLE_SHARE *share, &share->mem_root, idx_obj->secondary_engine_attribute()); if (keyinfo->secondary_engine_attribute.length > 0) keyinfo->flags |= HA_INDEX_USES_SECONDARY_ENGINE_ATTRIBUTE; + + if (idx_options.exists("vector_index_type")) { + if (idx_options.get("vector_index_type", &keyinfo->vector_index_type, + &share->mem_root)) + assert(false); + } + + if (idx_options.exists("vector_construction_params")) { + if (idx_options.get("vector_construction_params", + &keyinfo->vector_construction_params, &share->mem_root)) + assert(false); + } + return (false); } diff --git a/sql/field.cc b/sql/field.cc index abc98b6f449c..6f969c1c5ddf 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -34,6 +34,7 @@ #include #include "decimal.h" +#include "field_types.h" #include "my_alloc.h" #include "my_byteorder.h" #include "my_compare.h" @@ -75,7 +76,7 @@ #include "sql/mysqld_cs.h" #include "sql/protocol.h" #include "sql/psi_memory_key.h" -#include "sql/spatial.h" // Geometry +#include "sql/spatial.h" // Geometry #include "sql/sql_base.h" #include "sql/sql_class.h" // THD #include "sql/sql_exception_handler.h" // handle_std_exception @@ -1656,6 +1657,7 @@ bool Field::type_can_have_key_part(enum enum_field_types type) { case MYSQL_TYPE_VAR_STRING: case MYSQL_TYPE_STRING: case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_VECTOR: return true; default: return false; diff --git a/sql/handler.h b/sql/handler.h index cdb1ac488eb4..93e5541bde8f 100644 --- a/sql/handler.h +++ b/sql/handler.h @@ -535,6 +535,7 @@ enum class SelectExecutedIn : bool { kPrimaryEngine, kSecondaryEngine }; ANALYZE TABLE on it */ #define HA_ONLINE_ANALYZE (1LL << 56) +#define HA_CAN_VECTOR (1LL << 57) /* Bits in index_flags(index_number) for what you can do with index. @@ -2160,6 +2161,10 @@ using fix_default_table_encryption_t = bool (*)(ulong value, bool is_starting); */ typedef bool (*redo_log_set_state_t)(THD *thd, bool enable); +struct HA_CREATE_INFO; +using validate_engine_attributes_t = bool (*)(THD *thd, const char *db_name, + HA_CREATE_INFO *create_info, + const Alter_info *alter_info); /** @brief Retrieve ha_statistics from SE. @@ -3033,6 +3038,7 @@ struct handlerton { fix_tablespaces_empty_uuid_t fix_tablespaces_empty_uuid; fix_default_table_encryption_t fix_default_table_encryption; redo_log_set_state_t redo_log_set_state; + validate_engine_attributes_t validate_engine_attributes{nullptr}; get_table_statistics_t get_table_statistics; get_column_statistics_t get_column_statistics; @@ -3294,7 +3300,6 @@ inline constexpr const decltype(handlerton::flags) inline constexpr const decltype(handlerton::flags) HTON_SECONDARY_SUPPORTS_TEMPORARY_TABLE(1 << 25); - /** Start of Percona specific HTON_* defines */ /** @@ -3312,7 +3317,6 @@ inline constexpr const decltype(handlerton::flags) /** End of Percona specific HTON_* defines */ - /* Whether the handlerton is a secondary engine. */ inline bool hton_is_secondary_engine(const handlerton *hton) { return hton != nullptr && (hton->flags & HTON_IS_SECONDARY_ENGINE) != 0U; @@ -7055,16 +7059,16 @@ class handler { for details. */ [[nodiscard]] int ha_fast_update(THD *thd, - mem_root_deque &update_fields, - mem_root_deque &update_values, - Item *conds); + mem_root_deque &update_fields, + mem_root_deque &update_values, + Item *conds); /** @brief Offload an upsert to the storage engine. See handler::upsert() for details. */ [[nodiscard]] int ha_upsert(THD *thd, mem_root_deque &update_fields, - mem_root_deque &update_values); + mem_root_deque &update_values); private: /** @@ -7087,11 +7091,11 @@ class handler { handler::ha_update_row(...) does not accept conditions. */ [[nodiscard]] virtual int fast_update(THD *thd [[maybe_unused]], - mem_root_deque &update_fields - [[maybe_unused]], - mem_root_deque &update_values - [[maybe_unused]], - Item *conds [[maybe_unused]]) { + mem_root_deque &update_fields + [[maybe_unused]], + mem_root_deque &update_values + [[maybe_unused]], + Item *conds [[maybe_unused]]) { return ENOTSUP; } @@ -7112,10 +7116,10 @@ class handler { @return an error if the insert should be terminated. */ [[nodiscard]] virtual int upsert(THD *thd [[maybe_unused]], - mem_root_deque &update_fields - [[maybe_unused]], - mem_root_deque &update_values - [[maybe_unused]]) { + mem_root_deque &update_fields + [[maybe_unused]], + mem_root_deque &update_values + [[maybe_unused]]) { return ENOTSUP; } @@ -7624,7 +7628,6 @@ class handler { int get_lock_type() const { return m_lock_type; } - public: /* Read-free replication interface */ diff --git a/sql/key.h b/sql/key.h index 9f57a47bf354..f1a84b7244a2 100644 --- a/sql/key.h +++ b/sql/key.h @@ -170,6 +170,8 @@ class KEY { // the struct LEX_CSTRING engine_attribute{nullptr, 0}; LEX_CSTRING secondary_engine_attribute{nullptr, 0}; + LEX_CSTRING vector_index_type{nullptr, 0}; + LEX_CSTRING vector_construction_params{nullptr, 0}; private: /** diff --git a/sql/key_spec.h b/sql/key_spec.h index b58e0651b759..e573513ef1bf 100644 --- a/sql/key_spec.h +++ b/sql/key_spec.h @@ -34,16 +34,21 @@ class Create_field; class Item; +class PT_index_construction_parameter; class THD; struct MEM_ROOT; +using Index_construction_parameters = + Mem_root_array_YY; + enum keytype { KEYTYPE_PRIMARY = 0, KEYTYPE_UNIQUE = 1, KEYTYPE_MULTIPLE = 2, KEYTYPE_FULLTEXT = 4, KEYTYPE_SPATIAL = 8, - KEYTYPE_FOREIGN = 16 + KEYTYPE_FOREIGN = 16, + KEYTYPE_VECTOR = 32, }; enum fk_option { @@ -75,6 +80,8 @@ class KEY_CREATE_INFO { ulong block_size = 0; LEX_CSTRING parser_name = {NullS, 0}; LEX_CSTRING comment = {NullS, 0}; + LEX_CSTRING vector_index_type = {NullS, 0}; + const Index_construction_parameters *construction_params = nullptr; bool is_visible = true; KEY_CREATE_INFO() = default; @@ -205,6 +212,11 @@ class Key_part_spec { bool m_has_expression; }; +struct IndexConstructionParam { + LEX_CSTRING key; + LEX_CSTRING value; +}; + class Key_spec { public: const keytype type; @@ -219,6 +231,7 @@ class Key_spec { associated with it was dropped. */ const bool check_for_duplicate_indexes; + Mem_root_array construction_params; Key_spec(MEM_ROOT *mem_root, keytype type_par, const LEX_CSTRING &name_arg, const KEY_CREATE_INFO *key_info_arg, bool generated_arg, @@ -228,7 +241,8 @@ class Key_spec { columns(mem_root), name(name_arg), generated(generated_arg), - check_for_duplicate_indexes(check_for_duplicate_indexes_arg) { + check_for_duplicate_indexes(check_for_duplicate_indexes_arg), + construction_params(mem_root) { columns.reserve(cols.elements); List_iterator it(cols); Key_part_spec *column; diff --git a/sql/parse_tree_nodes.cc b/sql/parse_tree_nodes.cc index 43facdc13fc3..901e248f24d4 100644 --- a/sql/parse_tree_nodes.cc +++ b/sql/parse_tree_nodes.cc @@ -1708,8 +1708,8 @@ bool PT_table_factor_function::do_contextualize(Parse_context *pc) { bool PT_table_sequence_function::do_contextualize(Parse_context *pc) { if (super::do_contextualize(pc) || m_expr->itemize(pc, &m_expr)) return true; - auto stf = new (pc->mem_root) - Table_function_sequence(m_table_alias.str, m_expr); + auto stf = + new (pc->mem_root) Table_function_sequence(m_table_alias.str, m_expr); if (stf == nullptr) return true; // OOM LEX_CSTRING alias; @@ -1721,7 +1721,7 @@ bool PT_table_sequence_function::do_contextualize(Parse_context *pc) { if (ti == nullptr) return true; m_table_ref = pc->select->add_table_to_list(pc->thd, ti, m_table_alias.str, 0, - TL_READ, MDL_SHARED_READ); + TL_READ, MDL_SHARED_READ); if (m_table_ref == nullptr) return true; if (pc->select->add_joined_table(m_table_ref)) return true; @@ -2157,6 +2157,12 @@ bool PT_intersect::do_contextualize(Parse_context *pc [[maybe_unused]]) { m_is_distinct ? SC_INTERSECT_DISTINCT : SC_INTERSECT_ALL); } +bool PT_vector_index_type::do_contextualize(Table_ddl_parse_context *pc) { + pc->key_create_info->vector_index_type = m_type_name; + pc->key_create_info->construction_params = &m_construction_params; + return false; +} + static bool setup_index(keytype key_type, const LEX_STRING name, PT_base_index_option *type, List *columns, @@ -2203,6 +2209,15 @@ static bool setup_index(keytype key_type, const LEX_STRING name, pc->key_create_info, false, true, cols); if (key == nullptr || pc->alter_info->key_list.push_back(key)) return true; + if (pc->key_create_info->construction_params != nullptr) { + for (const auto *param : *pc->key_create_info->construction_params) { + if (param == nullptr) return true; + IndexConstructionParam p{to_lex_cstring(param->key()), + to_lex_cstring(param->value())}; + if (key->construction_params.push_back(p)) return true; + } + } + return false; } @@ -2777,8 +2792,7 @@ bool PT_column_def::do_contextualize(Table_ddl_parse_context *pc) { field_def->has_explicit_collation, field_def->uint_geom_type, &field_def->m_zip_dict, field_def->gcol_info, field_def->default_val_info, field_def->masking_policy, opt_place, field_def->m_srid, - field_def->check_const_spec_list, - field_hidden_type); + field_def->check_const_spec_list, field_hidden_type); } Sql_cmd *PT_create_table_stmt::make_cmd(THD *thd) { @@ -3653,9 +3667,10 @@ bool PT_alter_table_change_column::do_contextualize( m_field_def->on_update_value, &m_field_def->comment, m_old_name.str, m_field_def->interval_list, m_field_def->charset, m_field_def->has_explicit_collation, m_field_def->uint_geom_type, - &m_field_def->m_zip_dict, m_field_def->gcol_info, m_field_def->default_val_info, - m_field_def->masking_policy, m_opt_place, m_field_def->m_srid, - m_field_def->check_const_spec_list, field_hidden_type); + &m_field_def->m_zip_dict, m_field_def->gcol_info, + m_field_def->default_val_info, m_field_def->masking_policy, m_opt_place, + m_field_def->m_srid, m_field_def->check_const_spec_list, + field_hidden_type); } bool PT_alter_table_rename::do_contextualize(Table_ddl_parse_context *pc) { diff --git a/sql/parse_tree_nodes.h b/sql/parse_tree_nodes.h index d97f0d91b342..4992f3f643d4 100644 --- a/sql/parse_tree_nodes.h +++ b/sql/parse_tree_nodes.h @@ -530,7 +530,8 @@ class PT_table_sequence_function : public PT_table_reference { typedef PT_table_reference super; public: - PT_table_sequence_function(const POS &pos, Item *expr, const LEX_CSTRING &table_alias) + PT_table_sequence_function(const POS &pos, Item *expr, + const LEX_CSTRING &table_alias) : super(pos), m_expr(expr), m_table_alias(table_alias) {} bool do_contextualize(Parse_context *pc) override; @@ -2507,6 +2508,43 @@ typedef PT_traceable_index_option PT_index_type; +/** + A single key=value parameter in an index construction clause. +*/ +class PT_index_construction_parameter : public Parse_tree_node { + public: + PT_index_construction_parameter(const POS &pos, const LEX_STRING &key, + const LEX_STRING &value) + : Parse_tree_node(pos), m_key(key), m_value(value) {} + + const LEX_STRING &key() const { return m_key; } + const LEX_STRING &value() const { return m_value; } + + private: + LEX_STRING m_key; + LEX_STRING m_value; +}; + +/** + Index option for vector index type with optional construction parameters. + + Represents TYPE [WITH (key=value, ...)] in index definitions. +*/ +class PT_vector_index_type : public PT_base_index_option { + public: + PT_vector_index_type(const POS &pos, LEX_CSTRING type_name, + Index_construction_parameters construction_params) + : PT_base_index_option(pos), + m_type_name(type_name), + m_construction_params(construction_params) {} + + bool do_contextualize(Table_ddl_parse_context *pc) override; + + private: + LEX_CSTRING m_type_name; + Index_construction_parameters m_construction_params; +}; + class PT_create_index_stmt final : public PT_table_ddl_stmt_base { public: PT_create_index_stmt(const POS &pos, MEM_ROOT *mem_root, keytype type_par, diff --git a/sql/parser_yystype.h b/sql/parser_yystype.h index f0dca48ce8b1..277fc8f43cb3 100644 --- a/sql/parser_yystype.h +++ b/sql/parser_yystype.h @@ -82,6 +82,7 @@ class PT_column_def; class PT_common_table_expr; class PT_create_index_stmt; class PT_create_table_option; +class PT_index_construction_parameter; class PT_ddl_table_option; class PT_derived_table; class PT_exclusion; @@ -528,6 +529,9 @@ union MY_SQL_PARSER_STYPE { } index_name_and_type; PT_base_index_option *index_option; Mem_root_array_YY index_options; + PT_index_construction_parameter *index_construction_parameter; + Mem_root_array_YY + index_construction_parameters; Mem_root_array_YY lex_str_list; bool visibility; PT_with_clause *with_clause; diff --git a/sql/sql_show.cc b/sql/sql_show.cc index 52b03e9f785e..c6f1a1d20f25 100644 --- a/sql/sql_show.cc +++ b/sql/sql_show.cc @@ -2791,6 +2791,8 @@ bool store_create_info(THD *thd, Table_ref *table_list, String *packet, packet->append(STRING_WITH_LEN("FULLTEXT KEY ")); else if (key_info->flags & HA_SPATIAL) packet->append(STRING_WITH_LEN("SPATIAL KEY ")); + else if (key_info->flags & HA_VECTOR) + packet->append(STRING_WITH_LEN("VECTOR KEY ")); else packet->append(STRING_WITH_LEN("KEY ")); @@ -2823,7 +2825,7 @@ bool store_create_info(THD *thd, Table_ref *table_list, String *packet, if (key_part->field && (key_part->length != table->field[key_part->fieldnr - 1]->key_length() && - !(key_info->flags & (HA_FULLTEXT | HA_SPATIAL)))) { + !(key_info->flags & (HA_FULLTEXT | HA_SPATIAL | HA_VECTOR)))) { packet->append_parenthesized((long)key_part->length / key_part->field->charset()->mbmaxlen); } @@ -3260,6 +3262,34 @@ static void store_key_options(THD *thd, String *packet, TABLE *table, end = longlong10_to_str(key_info->block_size, buff, 10); packet->append(buff, (uint)(end - buff)); } + + if (key_info->vector_index_type.length > 0) { + packet->append(STRING_WITH_LEN(" TYPE ")); + append_identifier(thd, packet, key_info->vector_index_type.str, + key_info->vector_index_type.length); + } + + if (key_info->vector_construction_params.length > 0) { + packet->append(STRING_WITH_LEN(" WITH (")); + const char *p = key_info->vector_construction_params.str; + const char *end_p = p + key_info->vector_construction_params.length; + bool first = true; + while (p < end_p) { + if (!first) packet->append(STRING_WITH_LEN(", ")); + first = false; + const auto *eq = static_cast(memchr(p, '=', end_p - p)); + if (eq == nullptr) break; + const auto *comma = + static_cast(memchr(eq + 1, ',', end_p - eq - 1)); + const char *val_end = (comma != nullptr) ? comma : end_p; + append_identifier(thd, packet, p, eq - p); + packet->append('='); + packet->append(eq + 1, val_end - eq - 1); + p = (val_end < end_p) ? val_end + 1 : end_p; + } + packet->append(')'); + } + assert(((key_info->flags & HA_USES_COMMENT) != 0) == (key_info->comment.length > 0)); if (key_info->flags & HA_USES_COMMENT) { @@ -5482,6 +5512,23 @@ static int fill_schema_engines(THD *thd, Table_ref *tables, Item *) { #define TMP_TABLE_KEYS_IS_VISIBLE 14 #define TMP_TABLE_KEYS_EXPRESSION 15 +/** + Detect vector indexes for SHOW INDEX output. + + @param[in] key_info index metadata from TABLE_SHARE + + @return Whether this is a vector index. +*/ +static bool is_show_index_vector_type(const KEY *key_info) { + if (key_info->flags & HA_VECTOR) return true; + + if (key_info->user_defined_key_parts != 1) return false; + + const KEY_PART_INFO *key_part = key_info->key_part; + return key_part != nullptr && key_part->field != nullptr && + key_part->field->real_type() == MYSQL_TYPE_VECTOR; +} + static int get_schema_tmp_table_keys_record(THD *thd, Table_ref *tables, TABLE *table, bool res, LEX_CSTRING, LEX_CSTRING table_name) { @@ -5570,6 +5617,8 @@ static int get_schema_tmp_table_keys_record(THD *thd, Table_ref *tables, // INDEX_TYPE if (key_info->flags & HA_SPATIAL) str = "SPATIAL"; + else if (is_show_index_vector_type(key_info)) + str = "VECTOR"; else { const ha_key_alg key_alg = key_info->algorithm; /* If index algorithm is implicit get SE default. */ diff --git a/sql/sql_table.cc b/sql/sql_table.cc index faaf57304faf..7f1147f8591d 100644 --- a/sql/sql_table.cc +++ b/sql/sql_table.cc @@ -5175,8 +5175,8 @@ static bool prepare_key_column(THD *thd, HA_CREATE_INFO *create_info, return true; } - // VECTOR columns cannot be used as keys - if (sql_field->sql_type == MYSQL_TYPE_VECTOR) { + if (sql_field->sql_type == MYSQL_TYPE_VECTOR && + false /* !(key_info->flags & HA_VECTOR) */) { my_error(ER_NON_SCALAR_USED_AS_KEY, MYF(0), column->get_field_name()); return true; } @@ -5227,6 +5227,14 @@ static bool prepare_key_column(THD *thd, HA_CREATE_INFO *create_info, data prefix, ignoring column->length). */ column_length = is_blob(sql_field->sql_type); + } else if (key->type == KEYTYPE_VECTOR) { + // VECTOR indexes are only allowed on VECTOR columns. + if (sql_field->sql_type != MYSQL_TYPE_VECTOR) { + my_error(ER_INDEX_MUST_HAVE_COMPATIBLE_COLUMN, MYF(0), "VECTOR", + "vector"); + return true; + } + column_length = 1; // Dummy value. } else { switch (sql_field->sql_type) { case MYSQL_TYPE_GEOMETRY: @@ -5469,7 +5477,7 @@ static bool prepare_key_column(THD *thd, HA_CREATE_INFO *create_info, } if (key_part_length > file->max_key_part_length(create_info) && - key->type != KEYTYPE_FULLTEXT) { + key->type != KEYTYPE_FULLTEXT && key->type != KEYTYPE_VECTOR) { key_part_length = file->max_key_part_length(create_info); if (key->type & KEYTYPE_MULTIPLE) { /* not a critical problem */ @@ -5833,7 +5841,7 @@ static bool prepare_self_ref_fk_parent_key( for (const KEY *key = key_info_buffer; key < key_info_buffer + key_count; key++) { // We can't use FULLTEXT or SPATIAL indexes. - if (key->flags & (HA_FULLTEXT | HA_SPATIAL)) continue; + if (key->flags & (HA_FULLTEXT | HA_SPATIAL | HA_VECTOR)) continue; if (hton->foreign_keys_flags & HTON_FKS_NEED_DIFFERENT_PARENT_AND_SUPPORTING_KEYS) { @@ -6036,7 +6044,7 @@ static const KEY *find_fk_supporting_key(handlerton *hton, for (const KEY *key = key_info_buffer; key < key_info_buffer + key_count; key++) { // We can't use FULLTEXT or SPATIAL indexes. - if (key->flags & (HA_FULLTEXT | HA_SPATIAL)) continue; + if (key->flags & (HA_FULLTEXT | HA_SPATIAL | HA_VECTOR)) continue; if (key->algorithm == HA_KEY_ALG_HASH) { if (hton->foreign_keys_flags & HTON_FKS_WITH_SUPPORTING_HASH_KEYS) { @@ -7673,6 +7681,13 @@ static bool prepare_key( return true; if (key_info->comment.length > 0) key_info->flags |= HA_USES_COMMENT; + if (key->type == KEYTYPE_VECTOR) { + key_info->vector_index_type.length = + key->key_create_info.vector_index_type.length; + key_info->vector_index_type.str = + key->key_create_info.vector_index_type.str; + } + key_info->engine_attribute = key->key_create_info.m_engine_attribute; if (key_info->engine_attribute.length > 0) key_info->flags |= HA_INDEX_USES_ENGINE_ATTRIBUTE; @@ -7686,6 +7701,17 @@ static bool prepare_key( switch (static_cast(key->type)) { case KEYTYPE_MULTIPLE: break; + case KEYTYPE_VECTOR: + if (!(file->ha_table_flags() & HA_CAN_VECTOR)) { + my_error(ER_UNKNOWN_ERROR, MYF(0)); + return true; + } + if (key->columns.size() != 1) { + my_error(ER_TOO_MANY_KEY_PARTS, MYF(0), 1); + return true; + } + key_info->flags |= HA_VECTOR; + break; case KEYTYPE_FULLTEXT: if (!(file->ha_table_flags() & HA_CAN_FULLTEXT)) { my_error(ER_TABLE_CANT_HANDLE_FT, MYF(0)); @@ -7721,6 +7747,21 @@ static bool prepare_key( assert((key_info->flags & flags_before_switch) == flags_before_switch); if (key->generated) key_info->flags |= HA_GENERATED_KEY; + // Serialize vector index construction params (WITH clause). + if (!key->construction_params.empty()) { + String buf; + for (size_t i = 0; i < key->construction_params.size(); i++) { + if (i > 0) buf.append(','); + const auto &p = key->construction_params[i]; + buf.append(p.key.str, p.key.length); + buf.append('='); + buf.append(p.value.str, p.value.length); + } + key_info->vector_construction_params.str = + strmake_root(thd->mem_root, buf.ptr(), buf.length()); + key_info->vector_construction_params.length = buf.length(); + } + key_info->algorithm = key->key_create_info.algorithm; key_info->user_defined_key_parts = key->columns.size(); key_info->actual_key_parts = key_info->user_defined_key_parts; @@ -7743,6 +7784,9 @@ static bool prepare_key( } else if (key_info->flags & HA_FULLTEXT) { assert(!key->key_create_info.is_algorithm_explicit); key_info->algorithm = HA_KEY_ALG_FULLTEXT; + } else if (key_info->flags & HA_VECTOR) { + assert(!key->key_create_info.is_algorithm_explicit); + key_info->algorithm = HA_KEY_ALG_VECTOR; } else { if (key->key_create_info.is_algorithm_explicit) { if (key->key_create_info.algorithm != HA_KEY_ALG_RTREE) { @@ -8644,6 +8688,7 @@ bool mysql_prepare_create_table( uint key_number = 0; bool primary_key = false; + uint vector_key_number = 0; // First prepare non-foreign keys so that they are ready when // we prepare foreign keys. @@ -8660,6 +8705,14 @@ bool mysql_prepare_create_table( primary_key = true; } + if (key->type == KEYTYPE_VECTOR) { + if (vector_key_number) { + my_error(ER_ONLY_SINGLE_VECTOR_INDEX_ALLOWED, MYF(0)); + return true; + } + ++vector_key_number; + } + if (key->type != KEYTYPE_FOREIGN) { if (prepare_key(thd, error_schema_name, error_table_name, create_info, &alter_info->create_list, key, key_info_buffer, key_info, @@ -8724,6 +8777,32 @@ bool mysql_prepare_create_table( /* Sort keys in optimized order */ std::sort(*key_info_buffer, *key_info_buffer + *key_count, sort_keys()); + // We allow VECTOR indexes only on tables with BIGINT UNSIGNED PKs. + // After sorting, key_info_buffer[0] is the PK or a promoted UNIQUE NOT + // NULL key. If neither exists, reject. + if (vector_key_number) { + if (*key_count == 0 || !((*key_info_buffer)[0].flags & HA_NOSAME) || + ((*key_info_buffer)[0].flags & HA_NULL_PART_KEY)) { + my_error(ER_VECTOR_INDEX_NEEDS_PK, MYF(0)); + return true; + } + const KEY &primary_info = (*key_info_buffer)[0]; + + if (primary_info.actual_key_parts > 1) { + my_error(ER_UNKNOWN_ERROR, MYF(0)); + return true; + } + + for (it.rewind(), field_no = 0; (sql_field = it++); field_no++) { + if (field_no >= primary_info.key_part[0].fieldnr) break; + } + assert(sql_field); + if (sql_field->sql_type != MYSQL_TYPE_LONGLONG || !sql_field->is_unsigned) { + my_error(ER_VECTOR_INDEX_NEEDS_PK, MYF(0)); + return true; + } + } + /* Normal keys are done, now prepare foreign keys. @@ -16192,8 +16271,8 @@ bool prepare_fields_and_keys(THD *thd, const dd::Table *src_table, TABLE *table, */ if (!Field::type_can_have_key_part(cfield->field->type()) || !Field::type_can_have_key_part(cfield->sql_type) || - /* spatial keys can't have sub-key length */ - (key_info->flags & HA_SPATIAL) || + /* spatial and vector keys can't have sub-key length */ + (key_info->flags & (HA_SPATIAL | HA_VECTOR)) || (cfield->field->field_length == key_part_length && key_part->field->type() != MYSQL_TYPE_BLOB) || (cfield->max_display_width_in_codepoints() && @@ -16289,7 +16368,8 @@ bool prepare_fields_and_keys(THD *thd, const dd::Table *src_table, TABLE *table, key_create_info.parser_name = *plugin_name(key_info->parser); if (key_info->flags & HA_USES_COMMENT) key_create_info.comment = key_info->comment; - + if (key_info->vector_index_type.str != nullptr) + key_create_info.vector_index_type = key_info->vector_index_type; if (key_info->engine_attribute.str != nullptr) key_create_info.m_engine_attribute = key_info->engine_attribute; @@ -16319,6 +16399,8 @@ bool prepare_fields_and_keys(THD *thd, const dd::Table *src_table, TABLE *table, key_type = KEYTYPE_UNIQUE; } else if (key_info->flags & HA_FULLTEXT) key_type = KEYTYPE_FULLTEXT; + else if (key_info->flags & HA_VECTOR) + key_type = KEYTYPE_VECTOR; else key_type = KEYTYPE_MULTIPLE; @@ -20597,6 +20679,11 @@ static bool check_engine(THD *thd, const char *db_name, const char *table_name, } } + if (auto vea = (*new_engine)->validate_engine_attributes; + vea != nullptr && vea(thd, db_name, create_info, alter_info)) { + return true; + } + return false; } diff --git a/sql/sql_yacc.yy b/sql/sql_yacc.yy index 9b5bc6bc2868..b0cc4b885094 100644 --- a/sql/sql_yacc.yy +++ b/sql/sql_yacc.yy @@ -2147,6 +2147,12 @@ CHARSET_INFO *warn_on_deprecated_user_defined_collation( %type opt_index_options index_options opt_fulltext_index_options fulltext_index_options opt_spatial_index_options spatial_index_options + opt_vector_index_options vector_index_options + +%type index_construction_parameter + +%type opt_index_construction_clause + index_construction_clause index_construction_parameter_list %type opt_index_lock_and_algorithm @@ -2154,6 +2160,7 @@ CHARSET_INFO *warn_on_deprecated_user_defined_collation( spatial_index_option index_type_clause opt_index_type_clause + vector_index_option %type alter_algorithm_option_value alter_algorithm_option @@ -3615,7 +3622,54 @@ create_index_stmt: $11.algo.get_or_default(), $11.lock.get_or_default()); } + | CREATE VECTOR_SYM INDEX_SYM ident ON_SYM table_ident + '(' key_list_with_expression ')' opt_index_lock_and_algorithm opt_vector_index_options + { + $$= NEW_PTN PT_create_index_stmt(@$, YYMEM_ROOT, KEYTYPE_VECTOR, $4, + nullptr, $6, $8, $11, + $10.algo.get_or_default(), + $10.lock.get_or_default()); + } ; + +opt_index_construction_clause: + %empty { $$.init(YYMEM_ROOT); } + | index_construction_clause + ; + +index_construction_clause: + WITH '(' index_construction_parameter_list ')' + { + $$= $3; + } + ; + +index_construction_parameter_list: + index_construction_parameter + { + $$.init(YYMEM_ROOT); + if ($$.push_back($1)) + MYSQL_YYABORT; // OOM + } + | index_construction_parameter_list ',' index_construction_parameter + { + $$= $1; + if ($$.push_back($3)) + MYSQL_YYABORT; // OOM + } + ; + +index_construction_parameter: + IDENT_QUOTED EQ IDENT_QUOTED + { + $$= NEW_PTN PT_index_construction_parameter(@$, $1, $3); + } + | IDENT_QUOTED EQ NUM + { + $$= NEW_PTN PT_index_construction_parameter(@$, $1, $3); + } + ; + /* Only a limited subset of are allowed in CREATE COMPRESSION_DICTIONARY. @@ -7110,6 +7164,12 @@ table_constraint_def: { $$= NEW_PTN PT_inline_index_definition(@$, KEYTYPE_SPATIAL, $3, nullptr, $5, $7); } + | VECTOR_SYM opt_key_or_index opt_ident '(' key_list_with_expression ')' + opt_vector_index_options + { + $$= NEW_PTN PT_inline_index_definition(@$, KEYTYPE_VECTOR, $3, + nullptr, $5, $7); + } | opt_constraint_name constraint_key_type opt_index_name_and_type '(' key_list_with_expression ')' opt_index_options { @@ -8085,6 +8145,31 @@ spatial_index_option: common_index_option ; +opt_vector_index_options: + %empty { $$.init(YYMEM_ROOT); } + | vector_index_options + ; + +vector_index_options: + vector_index_option + { + $$.init(YYMEM_ROOT); + if ($$.push_back($1)) + MYSQL_YYABORT; // OOM + } + | vector_index_options vector_index_option + { + if ($1.push_back($2)) + MYSQL_YYABORT; // OOM + $$= $1; + } + ; + +vector_index_option: + common_index_option + | index_type_clause { $$= $1; } + ; + opt_index_options: %empty { $$.init(YYMEM_ROOT); } | index_options @@ -8162,6 +8247,10 @@ opt_index_type_clause: index_type_clause: USING index_type { $$= NEW_PTN PT_index_type(@$, $2); } | TYPE_SYM index_type { $$= NEW_PTN PT_index_type(@$, $2); } + | TYPE_SYM IDENT_sys opt_index_construction_clause + { + $$= NEW_PTN PT_vector_index_type(@$, to_lex_cstring($2), $3); + } ; visibility: @@ -8173,7 +8262,7 @@ index_type: BTREE_SYM { $$= HA_KEY_ALG_BTREE; } | RTREE_SYM { $$= HA_KEY_ALG_RTREE; } | HASH_SYM { $$= HA_KEY_ALG_HASH; } - ; + ; key_list: key_list ',' key_part @@ -11038,11 +11127,11 @@ opt_jdv_table_tags: jdv_table_tag: INSERT_SYM { $$ = jdv::DVT_INSERT; } | UPDATE_SYM { $$ = jdv::DVT_UPDATE; } - | DELETE_SYM { $$ = jdv::DVT_DELETE; } + | DELETE_SYM { $$ = jdv::DVT_DELETE; } | NO_SYM INSERT_SYM { $$ = jdv::DVT_NOINSERT; } | NO_SYM UPDATE_SYM { $$ = jdv::DVT_NOUPDATE; } | NO_SYM DELETE_SYM { $$ = jdv::DVT_NODELETE; } - ; + ; jdv_table_tags: jdv_table_tag { $$= $1; } @@ -11057,7 +11146,7 @@ jdv_table_tags: ($3 == jdv::DVT_NOUPDATE && $$ & jdv::DVT_UPDATE) || ($3 == jdv::DVT_DELETE && $$ & jdv::DVT_NODELETE) || ($3 == jdv::DVT_NODELETE && $$ & jdv::DVT_DELETE) - )) + )) { my_error(ER_JDV_INVALID_DEFINITION_WRONG_ANNOTATIONS, MYF(0)); MYSQL_YYABORT; @@ -16538,7 +16627,6 @@ ident_keywords_unambiguous: | XML_SYM | YEAR_SYM | ZONE_SYM - | VECTOR_SYM ; /* diff --git a/storage/innobase/CMakeLists.txt b/storage/innobase/CMakeLists.txt index ad85b83ac023..05e46e5f22d3 100644 --- a/storage/innobase/CMakeLists.txt +++ b/storage/innobase/CMakeLists.txt @@ -160,6 +160,7 @@ SET(INNOBASE_SOURCES handler/ha0check.cc handler/i_s.cc handler/p_s.cc + handler/vec0vec.cc handler/xtradb_i_s.cc ibuf/ibuf0ibuf.cc lob/lob0bulk.cc diff --git a/storage/innobase/btr/btr0btr.cc b/storage/innobase/btr/btr0btr.cc index 3c06ee5adfb1..7a9e0fdf1bd2 100644 --- a/storage/innobase/btr/btr0btr.cc +++ b/storage/innobase/btr/btr0btr.cc @@ -4655,7 +4655,8 @@ bool btr_validate_index( /* Full Text index are implemented by auxiliary tables, not the B-tree */ - if (dict_index_is_online_ddl(index) || (index->type & DICT_FTS)) { + if (dict_index_is_online_ddl(index) || + ((index->type & DICT_FTS) || dict_index_is_vector(index))) { return (true); } diff --git a/storage/innobase/dict/dict0crea.cc b/storage/innobase/dict/dict0crea.cc index 182bf0051a85..eebc751240ef 100644 --- a/storage/innobase/dict/dict0crea.cc +++ b/storage/innobase/dict/dict0crea.cc @@ -438,7 +438,7 @@ dberr_t dict_create_index_tree_in_mem(dict_index_t *index, trx_t *trx) { DBUG_EXECUTE_IF("ib_dict_create_index_tree_fail", return (DB_OUT_OF_MEMORY);); - if (index->type == DICT_FTS) { + if ((index->type & DICT_FTS) || dict_index_is_vector(index)) { /* FTS index does not need an index tree */ return (DB_SUCCESS); } diff --git a/storage/innobase/dict/dict0dd.cc b/storage/innobase/dict/dict0dd.cc index f77448568965..63798ec908d4 100644 --- a/storage/innobase/dict/dict0dd.cc +++ b/storage/innobase/dict/dict0dd.cc @@ -1979,7 +1979,7 @@ void dd_visit_keys_with_too_long_parts( std::function visitor) { for (uint key_num = 0; key_num < table->s->keys; key_num++) { const KEY &key = table->key_info[key_num]; - if (!(key.flags & (HA_SPATIAL | HA_FULLTEXT))) { + if (!(key.flags & (HA_SPATIAL | HA_FULLTEXT | HA_VECTOR))) { for (unsigned i = 0; i < key.user_defined_key_parts; i++) { const KEY_PART_INFO *key_part = &key.key_part[i]; if (max_part_len < key_part->length) { @@ -2909,7 +2909,7 @@ MY_COMPILER_DIAGNOSTIC_POP() */ static inline uint16_t get_index_prefix_len(const KEY &key, const KEY_PART_INFO *key_part) { - if (key.flags & (HA_SPATIAL | HA_FULLTEXT)) { + if (key.flags & (HA_SPATIAL | HA_FULLTEXT | HA_VECTOR)) { return 0; } @@ -2949,6 +2949,7 @@ template const dict_index_t *dd_find_index( uint key_num) { const KEY &key = form->key_info[key_num]; ulint type = 0; + bool is_vector = false; unsigned n_fields = key.user_defined_key_parts; unsigned n_uniq = n_fields; @@ -2969,6 +2970,10 @@ template const dict_index_t *dd_find_index( ut_ad(!table->is_intrinsic()); type = DICT_FTS; n_uniq = 0; + } else if (key.flags & HA_VECTOR) { + ut_ad(!table->is_intrinsic()); + is_vector = true; + n_uniq = 0; } else if (key_num == form->primary_key) { ut_ad(key.flags & HA_NOSAME); ut_ad(n_uniq > 0); @@ -2977,11 +2982,13 @@ template const dict_index_t *dd_find_index( type = (key.flags & HA_NOSAME) ? DICT_UNIQUE : 0; } - ut_ad(!!(type & DICT_FTS) == (n_uniq == 0)); + ut_ad((!!(type & DICT_FTS) || is_vector) == (n_uniq == 0)); dict_index_t *index = dict_mem_index_create(table->name.m_name, key.name, 0, type, n_fields); + index->is_vector_index = is_vector; + index->n_uniq = n_uniq; DBUG_EXECUTE_IF("ib_create_table_fail_at_create_index", @@ -5206,8 +5213,8 @@ dict_table_t *dd_open_table_one(dd::cache::Dictionary_client *client, } ut_ad(root > 1); - ut_ad(index->type & DICT_FTS || root != FIL_NULL || - dict_table_is_discarded(m_table)); + ut_ad((index->type & DICT_FTS) || dict_index_is_vector(index) || + root != FIL_NULL || dict_table_is_discarded(m_table)); ut_ad(id != 0); index->page = root; index->space = sid; diff --git a/storage/innobase/dict/dict0dict.cc b/storage/innobase/dict/dict0dict.cc index 6710d5abff14..56344f30c2ee 100644 --- a/storage/innobase/dict/dict0dict.cc +++ b/storage/innobase/dict/dict0dict.cc @@ -218,6 +218,9 @@ static dict_index_t *dict_index_build_internal_fts( dict_table_t *table, /*!< in: table */ dict_index_t *index); /*!< in: user representation of an FTS index */ +static dict_index_t *dict_index_build_internal_vec(dict_table_t *table, + dict_index_t *index); + /** Removes an index from the dictionary cache. */ static void dict_index_remove_from_cache_low( dict_table_t *table, /*!< in/out: table */ @@ -2217,7 +2220,7 @@ static bool dict_index_too_big_for_tree(const dict_table_t *table, const dict_index_t *new_index) { /* FTS index consists of auxiliary tables, they shall be excluded from index row size check */ - if (new_index->type & DICT_FTS) { + if ((new_index->type & DICT_FTS) || dict_index_is_vector(new_index)) { return (false); } @@ -2446,6 +2449,8 @@ dberr_t dict_index_add_to_cache_w_vcol(dict_table_t *table, dict_index_t *index, if (index->type == DICT_FTS) { new_index = dict_index_build_internal_fts(table, index); + } else if (dict_index_is_vector(index)) { + new_index = dict_index_build_internal_vec(table, index); } else if (index->is_clustered()) { new_index = dict_index_build_internal_clust(table, index); } else { @@ -3284,6 +3289,37 @@ static dict_index_t *dict_index_build_internal_fts( return (new_index); } + +static dict_index_t *dict_index_build_internal_vec( + dict_table_t *table, /*!< in: table */ + dict_index_t *index) /*!< in: user representation of a vector index */ +{ + ut_ad(table && index); + ut_ad(dict_index_is_vector(index)); + ut_ad(!dict_sys_mutex_own()); + ut_ad(table->magic_n == DICT_TABLE_MAGIC_N); + + /* Create a new index */ + auto new_index = + dict_mem_index_create(table->name.m_name, index->name, index->space, + index->type, index->n_fields); + new_index->is_vector_index = index->is_vector_index; + + /* Copy other relevant data from the old index struct to the new + struct: it inherits the values */ + + new_index->n_user_defined_cols = index->n_fields; + + new_index->id = index->id; + + /* Copy fields from index to new_index */ + dict_index_copy(new_index, index, table, 0, index->n_fields); + + new_index->n_uniq = 0; + new_index->cached = true; + + return (new_index); +} /*====================== FOREIGN KEY PROCESSING ========================*/ /** Checks if a table is referenced by foreign keys. @@ -3371,7 +3407,8 @@ NOT NULL */ while (index != nullptr) { if (types_idx != index && !(index->type & DICT_FTS) && - !dict_index_is_spatial(index) && !index->to_be_dropped && + !dict_index_is_vector(index) && !dict_index_is_spatial(index) && + !index->to_be_dropped && (!(index->uncommitted && ((index->online_status == ONLINE_INDEX_ABORTED_DROPPED) || (index->online_status == ONLINE_INDEX_ABORTED)))) && @@ -3628,6 +3665,7 @@ bool dict_index_check_search_tuple( ut_ad(index->page >= FSP_FIRST_INODE_PAGE_NO); ut_ad(dtuple_check_typed(tuple)); ut_ad(!(index->type & DICT_FTS)); + ut_ad(!dict_index_is_vector(index)); return true; } #endif /* UNIV_DEBUG */ diff --git a/storage/innobase/handler/ha_innodb.cc b/storage/innobase/handler/ha_innodb.cc index 69a2c99df38e..b1242ee5c7b0 100644 --- a/storage/innobase/handler/ha_innodb.cc +++ b/storage/innobase/handler/ha_innodb.cc @@ -1,3 +1,5 @@ +// clang-format off + /***************************************************************************** Copyright (c) 2000, 2026, Oracle and/or its affiliates. @@ -44,6 +46,7 @@ this program; if not, write to the Free Software Foundation, Inc., /** @file ha_innodb.cc */ #include +#include #ifndef UNIV_HOTBACKUP #include "my_config.h" #endif /* !UNIV_HOTBACKUP */ @@ -210,6 +213,8 @@ this program; if not, write to the Free Software Foundation, Inc., #include "sql-common/json_binary.h" #include "sql-common/json_dom.h" +#include "vec0vec.h" + #include "os0enc.h" #include "os0file.h" @@ -3231,7 +3236,7 @@ ha_innobase::ha_innobase(handlerton *hton, TABLE_SHARE *table_arg) HA_ATTACHABLE_TRX_COMPATIBLE | HA_CAN_INDEX_VIRTUAL_GENERATED_COLUMN | HA_DESCENDING_INDEX | HA_MULTI_VALUED_KEY_SUPPORT | HA_BLOB_PARTIAL_UPDATE | HA_SUPPORTS_GEOGRAPHIC_GEOMETRY_COLUMN | - HA_SUPPORTS_DEFAULT_EXPRESSION | HA_ONLINE_ANALYZE), + HA_SUPPORTS_DEFAULT_EXPRESSION | HA_ONLINE_ANALYZE | HA_CAN_VECTOR), m_start_of_scan(), m_stored_select_lock_type(LOCK_NONE_UNSET), m_mysql_has_locked() {} @@ -4804,6 +4809,27 @@ static bool innobase_redo_set_state(THD *thd, bool enable) { return (false); } +// clang-format on + +static bool innobase_validate_engine_attributes(THD *thd, const char *db_name, + HA_CREATE_INFO *create_info, + const Alter_info *alter_info) { + for (const auto *key : alter_info->key_list) { + switch (key->type) { + case KEYTYPE_VECTOR: + return storage::innobase::vec::validate_options(*key); + break; + default: + break; + } + } + + return false; +} + +// clang-format off + + /** Return partitioning flags. */ static uint innobase_partition_flags() { return (HA_CAN_EXCHANGE_PARTITION | HA_CANNOT_PARTITION_FK | @@ -5683,8 +5709,6 @@ static PSI_metric_info_v1 data_metrics[] = { export_vars.innodb_data_written) }; -// clang-format on - static PSI_meter_info_v1 inno_meter[] = { {"mysql.inno", "MySql InnoDB metrics", 10, 0, 0, inno_metrics, std::size(inno_metrics)}, @@ -5750,13 +5774,13 @@ static int innodb_init(void *p) { innobase_hton->lock_hton_log = innobase_lock_hton_log; innobase_hton->unlock_hton_log = innobase_unlock_hton_log; innobase_hton->collect_hton_log_info = innobase_collect_hton_log_info; - innobase_hton->flags = HTON_SUPPORTS_EXTENDED_KEYS | - HTON_SUPPORTS_FOREIGN_KEYS | HTON_SUPPORTS_ATOMIC_DDL | - HTON_CAN_RECREATE | HTON_SUPPORTS_SECONDARY_ENGINE | - HTON_SUPPORTS_TABLE_ENCRYPTION | - HTON_SUPPORTS_GENERATED_INVISIBLE_PK | - HTON_SUPPORTS_BULK_LOAD | HTON_SUPPORTS_SQL_FK | - HTON_SUPPORTS_ONLINE_BACKUPS | HTON_SUPPORTS_COMPRESSED_COLUMNS; + innobase_hton->flags = + HTON_SUPPORTS_EXTENDED_KEYS | HTON_SUPPORTS_FOREIGN_KEYS | + HTON_SUPPORTS_ATOMIC_DDL | HTON_CAN_RECREATE | + HTON_SUPPORTS_SECONDARY_ENGINE | HTON_SUPPORTS_TABLE_ENCRYPTION | + HTON_SUPPORTS_GENERATED_INVISIBLE_PK | HTON_SUPPORTS_BULK_LOAD | + HTON_SUPPORTS_SQL_FK | HTON_SUPPORTS_ONLINE_BACKUPS | + HTON_SUPPORTS_COMPRESSED_COLUMNS; // TODO(WL9440): to be enabled when distance scan is implemented in innodb. //| HTON_SUPPORTS_DISTANCE_SCAN; @@ -5808,6 +5832,7 @@ static int innodb_init(void *p) { innobase_fix_default_table_encryption; innobase_hton->redo_log_set_state = innobase_redo_set_state; + innobase_hton->validate_engine_attributes = innobase_validate_engine_attributes; innobase_hton->post_ddl = innobase_post_ddl; @@ -8303,7 +8328,7 @@ int ha_innobase::open(const char *name, int, uint open_flags, dict_table_autoinc_unlock(ib_table); } - /* Set plugin parser for fulltext index */ + /* Set plugin parser for fulltext index / handle vector index. */ for (uint i = 0; i < table->s->keys; i++) { if (table->key_info[i].flags & HA_USES_PARSER) { dict_index_t *index = innobase_get_index(i); @@ -11019,7 +11044,7 @@ int ha_innobase::index_read( : HA_ERR_TABLE_DEF_CHANGED; } - if (index->type & DICT_FTS) { + if ((index->type & DICT_FTS) || dict_index_is_vector(index)) { return HA_ERR_KEY_NOT_FOUND; } @@ -12871,6 +12896,7 @@ inline int create_index( index = dict_mem_index_create(table_name, key->name, 0, ind_type, key->user_defined_key_parts); + index->is_vector_index = (key->flags & HA_VECTOR); innodb_session_t *&priv = thd_to_innodb_session(trx->mysql_thd); dict_table_t *handler = priv->lookup_table_handler(table_name); @@ -12964,6 +12990,7 @@ inline int create_index( } ut_ad(key->flags & HA_FULLTEXT || !(index->type & DICT_FTS)); + ut_ad((key->flags & HA_VECTOR) || !dict_index_is_vector(index)); multi_val_idx = ((index->type & DICT_MULTI_VALUE) == DICT_MULTI_VALUE); @@ -14059,7 +14086,7 @@ bool create_table_info_t::innobase_table_flags() { if (fts_doc_id_index_bad) { goto index_bad; } - } else if (key->flags & HA_SPATIAL) { + } else if (key->flags & (HA_SPATIAL | HA_VECTOR)) { assert(~m_create_info->options & (HA_LEX_CREATE_TMP_TABLE | HA_LEX_CREATE_INTERNAL_TMP_TABLE)); } @@ -15656,16 +15683,51 @@ int innobase_truncate::exec() { template int innobase_truncate::exec(); template int innobase_truncate::exec(); -/** Check if a column is the only column in an index. -@param[in] index data dictionary index -@param[in] column the column to look for -@return whether the column is the only column in the index */ +/** + Check if a column is the only column in an index. + + @param[in] index data dictionary index + @param[in] column the column to look for + + @return Whether the column is the only column in the index. +*/ static bool dd_is_only_column(const dd::Index *index, const dd::Column *column) { return (index->elements().size() == 1 && &(*index->elements().begin())->column() == column); } +/** + Check if an index uses marker-based vector metadata. + + @param[in] index data dictionary index + + @return Whether the index is a vector index. +*/ +static bool dd_is_vector_index(const dd::Index *index) { + if (index->algorithm() != dd::Index::IA_SE_SPECIFIC || + index->type() != dd::Index::IT_MULTIPLE) { + return false; + } + + uint visible_elements = 0; + for (const dd::Index_element *elem : index->elements()) { + if (elem->is_hidden()) continue; + + visible_elements++; + + const dd::Properties &col_options = elem->column().options(); + bool is_vector_column = false; + if (col_options.exists("vector_index") && + !col_options.get("vector_index", &is_vector_column) && + is_vector_column) { + return visible_elements == 1; + } + } + + return false; +} + /** Add hidden columns and indexes to an InnoDB table definition. @param[in,out] dd_table data dictionary cache object @return error number @@ -15693,6 +15755,10 @@ int ha_innobase::get_extra_columns_and_keys(const HA_CREATE_INFO *, fts_doc_id_index = i; } + if (dd_is_vector_index(i)) { + continue; + } + switch (i->algorithm()) { case dd::Index::IA_SE_SPECIFIC: ut_d(ut_error); @@ -18334,7 +18400,8 @@ void ha_innobase::info_low_key(uint flag, const dict_table_t *ib_table) { /* We do not maintain stats for fulltext or spatial indexes. Thus, we can't calculate pct_cached below because we need dict_index_t::stat_n_leaf_pages for that. See dict_stats_should_ignore_index(). */ - if ((key->flags & HA_FULLTEXT) || (key->flags & HA_SPATIAL)) { + if ((key->flags & HA_FULLTEXT) || (key->flags & HA_SPATIAL) || + (key->flags & HA_VECTOR)) { pct_cached = IN_MEMORY_ESTIMATE_UNKNOWN; } else { pct_cached = index_pct_cached(index); @@ -18352,7 +18419,8 @@ void ha_innobase::info_low_key(uint flag, const dict_table_t *ib_table) { } for (ulong j = 0; j < key->actual_key_parts; j++) { - if ((key->flags & HA_FULLTEXT) || (key->flags & HA_SPATIAL)) { + if ((key->flags & HA_FULLTEXT) || (key->flags & HA_SPATIAL) || + (key->flags & HA_VECTOR)) { /* The record per key does not apply to FTS or Spatial indexes. */ key->set_records_per_key(j, 1.0f); continue; @@ -18678,7 +18746,8 @@ static bool innobase_get_index_column_cardinality( } DEBUG_SYNC(thd, "innodb.after_init_check"); - if (index->type & (DICT_FTS | DICT_SPATIAL)) { + if ((index->type & (DICT_FTS | DICT_SPATIAL)) || + dict_index_is_vector(index)) { /* For these indexes innodb_rec_per_key is fixed as 1.0 */ *cardinality = ib_table->stat_n_rows; diff --git a/storage/innobase/handler/vec0vec.cc b/storage/innobase/handler/vec0vec.cc new file mode 100644 index 000000000000..d3b7d902478f --- /dev/null +++ b/storage/innobase/handler/vec0vec.cc @@ -0,0 +1,138 @@ +/***************************************************************************** + +Copyright (c) 2025, Percona Inc. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, +for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +#include "vec0vec.h" + +#include +#include +#include +#include +#include +#include + +// ut0ut.h isn't self-contained. +#include "handler.h" +#include "lex_string.h" +#include "my_base.h" +#include "mysql/strings/m_ctype.h" +#include "mysqld_cs.h" +#include "ut0mem.h" +#include "ut0test.h" +#include "ut0ut.h" + +#include +#include +#include +#include + +#include "key_spec.h" +#include "my_sys.h" +#include "mysqld_error.h" + +using namespace std; + +namespace { +const char *alg_to_string(ha_key_alg alg) { + switch (alg) { + case HA_KEY_ALG_SE_SPECIFIC: + assert(false); + case HA_KEY_ALG_BTREE: + return "BTREE"; + case HA_KEY_ALG_RTREE: + return "RTREE"; + case HA_KEY_ALG_HASH: + return "HASH"; + case HA_KEY_ALG_FULLTEXT: + return "FULLTEXT"; + case HA_KEY_ALG_VECTOR: + return "VECTOR"; + } +} +} // namespace + +namespace storage::innobase::vec { + +bool validate_options(const Key_spec &index_def) { + VectorIndexParam vip; + return parse_options(index_def, vip); +} + +bool parse_options(const Key_spec &index_def, VectorIndexParam &vip) { + assert(index_def.type == KEYTYPE_VECTOR); + + // prepare_key() will make sure there's only one column. + // Possibly this check belongs there, too. + if (index_def.columns[0]->get_prefix_length() != 0) { + my_error(ER_WRONG_SUB_KEY, MYF(0)); + return true; + } + + if (index_def.key_create_info.algorithm != HA_KEY_ALG_SE_SPECIFIC) { + my_error(ER_INDEX_TYPE_NOT_SUPPORTED, MYF(0), + alg_to_string(index_def.key_create_info.algorithm), "vector"); + return true; + } + + if (index_def.key_create_info.vector_index_type.str == nullptr) { + my_error(ER_NO_INDEX_TYPE, MYF(0), ""); + return true; + } + if (my_strcasecmp(system_charset_info, + index_def.key_create_info.vector_index_type.str, + "HNSW") == 0) { + vip = HnswParam(); + auto &hnsw_param = std::get(vip); + for (const IndexConstructionParam &p : index_def.construction_params) { + if (my_strcasecmp(system_charset_info, p.key.str, "M") == 0) { + if (!std::all_of(p.value.str, p.value.str + p.value.length, + [](auto c) { return std::isdigit(c); })) { + my_error(ER_ILLEGAL_INDEX_CONSTRUCTION_PARAMETER_VALUE, MYF(0), + p.value.str); + return true; + } + hnsw_param.M = std::atoi(p.value.str); + } else if (my_strcasecmp(system_charset_info, p.key.str, "metric") == 0) { + if (my_strcasecmp(system_charset_info, p.value.str, "euclidean") == 0) { + hnsw_param.metric = "euclidean"; + } else { + my_error(ER_ILLEGAL_INDEX_CONSTRUCTION_PARAMETER_VALUE, MYF(0), + p.value.str); + return true; + } + } else { + my_error(ER_ILLEGAL_INDEX_CONSTRUCTION_PARAMETER, MYF(0), p.key.str); + return true; + } + } + return false; + } + my_error(ER_INDEX_TYPE_NOT_SUPPORTED, MYF(0), + index_def.key_create_info.vector_index_type.str, "vector"); + return true; +} + +} // namespace storage::innobase::vec diff --git a/storage/innobase/include/dict0dict.ic b/storage/innobase/include/dict0dict.ic index abb3b0a8d35d..b7e415c43c31 100644 --- a/storage/innobase/include/dict0dict.ic +++ b/storage/innobase/include/dict0dict.ic @@ -134,6 +134,22 @@ static inline ulint dict_index_is_spatial( return (index->type & DICT_SPATIAL); } +/** + Check whether the index is a vector index. + + @param[in] index Index. + + @return Nonzero for vector index, zero for other indexes +*/ +static inline ulint dict_index_is_vector( + const dict_index_t *index) /*!< in: index */ +{ + ut_ad(index); + ut_ad(index->magic_n == DICT_INDEX_MAGIC_N); + + return (index->is_vector()); +} + /** Check whether the index contains a virtual column @param[in] index index @return nonzero for the index has virtual column, zero for other indexes */ diff --git a/storage/innobase/include/dict0mem.h b/storage/innobase/include/dict0mem.h index 9a083c0c1c74..feb43d900600 100644 --- a/storage/innobase/include/dict0mem.h +++ b/storage/innobase/include/dict0mem.h @@ -1206,6 +1206,9 @@ struct dict_index_t { /** if the index is an hidden index */ bool hidden; + + /** true if this is a vector index according to DD metadata */ + bool is_vector_index; #endif /* !UNIV_HOTBACKUP */ /** list of indexes of the table */ @@ -1337,6 +1340,17 @@ struct dict_index_t { return (type & DICT_MULTI_VALUE); } + /** + Check whether the index is a vector index. + + @return true if the index is a vector index, false otherwise + */ + [[nodiscard]] bool is_vector() const { + ut_ad(magic_n == DICT_INDEX_MAGIC_N); + + return (is_vector_index); + } + /** Returns the minimum data size of an index record. @return minimum data size in bytes */ ulint get_min_size() const { diff --git a/storage/innobase/include/dict0mem.ic b/storage/innobase/include/dict0mem.ic index 65993ac857a0..7b3bb7dbf788 100644 --- a/storage/innobase/include/dict0mem.ic +++ b/storage/innobase/include/dict0mem.ic @@ -76,6 +76,7 @@ static inline void dict_mem_fill_index_struct( index->allow_duplicates = false; index->nulls_equal = false; index->disable_ahi = false; + index->is_vector_index = false; index->last_ins_cur = nullptr; index->last_sel_cur = nullptr; #ifndef UNIV_HOTBACKUP diff --git a/storage/innobase/include/vec0vec.h b/storage/innobase/include/vec0vec.h new file mode 100644 index 000000000000..72fd251f5d43 --- /dev/null +++ b/storage/innobase/include/vec0vec.h @@ -0,0 +1,49 @@ +/***************************************************************************** + +Copyright (c) 2025, Percona Inc. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, +for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +#pragma once + +#include +#include +#include "key_spec.h" + +namespace storage::innobase::vec { + +bool validate_options(const Key_spec &index_def); + +struct HnswParam { + int M{25}; + int max_elements{10000}; + int ef_construction{200}; + std::string_view metric{"euclidean"}; +}; + +using VectorIndexParam = std::variant; + +bool parse_options(const Key_spec &index_def, VectorIndexParam &vip); + +} // namespace storage::innobase::vec diff --git a/storage/temptable/src/handler.cc b/storage/temptable/src/handler.cc index 7c3e8e0058bd..929e811a6972 100644 --- a/storage/temptable/src/handler.cc +++ b/storage/temptable/src/handler.cc @@ -877,6 +877,7 @@ ulong Handler::index_flags(uint index_no, uint, bool) const { case HA_KEY_ALG_SE_SPECIFIC: case HA_KEY_ALG_RTREE: case HA_KEY_ALG_FULLTEXT: + case HA_KEY_ALG_VECTOR: flags = 0; break; } diff --git a/storage/temptable/src/table.cc b/storage/temptable/src/table.cc index 3c87e1e7728c..ef3504aee96f 100644 --- a/storage/temptable/src/table.cc +++ b/storage/temptable/src/table.cc @@ -290,6 +290,7 @@ void Table::indexes_create() { case HA_KEY_ALG_SE_SPECIFIC: case HA_KEY_ALG_RTREE: case HA_KEY_ALG_FULLTEXT: + case HA_KEY_ALG_VECTOR: DBUG_ABORT(); } } diff --git a/unittest/gunit/innodb/CMakeLists.txt b/unittest/gunit/innodb/CMakeLists.txt index 7e63da1f3c4c..bc8104471f57 100644 --- a/unittest/gunit/innodb/CMakeLists.txt +++ b/unittest/gunit/innodb/CMakeLists.txt @@ -63,6 +63,7 @@ SET(TESTS ut0object_cache ut0rbt ut0rnd + vec0vec ) # Treat warnings as errors on MSVC for InnoDB's unittests diff --git a/unittest/gunit/innodb/vec0vec-t.cc b/unittest/gunit/innodb/vec0vec-t.cc new file mode 100644 index 000000000000..8cb8a62e894c --- /dev/null +++ b/unittest/gunit/innodb/vec0vec-t.cc @@ -0,0 +1,114 @@ +/***************************************************************************** + +Copyright (c) 2025, Percona Inc. + +This program is free software; you can redistribute it and/or modify it under +the terms of the GNU General Public License, version 2.0, as published by the +Free Software Foundation. + +This program is designed to work with certain software (including +but not limited to OpenSSL) that is licensed under separate terms, +as designated in a particular file or component or in included license +documentation. The authors of MySQL hereby grant you an additional +permission to link the program and your derivative works with the +separately licensed software that they have either included with +the program or referenced in the documentation. + +This program is distributed in the hope that it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, +for more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + +*****************************************************************************/ + +#include + +#include "my_base.h" +#include "mysqld_error.h" +#include "sql/sql_alter.h" +#include "sql/sql_lex.h" +#include "storage/innobase/include/vec0vec.h" +#include "unittest/gunit/parsertest.h" +#include "unittest/gunit/test_utils.h" + +namespace innodb_vec0vec_unittest { + +using namespace storage::innobase::vec; +using my_testing::Server_initializer; + +class Vec0VecTest : public ParserTest { + protected: + bool parse(const char *query, int expected_error = 0) { + ParserTest::parse(query); + + const Key_spec *ks = find_vector_key(); + if (ks == nullptr) { + ADD_FAILURE() << "No VECTOR key found in key_list"; + return true; + } + + Server_initializer::set_expected_error(expected_error); + bool err = parse_options(*ks, m_vip); + Server_initializer::set_expected_error(0); + return err; + } + + const Key_spec *find_vector_key() { + const Alter_info *alter_info = thd()->lex->alter_info; + if (alter_info == nullptr) return nullptr; + for (const Key_spec *ks : alter_info->key_list) { + if (ks->type == KEYTYPE_VECTOR) return ks; + } + return nullptr; + } + + VectorIndexParam m_vip; +}; + +TEST_F(Vec0VecTest, HnswWithM) { + EXPECT_FALSE( + parse("CREATE TABLE t1 (" + " id BIGINT UNSIGNED PRIMARY KEY," + " v1 VECTOR(128)," + " VECTOR KEY(v1) TYPE hnsw WITH (M = 16)" + ")")); + ASSERT_TRUE(std::holds_alternative(m_vip)); + EXPECT_EQ(16, std::get(m_vip).M); +} + +TEST_F(Vec0VecTest, HnswMetricEuclidean) { + EXPECT_FALSE( + parse("CREATE TABLE t1 (" + " id BIGINT UNSIGNED PRIMARY KEY," + " v1 VECTOR(128)," + " VECTOR KEY(v1) TYPE hnsw WITH (metric = euclidean)" + ")")); + ASSERT_TRUE(std::holds_alternative(m_vip)); + EXPECT_EQ("euclidean", std::get(m_vip).metric); +} + +TEST_F(Vec0VecTest, NonSeSpecificAlgorithm) { + ParserTest::parse( + "CREATE TABLE t1 (" + " id BIGINT UNSIGNED PRIMARY KEY," + " v1 VECTOR(128)," + " KEY(id) USING BTREE" + ")"); + const Alter_info *alter_info = thd()->lex->alter_info; + // Find the BTREE key (not PRIMARY, not VECTOR) + const Key_spec *ks = nullptr; + for (const Key_spec *k : alter_info->key_list) { + if (k->type == KEYTYPE_MULTIPLE) { + ks = k; + break; + } + } + ASSERT_NE(nullptr, ks); + EXPECT_FALSE(parse_options(*ks, m_vip)); +} + +} // namespace innodb_vec0vec_unittest