@@ -1790,19 +1790,31 @@ public function createCollection(string $id, array $attributes = [], array $inde
17901790 }
17911791 }
17921792
1793+ $ createdPhysicalTable = false ;
1794+
17931795 try {
17941796 $ this ->adapter ->createCollection ($ id , $ attributes , $ indexes );
1797+ $ createdPhysicalTable = true ;
17951798 } catch (DuplicateException $ e ) {
1796- // Metadata check (above) already verified collection is absent
1797- // from metadata. A DuplicateException from the adapter means the
1798- // collection exists only in physical schema — an orphan from a prior
1799- // partial failure. Drop and recreate to ensure schema matches.
1800- try {
1801- $ this ->adapter ->deleteCollection ($ id );
1802- } catch (NotFoundException ) {
1803- // Already removed by a concurrent reconciler.
1799+ if ($ this ->adapter ->getSharedTables ()
1800+ && ($ id === self ::METADATA || $ this ->adapter ->exists ($ this ->adapter ->getDatabase (), $ id ))) {
1801+ // In shared-tables mode the physical table is reused across
1802+ // tenants. A DuplicateException simply means the table already
1803+ // exists for another tenant — not an orphan.
1804+ } else {
1805+ // Metadata check (above) already verified collection is absent
1806+ // from metadata. A DuplicateException from the adapter means
1807+ // the collection exists only in physical schema — an orphan
1808+ // from a prior partial failure. Drop and recreate to ensure
1809+ // schema matches.
1810+ try {
1811+ $ this ->adapter ->deleteCollection ($ id );
1812+ } catch (NotFoundException ) {
1813+ // Already removed by a concurrent reconciler.
1814+ }
1815+ $ this ->adapter ->createCollection ($ id , $ attributes , $ indexes );
1816+ $ createdPhysicalTable = true ;
18041817 }
1805- $ this ->adapter ->createCollection ($ id , $ attributes , $ indexes );
18061818 }
18071819
18081820 if ($ id === self ::METADATA ) {
@@ -1812,10 +1824,12 @@ public function createCollection(string $id, array $attributes = [], array $inde
18121824 try {
18131825 $ createdCollection = $ this ->silent (fn () => $ this ->createDocument (self ::METADATA , $ collection ));
18141826 } catch (\Throwable $ e ) {
1815- try {
1816- $ this ->cleanupCollection ($ id );
1817- } catch (\Throwable $ e ) {
1818- Console::error ("Failed to rollback collection ' {$ id }': " . $ e ->getMessage ());
1827+ if ($ createdPhysicalTable ) {
1828+ try {
1829+ $ this ->cleanupCollection ($ id );
1830+ } catch (\Throwable $ e ) {
1831+ Console::error ("Failed to rollback collection ' {$ id }': " . $ e ->getMessage ());
1832+ }
18191833 }
18201834 throw new DatabaseException ("Failed to create collection metadata for ' {$ id }': " . $ e ->getMessage (), previous: $ e );
18211835 }
@@ -4560,18 +4574,49 @@ public function createIndex(string $collection, string $id, string $type, array
45604574 }
45614575
45624576 $ created = false ;
4577+ $ existsInSchema = false ;
45634578
4564- try {
4565- $ created = $ this ->adapter ->createIndex ($ collection ->getId (), $ id , $ type , $ attributes , $ lengths , $ orders , $ indexAttributesWithTypes , [], $ ttl );
4579+ if ($ this ->adapter ->getSupportForSchemaIndexes ()
4580+ && !($ this ->adapter ->getSharedTables () && $ this ->isMigrating ())) {
4581+ $ schemaIndexes = $ this ->getSchemaIndexes ($ collection ->getId ());
4582+ $ filteredId = $ this ->adapter ->filter ($ id );
45664583
4567- if (!$ created ) {
4568- throw new DatabaseException ('Failed to create index ' );
4584+ foreach ($ schemaIndexes as $ schemaIndex ) {
4585+ if (\strtolower ($ schemaIndex ->getId ()) === \strtolower ($ filteredId )) {
4586+ $ schemaColumns = $ schemaIndex ->getAttribute ('columns ' , []);
4587+ $ schemaLengths = $ schemaIndex ->getAttribute ('lengths ' , []);
4588+
4589+ $ filteredAttributes = \array_map (fn ($ a ) => $ this ->adapter ->filter ($ a ), $ attributes );
4590+ $ match = ($ schemaColumns === $ filteredAttributes && $ schemaLengths === $ lengths );
4591+
4592+ if ($ match ) {
4593+ $ existsInSchema = true ;
4594+ } else {
4595+ // Orphan index with wrong definition — drop so it
4596+ // gets recreated with the correct shape.
4597+ try {
4598+ $ this ->adapter ->deleteIndex ($ collection ->getId (), $ id );
4599+ } catch (NotFoundException ) {
4600+ }
4601+ }
4602+ break ;
4603+ }
4604+ }
4605+ }
4606+
4607+ if (!$ existsInSchema ) {
4608+ try {
4609+ $ created = $ this ->adapter ->createIndex ($ collection ->getId (), $ id , $ type , $ attributes , $ lengths , $ orders , $ indexAttributesWithTypes , [], $ ttl );
4610+
4611+ if (!$ created ) {
4612+ throw new DatabaseException ('Failed to create index ' );
4613+ }
4614+ } catch (DuplicateException ) {
4615+ // Metadata check (lines above) already verified index is absent
4616+ // from metadata. A DuplicateException from the adapter means the
4617+ // index exists only in physical schema — an orphan from a prior
4618+ // partial failure. Skip creation and proceed to metadata update.
45694619 }
4570- } catch (DuplicateException $ e ) {
4571- // Metadata check (lines above) already verified index is absent
4572- // from metadata. A DuplicateException from the adapter means the
4573- // index exists only in physical schema — an orphan from a prior
4574- // partial failure. Skip creation and proceed to metadata update.
45754620 }
45764621
45774622 $ collection ->setAttribute ('indexes ' , $ index , Document::SET_TYPE_APPEND );
@@ -9206,6 +9251,15 @@ public function getSchemaAttributes(string $collection): array
92069251 return $ this ->adapter ->getSchemaAttributes ($ collection );
92079252 }
92089253
9254+ /**
9255+ * @param string $collection
9256+ * @return array<Document>
9257+ */
9258+ public function getSchemaIndexes (string $ collection ): array
9259+ {
9260+ return $ this ->adapter ->getSchemaIndexes ($ collection );
9261+ }
9262+
92099263 /**
92109264 * @param string $collectionId
92119265 * @param string|null $documentId
0 commit comments