Skip to content

Commit 564b272

Browse files
committed
Add thread safety on database creation
1 parent dcd795c commit 564b272

File tree

3 files changed

+91
-66
lines changed

3 files changed

+91
-66
lines changed

data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/JDBCVectorStoreQueryProvider.java

Lines changed: 79 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,12 @@
2929
import java.util.HashMap;
3030
import java.util.List;
3131
import java.util.Map;
32+
import java.util.concurrent.locks.Lock;
33+
import java.util.concurrent.locks.ReentrantLock;
3234
import java.util.stream.Collectors;
3335
import java.util.stream.Stream;
3436
import javax.annotation.Nonnull;
37+
import javax.annotation.concurrent.GuardedBy;
3538
import javax.sql.DataSource;
3639
import org.slf4j.Logger;
3740
import org.slf4j.LoggerFactory;
@@ -54,6 +57,8 @@ public class JDBCVectorStoreQueryProvider
5457
private final String collectionsTable;
5558
private final String prefixForCollectionTables;
5659

60+
private final Object dbCreationLock = new Object();
61+
5762
@SuppressFBWarnings("EI_EXPOSE_REP2") // DataSource is not exposed
5863
protected JDBCVectorStoreQueryProvider(
5964
@Nonnull DataSource dataSource,
@@ -89,12 +94,13 @@ protected JDBCVectorStoreQueryProvider(
8994

9095
/**
9196
* Creates a new instance of the JDBCVectorStoreQueryProvider class.
92-
* @param dataSource the data source
93-
* @param collectionsTable the collections table
97+
*
98+
* @param dataSource the data source
99+
* @param collectionsTable the collections table
94100
* @param prefixForCollectionTables the prefix for collection tables
95-
* @param supportedKeyTypes the supported key types
96-
* @param supportedDataTypes the supported data types
97-
* @param supportedVectorTypes the supported vector types
101+
* @param supportedKeyTypes the supported key types
102+
* @param supportedDataTypes the supported data types
103+
* @param supportedVectorTypes the supported vector types
98104
*/
99105
public JDBCVectorStoreQueryProvider(
100106
@SuppressFBWarnings("EI_EXPOSE_REP2") @Nonnull DataSource dataSource,
@@ -276,48 +282,57 @@ public boolean collectionExists(String collectionName) {
276282
*/
277283
@Override
278284
@SuppressFBWarnings("SQL_PREPARED_STATEMENT_GENERATED_FROM_NONCONSTANT_STRING")
285+
@GuardedBy("dbCreationLock")
279286
// SQL query is generated dynamically with valid identifiers
280287
public void createCollection(String collectionName,
281288
VectorStoreRecordDefinition recordDefinition) {
282289

283-
// No approximate search is supported in JDBCVectorStoreQueryProvider
284-
if (recordDefinition.getVectorFields().stream()
285-
.anyMatch(
286-
field -> field.getIndexKind() != null && field.getIndexKind() != IndexKind.FLAT
287-
&& field.getIndexKind() != IndexKind.UNDEFINED)) {
288-
LOGGER
289-
.warn(String.format("Indexes are not supported in %s. Ignoring indexKind property.",
290-
this.getClass().getName()));
291-
}
292-
293-
String createStorageTable = formatQuery("CREATE TABLE IF NOT EXISTS %s ("
294-
+ "%s VARCHAR(255) PRIMARY KEY, "
295-
+ "%s, "
296-
+ "%s);",
297-
getCollectionTableName(collectionName),
298-
getKeyColumnName(recordDefinition.getKeyField()),
299-
getColumnNamesAndTypes(new ArrayList<>(recordDefinition.getDataFields()),
300-
getSupportedDataTypes()),
301-
getColumnNamesAndTypes(new ArrayList<>(recordDefinition.getVectorFields()),
302-
getSupportedVectorTypes()));
290+
synchronized (dbCreationLock) {
291+
// No approximate search is supported in JDBCVectorStoreQueryProvider
292+
if (recordDefinition.getVectorFields().stream()
293+
.anyMatch(
294+
field -> field.getIndexKind() != null && field.getIndexKind() != IndexKind.FLAT
295+
&& field.getIndexKind() != IndexKind.UNDEFINED)) {
296+
LOGGER
297+
.warn(String.format(
298+
"Indexes are not supported in %s. Ignoring indexKind property.",
299+
this.getClass().getName()));
300+
}
303301

304-
String insertCollectionQuery = formatQuery("INSERT INTO %s (collectionId) VALUES (?)",
305-
validateSQLidentifier(collectionsTable));
302+
String createStorageTable = formatQuery("CREATE TABLE IF NOT EXISTS %s ("
303+
+ "%s VARCHAR(255) PRIMARY KEY, "
304+
+ "%s, "
305+
+ "%s);",
306+
getCollectionTableName(collectionName),
307+
getKeyColumnName(recordDefinition.getKeyField()),
308+
getColumnNamesAndTypes(new ArrayList<>(recordDefinition.getDataFields()),
309+
getSupportedDataTypes()),
310+
getColumnNamesAndTypes(new ArrayList<>(recordDefinition.getVectorFields()),
311+
getSupportedVectorTypes()));
312+
313+
String insertCollectionQuery = this.getInsertCollectionQuery(collectionsTable);
314+
315+
try (Connection connection = dataSource.getConnection();
316+
PreparedStatement createTable = connection.prepareStatement(createStorageTable)) {
317+
createTable.execute();
318+
} catch (SQLException e) {
319+
throw new SKException("Failed to create collection", e);
320+
}
306321

307-
try (Connection connection = dataSource.getConnection();
308-
PreparedStatement createTable = connection.prepareStatement(createStorageTable)) {
309-
createTable.execute();
310-
} catch (SQLException e) {
311-
throw new SKException("Failed to create collection", e);
322+
try (Connection connection = dataSource.getConnection();
323+
PreparedStatement insert = connection.prepareStatement(insertCollectionQuery)) {
324+
insert.setObject(1, collectionName);
325+
insert.execute();
326+
} catch (SQLException e) {
327+
throw new SKException("Failed to insert collection", e);
328+
}
312329
}
330+
}
313331

314-
try (Connection connection = dataSource.getConnection();
315-
PreparedStatement insert = connection.prepareStatement(insertCollectionQuery)) {
316-
insert.setObject(1, collectionName);
317-
insert.execute();
318-
} catch (SQLException e) {
319-
throw new SKException("Failed to insert collection", e);
320-
}
332+
protected String getInsertCollectionQuery(String collectionsTable) {
333+
return formatQuery(
334+
"INSERT IGNORE INTO %s (collectionId) VALUES (?)",
335+
validateSQLidentifier(collectionsTable));
321336
}
322337

323338
/**
@@ -327,26 +342,29 @@ public void createCollection(String collectionName,
327342
* @throws SKException if an error occurs while deleting the collection
328343
*/
329344
@Override
345+
@GuardedBy("dbCreationLock")
330346
public void deleteCollection(String collectionName) {
331-
String deleteCollectionOperation = formatQuery("DELETE FROM %s WHERE collectionId = ?",
332-
validateSQLidentifier(collectionsTable));
333-
String dropTableOperation = formatQuery("DROP TABLE %s",
334-
getCollectionTableName(collectionName));
335-
336-
try (Connection connection = dataSource.getConnection();
337-
PreparedStatement deleteCollection = connection
338-
.prepareStatement(deleteCollectionOperation)) {
339-
deleteCollection.setObject(1, collectionName);
340-
deleteCollection.execute();
341-
} catch (SQLException e) {
342-
throw new SKException("Failed to delete collection", e);
343-
}
347+
synchronized (dbCreationLock) {
348+
String deleteCollectionOperation = formatQuery("DELETE FROM %s WHERE collectionId = ?",
349+
validateSQLidentifier(collectionsTable));
350+
String dropTableOperation = formatQuery("DROP TABLE %s",
351+
getCollectionTableName(collectionName));
352+
353+
try (Connection connection = dataSource.getConnection();
354+
PreparedStatement deleteCollection = connection
355+
.prepareStatement(deleteCollectionOperation)) {
356+
deleteCollection.setObject(1, collectionName);
357+
deleteCollection.execute();
358+
} catch (SQLException e) {
359+
throw new SKException("Failed to delete collection", e);
360+
}
344361

345-
try (Connection connection = dataSource.getConnection();
346-
PreparedStatement dropTable = connection.prepareStatement(dropTableOperation)) {
347-
dropTable.execute();
348-
} catch (SQLException e) {
349-
throw new SKException("Failed to drop table", e);
362+
try (Connection connection = dataSource.getConnection();
363+
PreparedStatement dropTable = connection.prepareStatement(dropTableOperation)) {
364+
dropTable.execute();
365+
} catch (SQLException e) {
366+
throw new SKException("Failed to drop table", e);
367+
}
350368
}
351369
}
352370

@@ -518,8 +536,8 @@ protected <Record> List<Record> getRecordsWithFilter(String collectionName,
518536
*
519537
* @param <Record> the record type
520538
* @param collectionName the collection name
521-
* @param vector the vector to search with
522-
* @param options the search options
539+
* @param vector the vector to search with
540+
* @param options the search options
523541
* @param recordDefinition the record definition
524542
* @param mapper the mapper, responsible for mapping the result set to the record
525543
* type.
@@ -622,8 +640,8 @@ public String getFilter(VectorSearchFilter filter,
622640
}
623641

624642
/**
625-
* Gets the filter parameters for the given vector search filter to associate with the filter string
626-
* generated by the getFilter method.
643+
* Gets the filter parameters for the given vector search filter to associate with the filter
644+
* string generated by the getFilter method.
627645
*
628646
* @param filter The filter to get the filter parameters for.
629647
* @return The filter parameters.

data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/JDBCVectorStoreRecordCollection.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
import com.microsoft.semantickernel.data.jdbc.postgres.PostgreSQLVectorStoreQueryProvider;
77
import com.microsoft.semantickernel.data.jdbc.postgres.PostgreSQLVectorStoreRecordMapper;
88
import com.microsoft.semantickernel.data.vectorsearch.VectorSearchResults;
9-
import com.microsoft.semantickernel.data.vectorstorage.VectorStoreRecordMapper;
109
import com.microsoft.semantickernel.data.vectorstorage.VectorStoreRecordCollection;
10+
import com.microsoft.semantickernel.data.vectorstorage.VectorStoreRecordMapper;
1111
import com.microsoft.semantickernel.data.vectorstorage.definition.VectorStoreRecordDefinition;
1212
import com.microsoft.semantickernel.data.vectorstorage.options.DeleteRecordOptions;
1313
import com.microsoft.semantickernel.data.vectorstorage.options.GetRecordOptions;
@@ -27,10 +27,9 @@
2727
import reactor.core.scheduler.Schedulers;
2828

2929
/**
30-
* The JDBCVectorStoreRecordCollection class represents a collection of records
31-
* in a JDBC vector store. It implements the SQLVectorStoreRecordCollection
32-
* interface and provides methods for managing the collection, such as creating,
33-
* deleting, and upserting records.
30+
* The JDBCVectorStoreRecordCollection class represents a collection of records in a JDBC vector
31+
* store. It implements the SQLVectorStoreRecordCollection interface and provides methods for
32+
* managing the collection, such as creating, deleting, and upserting records.
3433
*
3534
* @param <Record> the type of the records in the collection
3635
*/
@@ -322,6 +321,7 @@ public Mono<VectorSearchResults<Record>> searchAsync(List<Float> vector,
322321

323322
/**
324323
* Builder for a JDBCVectorStoreRecordCollection.
324+
*
325325
* @param <Record> the type of the records in the collection
326326
*/
327327
public static class Builder<Record>

data/semantickernel-data-jdbc/src/main/java/com/microsoft/semantickernel/data/jdbc/sqlite/SQLiteVectorStoreQueryProvider.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ public void upsertRecords(String collectionName, List<?> records,
112112
}
113113
}
114114

115+
@Override
116+
protected String getInsertCollectionQuery(String collectionsTable) {
117+
return formatQuery(
118+
"INSERT OR IGNORE INTO %s (collectionId) VALUES (?)",
119+
validateSQLidentifier(collectionsTable));
120+
}
121+
115122
/**
116123
* A builder for {@code SQLiteVectorStoreQueryProvider}.
117124
*/

0 commit comments

Comments
 (0)