@@ -83,9 +83,6 @@ void deeplake_sync_tables_from_catalog(const std::string& root_path, icm::string
8383 Oid relid = RangeVarGetRelid (rel, NoLock, true );
8484
8585 if (!OidIsValid (relid)) {
86- // Table doesn't exist locally - create it
87- elog (LOG, " pg_deeplake sync: creating table %s from catalog" , qualified_name.c_str ());
88-
8986 // Gather columns for this table, sorted by position
9087 std::vector<pg::dl_catalog::column_meta> table_columns;
9188 for (const auto & col : catalog_columns) {
@@ -102,26 +99,12 @@ void deeplake_sync_tables_from_catalog(const std::string& root_path, icm::string
10299 }
103100
104101 const char * qschema = quote_identifier (meta.schema_name .c_str ());
102+ const char * qtable = quote_identifier (meta.table_name .c_str ());
105103
104+ // Build CREATE TABLE IF NOT EXISTS statement
106105 StringInfoData buf;
107106 initStringInfo (&buf);
108-
109- // Create schema if needed
110- appendStringInfo (&buf, " CREATE SCHEMA IF NOT EXISTS %s" , qschema);
111-
112- pg::utils::spi_connector connector;
113- if (SPI_execute (buf.data , false , 0 ) != SPI_OK_UTILITY) {
114- elog (WARNING, " pg_deeplake sync: failed to create schema %s" , meta.schema_name .c_str ());
115- pfree (buf.data );
116- continue ;
117- }
118-
119- // Build CREATE TABLE statement directly from catalog metadata
120- // This avoids calling the SQL function create_deeplake_table which may not exist
121- // in the postgres database (extension might not be installed there)
122- resetStringInfo (&buf);
123- const char * qtable = quote_identifier (meta.table_name .c_str ());
124- appendStringInfo (&buf, " CREATE TABLE %s.%s (" , qschema, qtable);
107+ appendStringInfo (&buf, " CREATE TABLE IF NOT EXISTS %s.%s (" , qschema, qtable);
125108
126109 bool first = true ;
127110 for (const auto & col : table_columns) {
@@ -131,18 +114,42 @@ void deeplake_sync_tables_from_catalog(const std::string& root_path, icm::string
131114 first = false ;
132115 appendStringInfo (&buf, " %s %s" , quote_identifier (col.column_name .c_str ()), col.pg_type .c_str ());
133116 }
134-
135- // Table path is now derived from deeplake.root_path GUC set at database level
136- // Path: {root_path}/{schema}/{table_name}
137117 appendStringInfo (&buf, " ) USING deeplake" );
138118
139- if (SPI_execute (buf.data , false , 0 ) != SPI_OK_UTILITY) {
140- // Don't log as warning - the dataset might not be available yet
141- // The sync worker will retry on the next cycle
142- elog (DEBUG1, " pg_deeplake sync: table %s not ready yet, will retry" , qualified_name.c_str ());
143- } else {
144- elog (LOG, " pg_deeplake sync: successfully created table %s" , qualified_name.c_str ());
119+ // Wrap in subtransaction so that if another backend concurrently
120+ // creates the same table (race on composite type), the error is
121+ // caught and we continue instead of aborting the sync cycle.
122+ MemoryContext saved_context = CurrentMemoryContext;
123+ ResourceOwner saved_owner = CurrentResourceOwner;
124+
125+ BeginInternalSubTransaction (NULL );
126+ PG_TRY ();
127+ {
128+ pg::utils::spi_connector connector;
129+
130+ // Create schema if needed
131+ StringInfoData schema_buf;
132+ initStringInfo (&schema_buf);
133+ appendStringInfo (&schema_buf, " CREATE SCHEMA IF NOT EXISTS %s" , qschema);
134+ SPI_execute (schema_buf.data , false , 0 );
135+ pfree (schema_buf.data );
136+
137+ if (SPI_execute (buf.data , false , 0 ) == SPI_OK_UTILITY) {
138+ elog (LOG, " pg_deeplake sync: successfully created table %s" , qualified_name.c_str ());
139+ }
140+
141+ ReleaseCurrentSubTransaction ();
142+ }
143+ PG_CATCH ();
144+ {
145+ // Another backend created this table concurrently — not an error.
146+ MemoryContextSwitchTo (saved_context);
147+ CurrentResourceOwner = saved_owner;
148+ RollbackAndReleaseCurrentSubTransaction ();
149+ FlushErrorState ();
150+ elog (DEBUG1, " pg_deeplake sync: concurrent creation of %s, skipping" , qualified_name.c_str ());
145151 }
152+ PG_END_TRY ();
146153
147154 pfree (buf.data );
148155 }
0 commit comments