@@ -618,8 +618,14 @@ def create_indexes(db, indexes, verbose=True):
618618 # Convert uniqueness string to SQL index creation
619619 if uniqueness == "UNIQUE" :
620620 db .command ("sql" , f"CREATE INDEX ON { table } ({ column } ) UNIQUE" )
621+ elif uniqueness == "UNIQUE_HASH" :
622+ db .command ("sql" , f"CREATE INDEX ON { table } ({ column } ) UNIQUE_HASH" )
621623 elif uniqueness == "FULL_TEXT" :
622624 db .command ("sql" , f"CREATE INDEX ON { table } ({ column } ) FULL_TEXT" )
625+ elif uniqueness == "NOTUNIQUE_HASH" :
626+ db .command (
627+ "sql" , f"CREATE INDEX ON { table } ({ column } ) NOTUNIQUE_HASH"
628+ )
623629 else : # NOTUNIQUE
624630 db .command ("sql" , f"CREATE INDEX ON { table } ({ column } ) NOTUNIQUE" )
625631
@@ -1469,15 +1475,16 @@ def _flush_batch(batch_rows):
14691475
14701476# Check all existing indexes
14711477#
1472- # Note: ArcadeDB has 3 index engine types: LSM_TREE, FULL_TEXT, VECTOR
1478+ # Note: ArcadeDB exposes multiple index engines, including LSM_TREE, HASH,
1479+ # FULL_TEXT, and VECTOR.
14731480# The schema metadata query only exposes a boolean 'unique' field, not the engine type.
14741481# Therefore:
1475- # - UNIQUE indexes → unique=true, engine=LSM_TREE
1476- # - NOTUNIQUE indexes → unique=false, engine=LSM_TREE
1482+ # - UNIQUE / UNIQUE_HASH indexes → unique=true
1483+ # - NOTUNIQUE / NOTUNIQUE_HASH indexes → unique=false
14771484# - FULL_TEXT indexes → unique=false, engine=FULL_TEXT (appears as NOTUNIQUE!)
14781485#
1479- # This means FULL_TEXT indexes show as NOTUNIQUE in the metadata, so we need to
1480- # check for both when validating expected FULL_TEXT indexes .
1486+ # This means HASH and FULL_TEXT indexes must be validated by semantics rather than
1487+ # engine type alone .
14811488for idx in existing_indexes :
14821489 idx_dict = json .loads (idx .to_json ())
14831490 index_type = "UNIQUE" if idx_dict .get ("unique" ) else "NOTUNIQUE"
@@ -1505,7 +1512,7 @@ def _flush_batch(batch_rows):
15051512 candidate_columns .append (prop )
15061513
15071514 if isinstance (name , str ) and "[" in name and name .endswith ("]" ):
1508- raw_props = name [name .find ("[" ) + 1 : - 1 ]
1515+ raw_props = name [name .find ("[" ) + 1 : - 1 ]. strip ()
15091516 for col in raw_props .split ("," ):
15101517 col = col .strip ()
15111518 if col :
@@ -1514,15 +1521,23 @@ def _flush_batch(batch_rows):
15141521 candidate_columns = [c for c in candidate_columns if c ]
15151522
15161523 for column_name in candidate_columns :
1517- # Try matching as the reported type (UNIQUE/NOTUNIQUE)
1524+ # Try matching as the reported uniqueness semantics.
15181525 key = (type_name , column_name , index_type )
15191526 if key in expected_indexes :
15201527 expected_indexes [key ] = True
15211528
1529+ if index_type == "UNIQUE" :
1530+ unique_hash_key = (type_name , column_name , "UNIQUE_HASH" )
1531+ if unique_hash_key in expected_indexes :
1532+ expected_indexes [unique_hash_key ] = True
1533+
15221534 # FULL_TEXT indexes appear as NOTUNIQUE in metadata, so also check for FULL_TEXT
15231535 # This is expected behavior since FULL_TEXT is a different index engine type,
15241536 # not a variant of LSM_TREE, but metadata only exposes the 'unique' boolean.
15251537 if index_type == "NOTUNIQUE" :
1538+ notunique_hash_key = (type_name , column_name , "NOTUNIQUE_HASH" )
1539+ if notunique_hash_key in expected_indexes :
1540+ expected_indexes [notunique_hash_key ] = True
15261541 fulltext_key = (type_name , column_name , "FULL_TEXT" )
15271542 if fulltext_key in expected_indexes :
15281543 expected_indexes [fulltext_key ] = True
@@ -1534,6 +1549,9 @@ def _flush_batch(batch_rows):
15341549 unique_key = (type_name , column_name , "UNIQUE" )
15351550 if unique_key in expected_indexes :
15361551 expected_indexes [unique_key ] = True
1552+ unique_hash_key = (type_name , column_name , "UNIQUE_HASH" )
1553+ if unique_hash_key in expected_indexes :
1554+ expected_indexes [unique_hash_key ] = True
15371555
15381556# Validate all expected indexes were created
15391557print ("\n ✅ Validating expected indexes:" )
0 commit comments